From deb16a3db0e6959f67ec262504f5d6bc27fa413d Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Sun, 11 Jun 2023 15:02:44 +0800 Subject: [PATCH] Implement sliders Use @reach/slider to have styled enhanced sliders. --- .../web/kitchen-sink/react-next/package.json | 1 + .../src/pages/categories/blob/index.tsx | 16 +- .../src/pages/categories/number/index.tsx | 192 +++++++++++++++++- .../pages/categories/presentation/index.tsx | 35 ++++ .../kitchen-sink/react-next/src/tailwind.css | 4 + .../react-next/src/utils/event.ts | 13 ++ .../web/kitchen-sink/react-next/yarn.lock | 26 +++ 7 files changed, 276 insertions(+), 11 deletions(-) create mode 100644 packages/web/kitchen-sink/react-next/src/pages/categories/presentation/index.tsx create mode 100644 packages/web/kitchen-sink/react-next/src/utils/event.ts diff --git a/packages/web/kitchen-sink/react-next/package.json b/packages/web/kitchen-sink/react-next/package.json index fcc3db9..9ba0907 100644 --- a/packages/web/kitchen-sink/react-next/package.json +++ b/packages/web/kitchen-sink/react-next/package.json @@ -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", diff --git a/packages/web/kitchen-sink/react-next/src/pages/categories/blob/index.tsx b/packages/web/kitchen-sink/react-next/src/pages/categories/blob/index.tsx index 4e6ecd7..d45216d 100644 --- a/packages/web/kitchen-sink/react-next/src/pages/categories/blob/index.tsx +++ b/packages/web/kitchen-sink/react-next/src/pages/categories/blob/index.tsx @@ -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; @@ -782,7 +783,7 @@ export const FileSelectBox = React.forwardRef }: FileButtonProps, forwardedRef, ) => { - const [isEnhanced, setIsEnhanced] = React.useState(false); + const [renderEnhanced, setRenderEnhanced] = React.useState(false); const [fileList, setFileList] = React.useState(); const defaultRef = React.useRef(null); const ref = forwardedRef ?? defaultRef; @@ -801,6 +802,9 @@ export const FileSelectBox = React.forwardRef 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 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 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 } { (fileList?.length ?? 0) < 1 - && isEnhanced + && renderEnhanced && hint && (
} { (fileList?.length ?? 0) > 0 - && isEnhanced + && renderEnhanced && ( <>
diff --git a/packages/web/kitchen-sink/react-next/src/pages/categories/number/index.tsx b/packages/web/kitchen-sink/react-next/src/pages/categories/number/index.tsx index 300610a..0b09879 100644 --- a/packages/web/kitchen-sink/react-next/src/pages/categories/number/index.tsx +++ b/packages/web/kitchen-sink/react-next/src/pages/categories/number/index.tsx @@ -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, 'type' | 'min' | 'max' | 'list'> { + enhanced?: boolean; + orient?: SliderOrientation; + min?: number; + max?: number; + block?: boolean; + tickMarks?: (number | SelectControlBase.SelectOption)[]; +} + +const RenderOptions: React.FC = ({ + options, + optionComponent: Option = 'option', + optgroupComponent: Optgroup = 'optgroup', + level = 0, +}: RenderOptionsProps) => ( + <> + { + options.map((o) => { + if (typeof o === 'number') { + return ( + + ); + } + + if (typeof o.children !== 'undefined') { + if (level === 0) { + return ( + + + + ); + } + return ( + + + + + ); + } + + return null; + }) + } + +); + +export const Slider = React.forwardRef(({ + 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(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 + && ( + + + + ) + } +
+ 0 ? tickMarkId : undefined} + onChange={onChange} + /> + { + renderEnhanced + && ( + <> + + + + + + + + + ) + } +
+ + ); +}); + +Slider.displayName = 'Slider'; const NumberPage: NextPage = () => { return ( @@ -24,9 +205,12 @@ const NumberPage: NextPage = () => { Slider
- TODO - - +
@@ -35,9 +219,7 @@ const NumberPage: NextPage = () => {
TODO - -
diff --git a/packages/web/kitchen-sink/react-next/src/pages/categories/presentation/index.tsx b/packages/web/kitchen-sink/react-next/src/pages/categories/presentation/index.tsx new file mode 100644 index 0000000..5e33e5d --- /dev/null +++ b/packages/web/kitchen-sink/react-next/src/pages/categories/presentation/index.tsx @@ -0,0 +1,35 @@ +import { NextPage } from 'next'; +import { DefaultLayout } from 'src/components/DefaultLayout'; + +const PresentationPage: NextPage = () => { + return ( + +
+
+
+

+ Tabs +

+
+ TODO +
+
+
+
+
+

+ Accordion +

+
+ TODO +
+
+
+
+
+ ) +} + +export default PresentationPage; diff --git a/packages/web/kitchen-sink/react-next/src/tailwind.css b/packages/web/kitchen-sink/react-next/src/tailwind.css index 65dd5f6..47453aa 100644 --- a/packages/web/kitchen-sink/react-next/src/tailwind.css +++ b/packages/web/kitchen-sink/react-next/src/tailwind.css @@ -1 +1,5 @@ @tailwind utilities; + +input[type="range"][orient="vertical"] { + appearance: slider-vertical; +} diff --git a/packages/web/kitchen-sink/react-next/src/utils/event.ts b/packages/web/kitchen-sink/react-next/src/utils/event.ts new file mode 100644 index 0000000..1455596 --- /dev/null +++ b/packages/web/kitchen-sink/react-next/src/utils/event.ts @@ -0,0 +1,13 @@ +export const delegateTriggerChangeEvent = (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); + } + } +} diff --git a/packages/web/kitchen-sink/react-next/yarn.lock b/packages/web/kitchen-sink/react-next/yarn.lock index 5160fbc..78e4c36 100644 --- a/packages/web/kitchen-sink/react-next/yarn.lock +++ b/packages/web/kitchen-sink/react-next/yarn.lock @@ -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"