Browse Source

Implement sliders

Use @reach/slider to have styled enhanced sliders.
pull/1/head
TheoryOfNekomata 1 year ago
parent
commit
deb16a3db0
7 changed files with 276 additions and 11 deletions
  1. +1
    -0
      packages/web/kitchen-sink/react-next/package.json
  2. +10
    -6
      packages/web/kitchen-sink/react-next/src/pages/categories/blob/index.tsx
  3. +187
    -5
      packages/web/kitchen-sink/react-next/src/pages/categories/number/index.tsx
  4. +35
    -0
      packages/web/kitchen-sink/react-next/src/pages/categories/presentation/index.tsx
  5. +4
    -0
      packages/web/kitchen-sink/react-next/src/tailwind.css
  6. +13
    -0
      packages/web/kitchen-sink/react-next/src/utils/event.ts
  7. +26
    -0
      packages/web/kitchen-sink/react-next/yarn.lock

+ 1
- 0
packages/web/kitchen-sink/react-next/package.json View File

@@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@reach/slider": "^0.18.0",
"@tesseract-design/web-action-react": "link:../../categories/action/react",
"@tesseract-design/web-blob-react": "link:../../categories/blob/react",
"@tesseract-design/web-formatted-react": "link:../../categories/formatted/react",


+ 10
- 6
packages/web/kitchen-sink/react-next/src/pages/categories/blob/index.tsx View File

@@ -17,6 +17,7 @@ import {
import {getImageMetadata} from '../../../utils/image';
import {getAudioMetadata} from '../../../utils/audio';
import {getTextMetadata} from '../../../utils/text';
import {delegateTriggerChangeEvent} from '../../../utils/event';

interface FileWithPreview extends File {
metadata?: Record<string, string | number | ArrayBuffer>;
@@ -782,7 +783,7 @@ export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps>
}: FileButtonProps,
forwardedRef,
) => {
const [isEnhanced, setIsEnhanced] = React.useState(false);
const [renderEnhanced, setRenderEnhanced] = React.useState(false);
const [fileList, setFileList] = React.useState<FileList>();
const defaultRef = React.useRef<HTMLInputElement>(null);
const ref = forwardedRef ?? defaultRef;
@@ -801,6 +802,9 @@ export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps>
if (typeof ref === 'object' && ref.current) {
ref.current.value = '';
setFileList(undefined);
setTimeout(() => {
delegateTriggerChangeEvent(ref.current as HTMLInputElement);
})
}
};

@@ -815,12 +819,12 @@ export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps>
if (typeof ref === 'object' && ref.current) {
const { files } = dataTransfer;
setFileList(ref.current.files = files);
ref.current.dispatchEvent(new Event('change'));
delegateTriggerChangeEvent(ref.current);
}
}

React.useEffect(() => {
setIsEnhanced(enhanced);
setRenderEnhanced(enhanced);
}, [enhanced]);

return (
@@ -843,7 +847,7 @@ export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps>
disabled={disabled}
ref={ref}
type="file"
className={`${enhanced ? 'sr-only' : ''}`}
className={`${renderEnhanced ? 'sr-only' : ''}`}
onChange={addFile}
multiple={multiple}
data-testid="input"
@@ -879,7 +883,7 @@ export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps>
}
{
(fileList?.length ?? 0) < 1
&& isEnhanced
&& renderEnhanced
&& hint
&& (
<div
@@ -894,7 +898,7 @@ export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps>
}
{
(fileList?.length ?? 0) > 0
&& isEnhanced
&& renderEnhanced
&& (
<>
<div className={`absolute top-0 left-0 w-full h-full pointer-events-none pb-12 box-border overflow-hidden pt-8`}>


+ 187
- 5
packages/web/kitchen-sink/react-next/src/pages/categories/number/index.tsx View File

@@ -1,4 +1,185 @@
import * as React from 'react';
import { NextPage } from 'next';
import * as ReachSlider from '@reach/slider';
import '@reach/slider/styles.css';
import {delegateTriggerChangeEvent} from '../../../utils/event';
import * as SelectControlBase from '@tesseract-design/web-base-selectcontrol';

interface RenderOptionsProps {
options: (number | SelectControlBase.SelectOption)[],
optionComponent?: React.ElementType,
optgroupComponent?: React.ElementType,
level?: number,
}

export enum SliderOrientation {
HORIZONTAL = 'horizontal',
VERTICAL = 'vertical',
}

export interface SliderProps extends Omit<React.HTMLProps<HTMLInputElement>, 'type' | 'min' | 'max' | 'list'> {
enhanced?: boolean;
orient?: SliderOrientation;
min?: number;
max?: number;
block?: boolean;
tickMarks?: (number | SelectControlBase.SelectOption)[];
}

const RenderOptions: React.FC<RenderOptionsProps> = ({
options,
optionComponent: Option = 'option',
optgroupComponent: Optgroup = 'optgroup',
level = 0,
}: RenderOptionsProps) => (
<>
{
options.map((o) => {
if (typeof o === 'number') {
return (
<Option
key={`${o}:${o}`}
value={o}
/>
)
}

if (typeof o.value !== 'undefined') {
return (
<Option
key={`${o.label}:${o.value.toString()}`}
value={o.value}
label={o.label}
>
{o.label}
</Option>
);
}

if (typeof o.children !== 'undefined') {
if (level === 0) {
return (
<Optgroup
key={o.label}
label={o.label}
>
<RenderOptions
options={o.children}
optionComponent={Option}
optgroupComponent={Optgroup}
level={level + 1}
/>
</Optgroup>
);
}
return (
<React.Fragment
key={o.label}
>
<Option
disabled
>
{o.label}
</Option>
<RenderOptions
options={o.children}
optionComponent={Option}
optgroupComponent={Optgroup}
level={level + 1}
/>
</React.Fragment>
);
}

return null;
})
}
</>
);

export const Slider = React.forwardRef<HTMLInputElement, SliderProps>(({
enhanced = false,
orient = SliderOrientation.HORIZONTAL,
min = 0,
max = 100,
step = 'any',
block = false,
tickMarks = [],
onChange,
...etcProps
}, forwardedRef) => {
const [renderEnhanced, setRenderEnhanced] = React.useState(false);
const defaultRef = React.useRef<HTMLInputElement>(null);
const ref = forwardedRef ?? defaultRef;
const nonStandardProps: any = {
// Gecko (Firefox) fallback
orient,
}
const tickMarkId = React.useId();

React.useEffect(() => {
setRenderEnhanced(enhanced);
}, [enhanced]);

const handleEnhancedRangeChange = (newValue: number) => {
if (typeof ref === 'object' && ref.current) {
delegateTriggerChangeEvent(ref.current, newValue);
}
}

return (
<>
{
tickMarks.length > 0
&& (
<datalist
id={tickMarkId}
>
<RenderOptions
options={tickMarks}
/>
</datalist>
)
}
<div>
<input
{...etcProps}
{...nonStandardProps}
min={min}
max={max}
step={step ?? 'any'}
type="range"
className={`${renderEnhanced ? 'sr-only' : ''} ${block ? 'w-full' : ''}`}
ref={ref}
list={tickMarks.length > 0 ? tickMarkId : undefined}
onChange={onChange}
/>
{
renderEnhanced
&& (
<>
<ReachSlider.SliderInput
min={min}
max={max}
step={typeof step === 'number' ? step : Number.EPSILON}
orientation={orient === SliderOrientation.VERTICAL ? ReachSlider.SliderOrientation.Vertical : ReachSlider.SliderOrientation.Horizontal}
onChange={handleEnhancedRangeChange}
>
<ReachSlider.SliderTrack>
<ReachSlider.SliderRange />
<RenderOptions options={tickMarks} optionComponent={ReachSlider.SliderMarker} optgroupComponent={React.Fragment} />
<ReachSlider.SliderHandle />
</ReachSlider.SliderTrack>
</ReachSlider.SliderInput>
</>
)
}
</div>
</>
);
});

Slider.displayName = 'Slider';

const NumberPage: NextPage = () => {
return (
@@ -24,9 +205,12 @@ const NumberPage: NextPage = () => {
Slider
</h2>
<div>
TODO

<input type="range" />
<Slider
min={-100}
max={100}
tickMarks={[{ label: 'low', value: 25, }, 50]}
enhanced
/>
</div>
</section>
<section>
@@ -35,9 +219,7 @@ const NumberPage: NextPage = () => {
</h2>
<div>
TODO

<input type="range" />

<input type="range" />
</div>
</section>


+ 35
- 0
packages/web/kitchen-sink/react-next/src/pages/categories/presentation/index.tsx View File

@@ -0,0 +1,35 @@
import { NextPage } from 'next';
import { DefaultLayout } from 'src/components/DefaultLayout';

const PresentationPage: NextPage = () => {
return (
<DefaultLayout
title="Code"
>
<main className="mt-8 mb-16 md:mt-16 md:mb-32">
<section>
<div className="container mx-auto px-4">
<h2>
Tabs
</h2>
<div>
TODO
</div>
</div>
</section>
<section>
<div className="container mx-auto px-4">
<h2>
Accordion
</h2>
<div>
TODO
</div>
</div>
</section>
</main>
</DefaultLayout>
)
}

export default PresentationPage;

+ 4
- 0
packages/web/kitchen-sink/react-next/src/tailwind.css View File

@@ -1 +1,5 @@
@tailwind utilities;

input[type="range"][orient="vertical"] {
appearance: slider-vertical;
}

+ 13
- 0
packages/web/kitchen-sink/react-next/src/utils/event.ts View File

@@ -0,0 +1,13 @@
export const delegateTriggerChangeEvent = <T extends HTMLElement>(target: T, value?: unknown) => {
if (target.tagName === 'INPUT') {
const inputTarget = target as unknown as HTMLInputElement;
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
if (nativeInputValueSetter) {
if (inputTarget.type !== 'file') {
nativeInputValueSetter.call(inputTarget, value);
}
const simulatedEvent = new Event('change', {bubbles: true});
inputTarget.dispatchEvent(simulatedEvent);
}
}
}

+ 26
- 0
packages/web/kitchen-sink/react-next/yarn.lock View File

@@ -183,6 +183,32 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@reach/auto-id@0.18.0":
version "0.18.0"
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.18.0.tgz#4b97085cd1cf1360a9bedc6e9c78e97824014f0d"
integrity sha512-XwY1IwhM7mkHZFghhjiqjQ6dstbOdpbFLdggeke75u8/8icT8uEHLbovFUgzKjy9qPvYwZIB87rLiR8WdtOXCg==
dependencies:
"@reach/utils" "0.18.0"
"@reach/polymorphic@0.18.0":
version "0.18.0"
resolved "https://registry.yarnpkg.com/@reach/polymorphic/-/polymorphic-0.18.0.tgz#2fe42007a774e06cdbc8e13e0d46f2dc30f2f1ed"
integrity sha512-N9iAjdMbE//6rryZZxAPLRorzDcGBnluf7YQij6XDLiMtfCj1noa7KyLpEc/5XCIB/EwhX3zCluFAwloBKdblA==
"@reach/slider@^0.18.0":
version "0.18.0"
resolved "https://registry.yarnpkg.com/@reach/slider/-/slider-0.18.0.tgz#a2dbdad76611b0f12abc551849978e6237555331"
integrity sha512-DLq8ziZn74P/puY0F2tO7fR67PPp7/MIiydfvUdB3kefmn8ewXMUrY7tO6sdhVXk2y2Jtncd5PO1NStPn1QFfw==
dependencies:
"@reach/auto-id" "0.18.0"
"@reach/polymorphic" "0.18.0"
"@reach/utils" "0.18.0"
"@reach/utils@0.18.0":
version "0.18.0"
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.18.0.tgz#4f3cebe093dd436eeaff633809bf0f68f4f9d2ee"
integrity sha512-KdVMdpTgDyK8FzdKO9SCpiibuy/kbv3pwgfXshTI6tEcQT1OOwj7BAksnzGC0rPz0UholwC+AgkqEl3EJX3M1A==
"@rushstack/eslint-patch@^1.1.3":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728"


Loading…
Cancel
Save