From 075cc05db530ea4a255fe4a8afc2eac6766241ac Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 11:43:31 +0700 Subject: [PATCH 01/27] =?UTF-8?q?=E2=9A=92=EF=B8=8F=20build:=20add=20@radi?= =?UTF-8?q?x-ui/react-toast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/package.json | 1 + yarn.lock | 55 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/packages/frontend/package.json b/packages/frontend/package.json index a9674426..0e22ca7b 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -8,6 +8,7 @@ "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-toast": "^1.1.5", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/yarn.lock b/yarn.lock index d24c5a8a..98e99a0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3342,6 +3342,18 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-dismissable-layer@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4" + integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-escape-keydown" "1.0.3" + "@radix-ui/react-id@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" @@ -3350,6 +3362,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-portal@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15" + integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-presence@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba" @@ -3406,6 +3426,25 @@ "@radix-ui/react-roving-focus" "1.0.4" "@radix-ui/react-use-controllable-state" "1.0.1" +"@radix-ui/react-toast@^1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-toast/-/react-toast-1.1.5.tgz#f5788761c0142a5ae9eb97f0051fd3c48106d9e6" + integrity sha512-fRLn227WHIBRSzuRzGJ8W+5YALxofH23y0MlPLddaIpLpCDqdE0NZlS2NRQDRiptfxDeeCjgFIpexB1/zkxDlw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-collection" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-visually-hidden" "1.0.3" + "@radix-ui/react-use-callback-ref@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a" @@ -3421,6 +3460,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-use-escape-keydown@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" + integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" @@ -3443,6 +3490,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-visually-hidden@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz#51aed9dd0fe5abcad7dee2a234ad36106a6984ac" + integrity sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + "@remix-run/router@1.13.1": version "1.13.1" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.13.1.tgz#07e2a8006f23a3bc898b3f317e0a58cc8076b86e" From 3928e2610fe2b866484ec51a045b5bcacc20a6e7 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 11:45:01 +0700 Subject: [PATCH 02/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20zIndex=20to?= =?UTF-8?q?ast=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/tailwind.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 4bcb0b01..8279056b 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -157,6 +157,9 @@ export default withMT({ 3.25: '0.8125rem', 3.5: '0.875rem', }, + zIndex: { + toast: '9999', + }, }, }, plugins: [], From 98a4d7be07c3bfc67aeccd327ffb71087c87bf1a Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 11:45:35 +0700 Subject: [PATCH 03/27] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20implement=20?= =?UTF-8?q?toast=20contexts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/shared/Toast/ToastProvider.tsx | 15 ++ .../src/components/shared/Toast/Toaster.tsx | 25 +++ .../src/components/shared/Toast/useToast.tsx | 190 ++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 packages/frontend/src/components/shared/Toast/ToastProvider.tsx create mode 100644 packages/frontend/src/components/shared/Toast/Toaster.tsx create mode 100644 packages/frontend/src/components/shared/Toast/useToast.tsx diff --git a/packages/frontend/src/components/shared/Toast/ToastProvider.tsx b/packages/frontend/src/components/shared/Toast/ToastProvider.tsx new file mode 100644 index 00000000..57ecf2b6 --- /dev/null +++ b/packages/frontend/src/components/shared/Toast/ToastProvider.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { + Provider, + Viewport, + type ToastProviderProps, +} from '@radix-ui/react-toast'; + +export const ToastProvider = ({ children, ...props }: ToastProviderProps) => { + return ( + + {children} + + + ); +}; diff --git a/packages/frontend/src/components/shared/Toast/Toaster.tsx b/packages/frontend/src/components/shared/Toast/Toaster.tsx new file mode 100644 index 00000000..85222167 --- /dev/null +++ b/packages/frontend/src/components/shared/Toast/Toaster.tsx @@ -0,0 +1,25 @@ +import React, { ComponentPropsWithoutRef, useMemo } from 'react'; +import { Provider, Viewport } from '@radix-ui/react-toast'; +import { SimpleToast, SimpleToastProps } from './SimpleToast'; +import { useToast } from './useToast'; + +interface ToasterProps extends ComponentPropsWithoutRef<'div'> {} + +export const Toaster = ({}: ToasterProps) => { + const { toasts } = useToast(); + + const renderToasts = useMemo( + () => + toasts.map(({ id, ...props }) => ( + + )), + [toasts], + ); + + return ( + + {renderToasts} + + + ); +}; diff --git a/packages/frontend/src/components/shared/Toast/useToast.tsx b/packages/frontend/src/components/shared/Toast/useToast.tsx new file mode 100644 index 00000000..ad088be4 --- /dev/null +++ b/packages/frontend/src/components/shared/Toast/useToast.tsx @@ -0,0 +1,190 @@ +// Inspired by react-hot-toast library +import React from 'react'; +import { type ToastProps } from '@radix-ui/react-toast'; + +const TOAST_LIMIT = 3; +const TOAST_REMOVE_DELAY_DEFAULT = 7000; + +type ToasterToast = ToastProps & { + id: string; +}; + +const actionTypes = { + ADD_TOAST: 'ADD_TOAST', + UPDATE_TOAST: 'UPDATE_TOAST', + DISMISS_TOAST: 'DISMISS_TOAST', + REMOVE_TOAST: 'REMOVE_TOAST', +} as const; + +let count = 0; + +const genId = () => { + count = (count + 1) % Number.MAX_VALUE; + return count.toString(); +}; + +type ActionType = typeof actionTypes; + +type Action = + | { + type: ActionType['ADD_TOAST']; + toast: ToasterToast; + } + | { + type: ActionType['UPDATE_TOAST']; + toast: Partial; + } + | { + type: ActionType['DISMISS_TOAST']; + toastId?: ToasterToast['id']; + duration?: ToasterToast['duration']; + } + | { + type: ActionType['REMOVE_TOAST']; + toastId?: ToasterToast['id']; + }; + +interface State { + toasts: ToasterToast[]; +} + +const toastTimeouts = new Map>(); + +const addToRemoveQueue = (toastId: string, duration: number) => { + if (toastTimeouts.has(toastId)) { + return; + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId); + dispatch({ + type: 'REMOVE_TOAST', + toastId: toastId, + }); + }, duration ?? TOAST_REMOVE_DELAY_DEFAULT); + + toastTimeouts.set(toastId, timeout); +}; + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case 'ADD_TOAST': + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + }; + + case 'UPDATE_TOAST': + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id + ? ({ ...t, ...action.toast } as ToasterToast) + : t, + ), + }; + + case 'DISMISS_TOAST': { + const { toastId, duration = TOAST_REMOVE_DELAY_DEFAULT } = action; + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId, duration); + } else { + state.toasts.forEach((_toast) => { + addToRemoveQueue(_toast.id, duration); + }); + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t, + ), + }; + } + case 'REMOVE_TOAST': + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + }; + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + }; + } +}; + +const listeners: Array<(_state: State) => void> = []; + +let memoryState: State = { toasts: [] }; + +const dispatch = (action: Action) => { + memoryState = reducer(memoryState, action); + listeners.forEach((listener) => { + listener(memoryState); + }); +}; + +const toast = (props: ToasterToast) => { + if (!props.duration) { + props.duration = 20000; + } + const id = genId(); + + const update = (_props: ToasterToast) => + dispatch({ + type: 'UPDATE_TOAST', + toast: { ..._props, id }, + }); + const dismiss = () => + dispatch({ type: 'DISMISS_TOAST', toastId: id, duration: props.duration }); + + dispatch({ + type: 'ADD_TOAST', + toast: { + ...props, + id, + open: true, + onOpenChange: (open: boolean) => { + if (!open) dismiss(); + }, + }, + }); + + return { + id: id, + dismiss, + update, + }; +}; + +const useToast = () => { + const [state, setState] = React.useState(memoryState); + + React.useEffect(() => { + listeners.push(setState); + return () => { + const index = listeners.indexOf(setState); + if (index > -1) { + listeners.splice(index, 1); + } + }; + }, [state]); + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }), + }; +}; + +export { toast, useToast }; From d3013719e69b71738f30f2f969e49fd0d90bb857 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 11:45:55 +0700 Subject: [PATCH 04/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20wip=20simple=20to?= =?UTF-8?q?ast=20render?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared/Toast/SimpleToast.theme.ts | 48 +++++++++++++++++++ .../components/shared/Toast/SimpleToast.tsx | 43 +++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts create mode 100644 packages/frontend/src/components/shared/Toast/SimpleToast.tsx diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts b/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts new file mode 100644 index 00000000..0d8ae24b --- /dev/null +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts @@ -0,0 +1,48 @@ +import { VariantProps, tv } from 'tailwind-variants'; + +export const simpleToastTheme = tv( + { + slots: { + wrapper: [ + 'flex', + 'py-2', + 'pl-2', + 'pr-1.5', + 'gap-2', + 'rounded-full', + 'mx-auto', + 'mt-3', + 'w-fit', + 'overflow-hidden', + 'bg-surface-high-contrast', + 'shadow-sm', + ], + icon: ['flex', 'items-center', 'justify-center', 'w-5', 'h-5'], + title: ['text-sm', 'text-elements-on-high-contrast'], + }, + variants: { + variant: { + success: { + icon: ['text-elements-success'], + }, + error: { + icon: ['text-elements-danger'], + }, + warning: { + icon: ['text-elements-warning'], + }, + info: { + icon: ['text-elements-info'], + }, + loading: { + icon: ['text-elements-info'], + }, + }, + }, + }, + { + responsiveVariants: true, + }, +); + +export type SimpleToastTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx new file mode 100644 index 00000000..e134d647 --- /dev/null +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -0,0 +1,43 @@ +import React, { useMemo } from 'react'; +import * as ToastPrimitive from '@radix-ui/react-toast'; +import { ToastProps } from '@radix-ui/react-toast'; +import { simpleToastTheme, type SimpleToastTheme } from './SimpleToast.theme'; +import { CheckIcon, CheckRoundFilledIcon } from 'components/shared/CustomIcon'; +import { cloneIcon } from 'utils/cloneIcon'; + +export interface SimpleToastProps extends ToastProps { + title: string; + variant?: SimpleToastTheme['variant']; +} + +export const SimpleToast = ({ + className, + title, + variant = 'success', + ...props +}: SimpleToastProps) => { + const { + wrapper: wrapperCls, + icon: iconCls, + title: titleCls, + } = simpleToastTheme({ variant }); + + const Icon = useMemo(() => { + if (variant === 'success') return ; + if (variant === 'error') return ; + if (variant === 'warning') return ; + if (variant === 'info') return ; + return ; // variant === 'loading' + }, [variant]); + + return ( + +
+ {cloneIcon(Icon, { className: iconCls() })} + +

{title}

+
+
+
+ ); +}; From 22c581dd332e536e33e4639cfefa59a3d810e7db Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 11:46:17 +0700 Subject: [PATCH 05/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20icons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomIcon/CheckRoundFilledIcon.tsx | 21 +++++++++++++++++++ .../src/components/shared/CustomIcon/index.ts | 1 + 2 files changed, 22 insertions(+) create mode 100644 packages/frontend/src/components/shared/CustomIcon/CheckRoundFilledIcon.tsx diff --git a/packages/frontend/src/components/shared/CustomIcon/CheckRoundFilledIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/CheckRoundFilledIcon.tsx new file mode 100644 index 00000000..145e68e1 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/CheckRoundFilledIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const CheckRoundFilledIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index c1ec6410..5e246238 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -5,3 +5,4 @@ export * from './ChevronGrabberHorizontal'; export * from './ChevronLeft'; export * from './ChevronRight'; export * from './GlobeIcon'; +export * from './CheckRoundFilledIcon'; From fa96b6e73478f6dc2317709a7f9a7cf6b2b22d3d Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 11:46:39 +0700 Subject: [PATCH 06/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20to=20compon?= =?UTF-8?q?ents=20render=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/pages/components/index.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index c1c46f6c..82fa69f3 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -13,8 +13,12 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; +import { useToast } from 'components/shared/Toast/useToast'; +import { Button } from 'components/shared/Button'; const Page = () => { + const { toast } = useToast(); + const [singleDate, setSingleDate] = useState(); const [dateRange, setDateRange] = useState(); @@ -31,6 +35,13 @@ const Page = () => {
+ {/* Toast */} +
+

Toasts

+ +
{/* Button */}

Button

From ef9f4df28aa514743e5adaad7cbb7957fbfc78ca Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:13:10 +0700 Subject: [PATCH 07/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20ghost=20button=20?= =?UTF-8?q?is=20white,=20not=20transparent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Button/Button.theme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/shared/Button/Button.theme.ts b/packages/frontend/src/components/shared/Button/Button.theme.ts index 6c9614c6..194c6550 100644 --- a/packages/frontend/src/components/shared/Button/Button.theme.ts +++ b/packages/frontend/src/components/shared/Button/Button.theme.ts @@ -62,7 +62,7 @@ export const buttonTheme = tv( 'text-elements-on-tertiary', 'border', 'border-border-interactive/10', - 'bg-transparent', + 'bg-controls-tertiary', 'hover:bg-controls-tertiary-hovered', 'hover:border-border-interactive-hovered', 'hover:border-border-interactive-hovered/[0.14]', From 028dd35db1df49f60e5f6dbabda973107c562cff Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:13:27 +0700 Subject: [PATCH 08/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20icons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared/CustomIcon/InfoRoundFilledIcon.tsx | 21 +++++++++++++++++++ .../shared/CustomIcon/LoadingIcon.tsx | 21 +++++++++++++++++++ .../src/components/shared/CustomIcon/index.ts | 2 ++ 3 files changed, 44 insertions(+) create mode 100644 packages/frontend/src/components/shared/CustomIcon/InfoRoundFilledIcon.tsx create mode 100644 packages/frontend/src/components/shared/CustomIcon/LoadingIcon.tsx diff --git a/packages/frontend/src/components/shared/CustomIcon/InfoRoundFilledIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/InfoRoundFilledIcon.tsx new file mode 100644 index 00000000..d7eb24c4 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/InfoRoundFilledIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const InfoRoundFilledIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/LoadingIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/LoadingIcon.tsx new file mode 100644 index 00000000..012fa986 --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/LoadingIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const LoadingIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index 7b84840e..cb5f9f37 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -10,3 +10,5 @@ export * from './SearchIcon'; export * from './CrossIcon'; export * from './GlobeIcon'; export * from './CheckRoundFilledIcon'; +export * from './InfoRoundFilledIcon'; +export * from './LoadingIcon'; From 5836779403f7004ff3be8b6c6a4b25c4a00dac24 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:13:47 +0700 Subject: [PATCH 09/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20export=20buttonBa?= =?UTF-8?q?seProps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Button/Button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/shared/Button/Button.tsx b/packages/frontend/src/components/shared/Button/Button.tsx index af845e0c..6033a440 100644 --- a/packages/frontend/src/components/shared/Button/Button.tsx +++ b/packages/frontend/src/components/shared/Button/Button.tsx @@ -9,7 +9,7 @@ import { cloneIcon } from 'utils/cloneIcon'; /** * Represents the properties of a base button component. */ -interface ButtonBaseProps { +export interface ButtonBaseProps { /** * The optional left icon element for a component. * @type {ReactNode} From cb928c3360303255a1b86f937a415f5ea42a8943 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:16:21 +0700 Subject: [PATCH 10/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20use=20buttonbasep?= =?UTF-8?q?rops,=20duration=202000=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/components/shared/Toast/useToast.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/useToast.tsx b/packages/frontend/src/components/shared/Toast/useToast.tsx index ad088be4..bd0be17e 100644 --- a/packages/frontend/src/components/shared/Toast/useToast.tsx +++ b/packages/frontend/src/components/shared/Toast/useToast.tsx @@ -1,13 +1,15 @@ // Inspired by react-hot-toast library import React from 'react'; import { type ToastProps } from '@radix-ui/react-toast'; +import { SimpleToastProps } from './SimpleToast'; const TOAST_LIMIT = 3; const TOAST_REMOVE_DELAY_DEFAULT = 7000; -type ToasterToast = ToastProps & { - id: string; -}; +type ToasterToast = ToastProps & + SimpleToastProps & { + id: string; + }; const actionTypes = { ADD_TOAST: 'ADD_TOAST', @@ -136,7 +138,7 @@ const dispatch = (action: Action) => { const toast = (props: ToasterToast) => { if (!props.duration) { - props.duration = 20000; + props.duration = 2000; } const id = genId(); From 675112079c5676a4dcce1cde3c1ceaed3f8083d5 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:16:47 +0700 Subject: [PATCH 11/27] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20feat:=20implement=20?= =?UTF-8?q?toast=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shared/Toast/SimpleToast.theme.ts | 10 ++++ .../components/shared/Toast/SimpleToast.tsx | 51 ++++++++++++++++--- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts b/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts index 0d8ae24b..bac805ce 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.theme.ts @@ -5,6 +5,7 @@ export const simpleToastTheme = tv( slots: { wrapper: [ 'flex', + 'items-center', 'py-2', 'pl-2', 'pr-1.5', @@ -18,6 +19,15 @@ export const simpleToastTheme = tv( 'shadow-sm', ], icon: ['flex', 'items-center', 'justify-center', 'w-5', 'h-5'], + closeIcon: [ + 'cursor-pointer', + 'flex', + 'items-center', + 'justify-center', + 'w-6', + 'h-6', + 'text-elements-on-high-contrast', + ], title: ['text-sm', 'text-elements-on-high-contrast'], }, variants: { diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx index e134d647..b3bafc21 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -2,41 +2,80 @@ import React, { useMemo } from 'react'; import * as ToastPrimitive from '@radix-ui/react-toast'; import { ToastProps } from '@radix-ui/react-toast'; import { simpleToastTheme, type SimpleToastTheme } from './SimpleToast.theme'; -import { CheckIcon, CheckRoundFilledIcon } from 'components/shared/CustomIcon'; +import { + LoadingIcon, + CheckRoundFilledIcon, + CrossIcon, + InfoRoundFilledIcon, + WarningIcon, +} from 'components/shared/CustomIcon'; +import { Button, ButtonBaseProps, ButtonTheme } from 'components/shared/Button'; import { cloneIcon } from 'utils/cloneIcon'; +import { cn } from 'utils/classnames'; +interface CtaProps extends ButtonBaseProps, ButtonTheme { + buttonLabel: string; +} export interface SimpleToastProps extends ToastProps { title: string; variant?: SimpleToastTheme['variant']; + cta?: CtaProps[]; + onDismiss: (id?: string) => void; } export const SimpleToast = ({ className, title, variant = 'success', + cta = [], + onDismiss, ...props }: SimpleToastProps) => { + const hasCta = cta.length > 0; const { wrapper: wrapperCls, icon: iconCls, + closeIcon: closeIconCls, title: titleCls, } = simpleToastTheme({ variant }); const Icon = useMemo(() => { if (variant === 'success') return ; - if (variant === 'error') return ; - if (variant === 'warning') return ; - if (variant === 'info') return ; - return ; // variant === 'loading' + if (variant === 'error') return ; + if (variant === 'warning') return ; + if (variant === 'info') return ; + return ; // variant === 'loading' }, [variant]); + const renderCta = useMemo( + () => + hasCta ? ( +
+ {cta.map(({ buttonLabel, ...props }, index) => ( + + ))} +
+ ) : null, + [], + ); + + const renderCloseButton = () => ( +
onDismiss(props.id)} className={closeIconCls()}> + +
+ ); + return ( -
+
{cloneIcon(Icon, { className: iconCls() })}

{title}

+ {renderCta} + {renderCloseButton()}
); From bb7a88f7e97e7e71e82f94fee1d73c264d690792 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:17:02 +0700 Subject: [PATCH 12/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20toast=20ren?= =?UTF-8?q?der=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/pages/components/index.tsx | 13 ++-- .../src/pages/components/renders/toast.tsx | 66 +++++++++++++++++++ 2 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 packages/frontend/src/pages/components/renders/toast.tsx diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 5866cdde..89403520 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -17,17 +17,14 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; -import { useToast } from 'components/shared/Toast/useToast'; -import { Button } from 'components/shared/Button'; import { renderInlineNotificationWithDescriptions, renderInlineNotifications, } from './renders/inlineNotifications'; import { renderInputs } from './renders/input'; +import { renderToast, renderToastsWithCta } from './renders/toast'; const Page = () => { - const { toast } = useToast(); - const [singleDate, setSingleDate] = useState(); const [dateRange, setDateRange] = useState(); @@ -47,10 +44,12 @@ const Page = () => { {/* Toast */}

Toasts

- + {renderToastsWithCta()} + {renderToast()}
+ +
+ {/* Button */}

Input

diff --git a/packages/frontend/src/pages/components/renders/toast.tsx b/packages/frontend/src/pages/components/renders/toast.tsx new file mode 100644 index 00000000..9b066e8c --- /dev/null +++ b/packages/frontend/src/pages/components/renders/toast.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { Button } from 'components/shared/Button'; +import { useToast } from 'components/shared/Toast/useToast'; + +export const renderToastsWithCta = () => { + const { toast, dismiss } = useToast(); + + return ( +
+ {(['success', 'error', 'warning', 'info', 'loading'] as const).map( + (variant, index) => ( + + ), + )} +
+ ); +}; + +export const renderToast = () => { + const { toast, dismiss } = useToast(); + + return ( +
+ {(['success', 'error', 'warning', 'info', 'loading'] as const).map( + (variant, index) => ( + + ), + )} +
+ ); +}; From c8a153ad279271583b3f6288d72d11bd1f219098 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:20:26 +0700 Subject: [PATCH 13/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20usememo=20depende?= =?UTF-8?q?ncies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/shared/Toast/SimpleToast.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx index b3bafc21..405cff0d 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -58,13 +58,16 @@ export const SimpleToast = ({ ))}
) : null, - [], + [cta], ); - const renderCloseButton = () => ( -
onDismiss(props.id)} className={closeIconCls()}> - -
+ const renderCloseButton = useMemo( + () => ( +
onDismiss(props.id)} className={closeIconCls()}> + +
+ ), + [], ); return ( @@ -75,7 +78,7 @@ export const SimpleToast = ({

{title}

{renderCta} - {renderCloseButton()} + {renderCloseButton}
); From 1cd60d84dd6728ac81d555bd2e3968a005d8bee7 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:22:29 +0700 Subject: [PATCH 14/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20use=20framer-moti?= =?UTF-8?q?on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/shared/Toast/SimpleToast.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx index 405cff0d..001ac32b 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from 'react'; import * as ToastPrimitive from '@radix-ui/react-toast'; import { ToastProps } from '@radix-ui/react-toast'; +import { motion } from 'framer-motion'; import { simpleToastTheme, type SimpleToastTheme } from './SimpleToast.theme'; import { LoadingIcon, @@ -11,7 +12,6 @@ import { } from 'components/shared/CustomIcon'; import { Button, ButtonBaseProps, ButtonTheme } from 'components/shared/Button'; import { cloneIcon } from 'utils/cloneIcon'; -import { cn } from 'utils/classnames'; interface CtaProps extends ButtonBaseProps, ButtonTheme { buttonLabel: string; @@ -72,14 +72,22 @@ export const SimpleToast = ({ return ( -
+ {cloneIcon(Icon, { className: iconCls() })}

{title}

{renderCta} {renderCloseButton} -
+
); }; From fffc370d40636adae9700a001f64ea1cb31fab9f Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:58:57 +0700 Subject: [PATCH 15/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20rework=20usememo?= =?UTF-8?q?=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/components/shared/Toast/SimpleToast.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx index 001ac32b..d780640f 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -47,9 +47,9 @@ export const SimpleToast = ({ return ; // variant === 'loading' }, [variant]); - const renderCta = useMemo( - () => - hasCta ? ( + const renderCta = useMemo(() => { + if (!hasCta) return null; + return (
{cta.map(({ buttonLabel, ...props }, index) => ( ))}
- ) : null, - [cta], ); + }, [cta]); const renderCloseButton = useMemo( () => ( From 5be29a9745aa08310edc3ebfcd2dc4112367d5c7 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 14:59:12 +0700 Subject: [PATCH 16/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20add=20id=20to=20a?= =?UTF-8?q?llow=20singular=20toast=20deletion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/shared/Toast/SimpleToast.tsx | 24 ++++++++++--------- .../src/components/shared/Toast/Toaster.tsx | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx index d780640f..c15f2d45 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -17,13 +17,15 @@ interface CtaProps extends ButtonBaseProps, ButtonTheme { buttonLabel: string; } export interface SimpleToastProps extends ToastProps { + id: string; title: string; variant?: SimpleToastTheme['variant']; cta?: CtaProps[]; - onDismiss: (id?: string) => void; + onDismiss: (toastId: string) => void; } export const SimpleToast = ({ + id, className, title, variant = 'success', @@ -50,23 +52,23 @@ export const SimpleToast = ({ const renderCta = useMemo(() => { if (!hasCta) return null; return ( -
- {cta.map(({ buttonLabel, ...props }, index) => ( - - ))} -
- ); +
+ {cta.map(({ buttonLabel, ...props }, index) => ( + + ))} +
+ ); }, [cta]); const renderCloseButton = useMemo( () => ( -
onDismiss(props.id)} className={closeIconCls()}> +
onDismiss(id)} className={closeIconCls()}>
), - [], + [id], ); return ( diff --git a/packages/frontend/src/components/shared/Toast/Toaster.tsx b/packages/frontend/src/components/shared/Toast/Toaster.tsx index 85222167..66556956 100644 --- a/packages/frontend/src/components/shared/Toast/Toaster.tsx +++ b/packages/frontend/src/components/shared/Toast/Toaster.tsx @@ -11,7 +11,7 @@ export const Toaster = ({}: ToasterProps) => { const renderToasts = useMemo( () => toasts.map(({ id, ...props }) => ( - + )), [toasts], ); From 4d646e1ac08b4e5ab4e1f0fc8c7ef1886b09aaca Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 15:03:21 +0700 Subject: [PATCH 17/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20extend=20to=20but?= =?UTF-8?q?tonOrLinkProps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/src/components/shared/Toast/SimpleToast.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx index c15f2d45..7c10f855 100644 --- a/packages/frontend/src/components/shared/Toast/SimpleToast.tsx +++ b/packages/frontend/src/components/shared/Toast/SimpleToast.tsx @@ -10,12 +10,12 @@ import { InfoRoundFilledIcon, WarningIcon, } from 'components/shared/CustomIcon'; -import { Button, ButtonBaseProps, ButtonTheme } from 'components/shared/Button'; +import { Button, type ButtonOrLinkProps } from 'components/shared/Button'; import { cloneIcon } from 'utils/cloneIcon'; -interface CtaProps extends ButtonBaseProps, ButtonTheme { +type CtaProps = ButtonOrLinkProps & { buttonLabel: string; -} +}; export interface SimpleToastProps extends ToastProps { id: string; title: string; From ba87c6f22a037b43c35815e3092de9bfd3d30bf6 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 16:11:37 +0700 Subject: [PATCH 18/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20replace=20react-h?= =?UTF-8?q?ot-toast=20with=20custom=20toast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx index da29f575..cc0281f1 100644 --- a/packages/frontend/src/index.tsx +++ b/packages/frontend/src/index.tsx @@ -1,7 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import assert from 'assert'; -import { Toaster } from 'react-hot-toast'; import { GQLClient } from 'gql-client'; import { ThemeProvider } from '@material-tailwind/react'; @@ -11,6 +10,7 @@ import '@fontsource/inter'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { GQLClientProvider } from './context/GQLClientContext'; +import { Toaster } from 'components/shared/Toast/Toaster'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement, @@ -26,7 +26,7 @@ root.render( - + , From 200ea3ca7bd6714cb1e7a223def942cc979d312a Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 16:14:27 +0700 Subject: [PATCH 19/27] =?UTF-8?q?=F0=9F=90=9B=20fix:=20aria-hidden=20namin?= =?UTF-8?q?g=20on=20input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Input/Input.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/components/shared/Input/Input.tsx b/packages/frontend/src/components/shared/Input/Input.tsx index f5bbdca5..d2d5459a 100644 --- a/packages/frontend/src/components/shared/Input/Input.tsx +++ b/packages/frontend/src/components/shared/Input/Input.tsx @@ -55,7 +55,7 @@ export const Input = ({ const renderLeftIcon = useMemo(() => { return (
- {cloneIcon(leftIcon, { className: iconCls(), ariaHidden: true })} + {cloneIcon(leftIcon, { className: iconCls(), 'aria-hidden': true })}
); }, [cloneIcon, iconCls, iconContainerCls, leftIcon]); @@ -63,7 +63,7 @@ export const Input = ({ const renderRightIcon = useMemo(() => { return (
- {cloneIcon(rightIcon, { className: iconCls(), ariaHidden: true })} + {cloneIcon(rightIcon, { className: iconCls(), 'aria-hidden': true })}
); }, [cloneIcon, iconCls, iconContainerCls, rightIcon]); @@ -73,7 +73,7 @@ export const Input = ({
{state && cloneIcon(, { - ariaHidden: true, + 'aria-hidden': true, })}

{helperText}

From 32918b793016d3835b0243c5735c28180f6c7308 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 17:07:15 +0700 Subject: [PATCH 20/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20index=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Toast/index.ts | 2 ++ packages/frontend/src/index.tsx | 3 +-- packages/frontend/src/pages/components/renders/toast.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 packages/frontend/src/components/shared/Toast/index.ts diff --git a/packages/frontend/src/components/shared/Toast/index.ts b/packages/frontend/src/components/shared/Toast/index.ts new file mode 100644 index 00000000..6404e342 --- /dev/null +++ b/packages/frontend/src/components/shared/Toast/index.ts @@ -0,0 +1,2 @@ +export * from './Toaster'; +export * from './useToast'; diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx index cc0281f1..bdfe6f6d 100644 --- a/packages/frontend/src/index.tsx +++ b/packages/frontend/src/index.tsx @@ -9,8 +9,7 @@ import './index.css'; import '@fontsource/inter'; import App from './App'; import reportWebVitals from './reportWebVitals'; -import { GQLClientProvider } from './context/GQLClientContext'; -import { Toaster } from 'components/shared/Toast/Toaster'; +import { Toaster } from 'components/shared/Toast'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement, diff --git a/packages/frontend/src/pages/components/renders/toast.tsx b/packages/frontend/src/pages/components/renders/toast.tsx index 9b066e8c..fb19e33a 100644 --- a/packages/frontend/src/pages/components/renders/toast.tsx +++ b/packages/frontend/src/pages/components/renders/toast.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Button } from 'components/shared/Button'; -import { useToast } from 'components/shared/Toast/useToast'; +import { useToast } from 'components/shared/Toast'; export const renderToastsWithCta = () => { const { toast, dismiss } = useToast(); From 8952393b31468520a2069e880eb24052da97573b Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 17:08:05 +0700 Subject: [PATCH 21/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20remove=20not=20wo?= =?UTF-8?q?rking=20swipe=20down=20gesture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/shared/Toast/ToastProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/components/shared/Toast/ToastProvider.tsx b/packages/frontend/src/components/shared/Toast/ToastProvider.tsx index 57ecf2b6..d45d6e89 100644 --- a/packages/frontend/src/components/shared/Toast/ToastProvider.tsx +++ b/packages/frontend/src/components/shared/Toast/ToastProvider.tsx @@ -7,7 +7,7 @@ import { export const ToastProvider = ({ children, ...props }: ToastProviderProps) => { return ( - + {children} From 0e7343da6f016db569ec99bc54d6a46efe9503f1 Mon Sep 17 00:00:00 2001 From: Andre H Date: Thu, 22 Feb 2024 17:11:03 +0700 Subject: [PATCH 22/27] =?UTF-8?q?=F0=9F=94=A7=20chore:=20gqlclientprovider?= =?UTF-8?q?=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx index bdfe6f6d..d0c0d9b7 100644 --- a/packages/frontend/src/index.tsx +++ b/packages/frontend/src/index.tsx @@ -9,6 +9,7 @@ import './index.css'; import '@fontsource/inter'; import App from './App'; import reportWebVitals from './reportWebVitals'; +import { GQLClientProvider } from './context/GQLClientContext'; import { Toaster } from 'components/shared/Toast'; const root = ReactDOM.createRoot( From 30bbe4d766de1741000a1573fc28b0355a9eb36e Mon Sep 17 00:00:00 2001 From: Zachery Date: Thu, 22 Feb 2024 18:25:04 +0800 Subject: [PATCH 23/27] [T-4870] Tooltip component (#96) * fix: button forwardRef * feat: tooltip component --- packages/frontend/package.json | 1 + .../src/components/shared/Button/Button.tsx | 20 ++- .../shared/Tooltip/Tooltip.theme.ts | 14 ++ .../src/components/shared/Tooltip/Tooltip.tsx | 47 +++++++ .../components/shared/Tooltip/TooltipBase.tsx | 38 ++++++ .../Tooltip/TooltipContent/TooltipContent.tsx | 46 +++++++ .../shared/Tooltip/TooltipContent/index.ts | 1 + .../Tooltip/TooltipTrigger/TooltipTrigger.tsx | 12 ++ .../shared/Tooltip/TooltipTrigger/index.ts | 1 + .../src/components/shared/Tooltip/index.ts | 2 + .../frontend/src/pages/components/index.tsx | 11 +- .../src/pages/components/renders/tooltip.tsx | 30 +++++ packages/frontend/tailwind.config.js | 9 +- yarn.lock | 122 ++++++++++++++++++ 14 files changed, 344 insertions(+), 10 deletions(-) create mode 100644 packages/frontend/src/components/shared/Tooltip/Tooltip.theme.ts create mode 100644 packages/frontend/src/components/shared/Tooltip/Tooltip.tsx create mode 100644 packages/frontend/src/components/shared/Tooltip/TooltipBase.tsx create mode 100644 packages/frontend/src/components/shared/Tooltip/TooltipContent/TooltipContent.tsx create mode 100644 packages/frontend/src/components/shared/Tooltip/TooltipContent/index.ts create mode 100644 packages/frontend/src/components/shared/Tooltip/TooltipTrigger/TooltipTrigger.tsx create mode 100644 packages/frontend/src/components/shared/Tooltip/TooltipTrigger/index.ts create mode 100644 packages/frontend/src/components/shared/Tooltip/index.ts create mode 100644 packages/frontend/src/pages/components/renders/tooltip.tsx diff --git a/packages/frontend/package.json b/packages/frontend/package.json index f31fa586..8daa876c 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -8,6 +8,7 @@ "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4", + "@radix-ui/react-tooltip": "^1.0.7", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/packages/frontend/src/components/shared/Button/Button.tsx b/packages/frontend/src/components/shared/Button/Button.tsx index af845e0c..968cca72 100644 --- a/packages/frontend/src/components/shared/Button/Button.tsx +++ b/packages/frontend/src/components/shared/Button/Button.tsx @@ -98,19 +98,29 @@ const Button = forwardRef< href, ...baseLinkProps, }; - return {_children}; + return ( + // @ts-expect-error - ref + + {_children} + + ); } // Internal link return ( - + // @ts-expect-error - ref + {_children} ); } else { const { ...buttonProps } = _props; - // @ts-expect-error - as prop is not a valid prop for button elements - return ; + return ( + // @ts-expect-error - as prop is not a valid prop for button elements + + ); } }, [], @@ -161,8 +171,6 @@ const Button = forwardRef< return ( {cloneIcon(leftIcon, { ...iconSize })} diff --git a/packages/frontend/src/components/shared/Tooltip/Tooltip.theme.ts b/packages/frontend/src/components/shared/Tooltip/Tooltip.theme.ts new file mode 100644 index 00000000..c4c1c5e0 --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/Tooltip.theme.ts @@ -0,0 +1,14 @@ +import { tv } from 'tailwind-variants'; + +export const tooltipTheme = tv({ + slots: { + content: [ + 'z-tooltip', + 'rounded-md', + 'bg-surface-high-contrast', + 'p-2', + 'text-elements-on-high-contrast', + ], + arrow: ['fill-surface-high-contrast'], + }, +}); diff --git a/packages/frontend/src/components/shared/Tooltip/Tooltip.tsx b/packages/frontend/src/components/shared/Tooltip/Tooltip.tsx new file mode 100644 index 00000000..75d73c78 --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/Tooltip.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import type { + TooltipContentProps, + TooltipTriggerProps, +} from '@radix-ui/react-tooltip'; +import { ReactNode, useState } from 'react'; + +import { TooltipBase, type TooltipBaseProps } from './TooltipBase'; + +export interface TooltipProps extends TooltipBaseProps { + triggerProps?: TooltipTriggerProps; + contentProps?: TooltipContentProps; + content?: ReactNode; +} + +// https://github.com/radix-ui/primitives/issues/955#issuecomment-1798201143 +// Wrap on top of Tooltip base to make tooltip open on mobile via click +export const Tooltip = ({ + children, + triggerProps, + contentProps, + content, + ...props +}: TooltipProps) => { + const [isTooltipVisible, setIsTooltipVisible] = useState(false); + + return ( + + setIsTooltipVisible(false)} + onClick={() => setIsTooltipVisible((prevOpen) => !prevOpen)} + onFocus={() => setTimeout(() => setIsTooltipVisible(true), 0)} + {...triggerProps} + > + {triggerProps?.children ?? children} + + + {content ?? contentProps?.children ?? 'Coming soon'} + + + ); +}; diff --git a/packages/frontend/src/components/shared/Tooltip/TooltipBase.tsx b/packages/frontend/src/components/shared/Tooltip/TooltipBase.tsx new file mode 100644 index 00000000..273a9cbe --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/TooltipBase.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import { + Provider, + TooltipProps as RadixTooltipProps, + Root, + type TooltipProviderProps, +} from '@radix-ui/react-tooltip'; +import { useState, type PropsWithChildren } from 'react'; + +import { TooltipContent } from './TooltipContent'; +import { TooltipTrigger } from './TooltipTrigger'; + +export interface TooltipBaseProps extends RadixTooltipProps { + providerProps?: TooltipProviderProps; +} + +export const TooltipBase = ({ + children, + providerProps, + ...props +}: PropsWithChildren) => { + const [isTooltipVisible, setIsTooltipVisible] = useState(false); + + return ( + + + {children} + + + ); +}; + +TooltipBase.Trigger = TooltipTrigger; +TooltipBase.Content = TooltipContent; diff --git a/packages/frontend/src/components/shared/Tooltip/TooltipContent/TooltipContent.tsx b/packages/frontend/src/components/shared/Tooltip/TooltipContent/TooltipContent.tsx new file mode 100644 index 00000000..57678206 --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/TooltipContent/TooltipContent.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { + Arrow, + Content, + Portal, + type TooltipArrowProps, + type TooltipContentProps, +} from '@radix-ui/react-tooltip'; + +import { tooltipTheme } from '../Tooltip.theme'; + +export interface ContentProps extends TooltipContentProps { + hasArrow?: boolean; + arrowProps?: TooltipArrowProps; +} + +export const TooltipContent = ({ + children, + arrowProps, + className, + hasArrow = true, + ...props +}: ContentProps) => { + const { content, arrow } = tooltipTheme(); + return ( + + + {hasArrow && ( + + )} + {children} + + + ); +}; diff --git a/packages/frontend/src/components/shared/Tooltip/TooltipContent/index.ts b/packages/frontend/src/components/shared/Tooltip/TooltipContent/index.ts new file mode 100644 index 00000000..1675248c --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/TooltipContent/index.ts @@ -0,0 +1 @@ +export * from './TooltipContent'; diff --git a/packages/frontend/src/components/shared/Tooltip/TooltipTrigger/TooltipTrigger.tsx b/packages/frontend/src/components/shared/Tooltip/TooltipTrigger/TooltipTrigger.tsx new file mode 100644 index 00000000..c27993ed --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/TooltipTrigger/TooltipTrigger.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Trigger, type TooltipTriggerProps } from '@radix-ui/react-tooltip'; + +export type TriggerProps = TooltipTriggerProps; + +export const TooltipTrigger = ({ children, ...props }: TriggerProps) => { + return ( + + {children} + + ); +}; diff --git a/packages/frontend/src/components/shared/Tooltip/TooltipTrigger/index.ts b/packages/frontend/src/components/shared/Tooltip/TooltipTrigger/index.ts new file mode 100644 index 00000000..e73d66c6 --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/TooltipTrigger/index.ts @@ -0,0 +1 @@ +export * from './TooltipTrigger'; diff --git a/packages/frontend/src/components/shared/Tooltip/index.ts b/packages/frontend/src/components/shared/Tooltip/index.ts new file mode 100644 index 00000000..a51f2a31 --- /dev/null +++ b/packages/frontend/src/components/shared/Tooltip/index.ts @@ -0,0 +1,2 @@ +export * from './Tooltip'; +export * from './TooltipBase'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 67e68e0b..bfe02385 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -23,6 +23,7 @@ import { renderInlineNotifications, } from './renders/inlineNotifications'; import { renderInputs } from './renders/input'; +import { renderTooltips } from './renders/tooltip'; const Page = () => { const [singleDate, setSingleDate] = useState(); @@ -41,13 +42,21 @@ const Page = () => {
- {/* Button */}
+

Tooltip

+
+ {renderTooltips()} +
+ +
+ + {/* Input */}

Input

{renderInputs()}
+ {/* Button */}

Button

{renderButtons()} diff --git a/packages/frontend/src/pages/components/renders/tooltip.tsx b/packages/frontend/src/pages/components/renders/tooltip.tsx new file mode 100644 index 00000000..7114f4db --- /dev/null +++ b/packages/frontend/src/pages/components/renders/tooltip.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Button } from 'components/shared/Button'; +import { Tooltip } from 'components/shared/Tooltip'; +import { ContentProps } from 'components/shared/Tooltip/TooltipContent'; + +const alignments: ContentProps['align'][] = ['start', 'center', 'end']; +const sides: ContentProps['side'][] = ['left', 'top', 'bottom', 'right']; + +export const renderTooltips = () => { + const tooltips = sides.map((side) => { + return alignments.map((align) => { + return ( + + + + ); + }); + }); + return tooltips; +}; diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 4bcb0b01..9a814026 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -8,10 +8,13 @@ export default withMT({ '../../node_modules/@material-tailwind/react/theme/components/**/*.{js,ts,jsx,tsx}', ], theme: { - fontFamily: { - sans: ['Inter', 'sans-serif'], - }, extend: { + zIndex: { + tooltip: '52', + }, + fontFamily: { + sans: ['Inter', 'sans-serif'], + }, fontSize: { '2xs': '0.625rem', '3xs': '0.5rem', diff --git a/yarn.lock b/yarn.lock index c8881430..f4d59093 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2032,6 +2032,13 @@ link-module-alias "^1.2.0" shx "^0.3.4" +"@floating-ui/core@^1.0.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" + integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== + dependencies: + "@floating-ui/utils" "^0.2.1" + "@floating-ui/core@^1.4.2": version "1.5.2" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.2.tgz#53a0f7a98c550e63134d504f26804f6b83dbc071" @@ -2047,6 +2054,14 @@ "@floating-ui/core" "^1.4.2" "@floating-ui/utils" "^0.1.3" +"@floating-ui/dom@^1.6.1": + version "1.6.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.3.tgz#954e46c1dd3ad48e49db9ada7218b0985cee75ef" + integrity sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + "@floating-ui/react-dom@^1.2.2": version "1.3.0" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.3.0.tgz#4d35d416eb19811c2b0e9271100a6aa18c1579b3" @@ -2054,6 +2069,13 @@ dependencies: "@floating-ui/dom" "^1.2.1" +"@floating-ui/react-dom@^2.0.0": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" + integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== + dependencies: + "@floating-ui/dom" "^1.6.1" + "@floating-ui/react@0.19.0": version "0.19.0" resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.19.0.tgz#d8e19a3fcfaa0684d5ec3f335232b4e0ac0c87e1" @@ -2068,6 +2090,11 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9" integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== +"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" + integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== + "@fontsource/inter@^5.0.16": version "5.0.16" resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.0.16.tgz#b858508cdb56dcbbf3166903122851e2fbd16b50" @@ -3284,6 +3311,14 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-arrow@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" + integrity sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-avatar@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz#de9a5349d9e3de7bbe990334c4d2011acbbb9623" @@ -3342,6 +3377,18 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-dismissable-layer@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4" + integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-escape-keydown" "1.0.3" + "@radix-ui/react-id@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" @@ -3350,6 +3397,31 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-popper@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42" + integrity sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w== + dependencies: + "@babel/runtime" "^7.13.10" + "@floating-ui/react-dom" "^2.0.0" + "@radix-ui/react-arrow" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-use-rect" "1.0.1" + "@radix-ui/react-use-size" "1.0.1" + "@radix-ui/rect" "1.0.1" + +"@radix-ui/react-portal@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15" + integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-presence@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba" @@ -3406,6 +3478,25 @@ "@radix-ui/react-roving-focus" "1.0.4" "@radix-ui/react-use-controllable-state" "1.0.1" +"@radix-ui/react-tooltip@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz#8f55070f852e7e7450cc1d9210b793d2e5a7686e" + integrity sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-popper" "1.1.3" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-visually-hidden" "1.0.3" + "@radix-ui/react-use-callback-ref@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a" @@ -3421,6 +3512,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-use-escape-keydown@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" + integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" @@ -3435,6 +3534,14 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-use-rect@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz#fde50b3bb9fd08f4a1cd204572e5943c244fcec2" + integrity sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/rect" "1.0.1" + "@radix-ui/react-use-size@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz#1c5f5fea940a7d7ade77694bb98116fb49f870b2" @@ -3443,6 +3550,21 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-visually-hidden@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz#51aed9dd0fe5abcad7dee2a234ad36106a6984ac" + integrity sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + +"@radix-ui/rect@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.1.tgz#bf8e7d947671996da2e30f4904ece343bc4a883f" + integrity sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@remix-run/router@1.13.1": version "1.13.1" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.13.1.tgz#07e2a8006f23a3bc898b3f317e0a58cc8076b86e" From d2ca4df35a27f2cb424bef31923808a3eae277e8 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Thu, 22 Feb 2024 17:30:33 +0700 Subject: [PATCH 24/27] [T-4864: feat] Radio component (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🎨 style: make the cursor of the tab trigger wrapper to default * ⚡️ feat: create radio component * 📝 docs: add radio component to the example page * 🔧 chore: install `@radix-ui/react-radio-group` --- packages/frontend/package.json | 1 + .../components/shared/Radio/Radio.theme.ts | 54 ++++++++++++++ .../src/components/shared/Radio/Radio.tsx | 63 ++++++++++++++++ .../src/components/shared/Radio/RadioItem.tsx | 74 +++++++++++++++++++ .../src/components/shared/Radio/index.ts | 2 + .../src/components/shared/Tabs/Tabs.theme.ts | 2 + .../frontend/src/pages/components/index.tsx | 24 ++++++ .../src/pages/components/renders/radio.ts | 16 ++++ yarn.lock | 17 +++++ 9 files changed, 253 insertions(+) create mode 100644 packages/frontend/src/components/shared/Radio/Radio.theme.ts create mode 100644 packages/frontend/src/components/shared/Radio/Radio.tsx create mode 100644 packages/frontend/src/components/shared/Radio/RadioItem.tsx create mode 100644 packages/frontend/src/components/shared/Radio/index.ts create mode 100644 packages/frontend/src/pages/components/renders/radio.ts diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 8daa876c..2e040901 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -7,6 +7,7 @@ "@material-tailwind/react": "^2.1.7", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", "@testing-library/jest-dom": "^5.17.0", diff --git a/packages/frontend/src/components/shared/Radio/Radio.theme.ts b/packages/frontend/src/components/shared/Radio/Radio.theme.ts new file mode 100644 index 00000000..0b84601e --- /dev/null +++ b/packages/frontend/src/components/shared/Radio/Radio.theme.ts @@ -0,0 +1,54 @@ +import { VariantProps, tv } from 'tailwind-variants'; + +export const radioTheme = tv({ + slots: { + root: ['flex', 'gap-3', 'flex-wrap'], + wrapper: ['flex', 'items-center', 'gap-2', 'group'], + label: ['text-sm', 'tracking-[-0.006em]', 'text-elements-high-em'], + radio: [ + 'w-5', + 'h-5', + 'rounded-full', + 'border', + 'group', + 'border-border-interactive/10', + 'shadow-button', + 'group-hover:border-border-interactive/[0.14]', + 'focus-ring', + // Checked + 'data-[state=checked]:bg-controls-primary', + 'data-[state=checked]:group-hover:bg-controls-primary-hovered', + ], + indicator: [ + 'flex', + 'items-center', + 'justify-center', + 'relative', + 'w-full', + 'h-full', + 'after:content-[""]', + 'after:block', + 'after:w-2.5', + 'after:h-2.5', + 'after:rounded-full', + 'after:bg-transparent', + 'after:group-hover:bg-controls-disabled', + 'after:group-focus-visible:bg-controls-disabled', + // Checked + 'after:data-[state=checked]:bg-elements-on-primary', + 'after:data-[state=checked]:group-hover:bg-elements-on-primary', + 'after:data-[state=checked]:group-focus-visible:bg-elements-on-primary', + ], + }, + variants: { + orientation: { + vertical: { root: ['flex-col'] }, + horizontal: { root: ['flex-row'] }, + }, + }, + defaultVariants: { + orientation: 'vertical', + }, +}); + +export type RadioTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/Radio/Radio.tsx b/packages/frontend/src/components/shared/Radio/Radio.tsx new file mode 100644 index 00000000..96542493 --- /dev/null +++ b/packages/frontend/src/components/shared/Radio/Radio.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { + Root as RadixRoot, + RadioGroupProps, +} from '@radix-ui/react-radio-group'; +import { RadioTheme, radioTheme } from './Radio.theme'; +import { RadioItem, RadioItemProps } from './RadioItem'; + +export interface RadioOption extends RadioItemProps { + /** + * The label of the radio option. + */ + label: string; + /** + * The value of the radio option. + */ + value: string; +} + +export interface RadioProps extends RadioGroupProps, RadioTheme { + /** + * The options of the radio. + * @default [] + * @example + * ```tsx + * const options = [ + * { + * label: 'Label 1', + * value: '1', + * }, + * { + * label: 'Label 2', + * value: '2', + * }, + * { + * label: 'Label 3', + * value: '3', + * }, + * ]; + * ``` + */ + options: RadioOption[]; +} + +/** + * The Radio component is used to select one option from a list of options. + */ +export const Radio = ({ + className, + options, + orientation, + ...props +}: RadioProps) => { + const { root } = radioTheme({ orientation }); + + return ( + + {options.map((option) => ( + + ))} + + ); +}; diff --git a/packages/frontend/src/components/shared/Radio/RadioItem.tsx b/packages/frontend/src/components/shared/Radio/RadioItem.tsx new file mode 100644 index 00000000..177af9db --- /dev/null +++ b/packages/frontend/src/components/shared/Radio/RadioItem.tsx @@ -0,0 +1,74 @@ +import React, { ComponentPropsWithoutRef } from 'react'; +import { + Item as RadixRadio, + Indicator as RadixIndicator, + RadioGroupItemProps, + RadioGroupIndicatorProps, +} from '@radix-ui/react-radio-group'; +import { radioTheme } from './Radio.theme'; + +export interface RadioItemProps extends RadioGroupItemProps { + /** + * The wrapper props of the radio item. + * You can use this prop to customize the wrapper props. + */ + wrapperProps?: ComponentPropsWithoutRef<'div'>; + /** + * The label props of the radio item. + * You can use this prop to customize the label props. + */ + labelProps?: ComponentPropsWithoutRef<'label'>; + /** + * The indicator props of the radio item. + * You can use this prop to customize the indicator props. + */ + indicatorProps?: RadioGroupIndicatorProps; + /** + * The id of the radio item. + */ + id?: string; + /** + * The label of the radio item. + */ + label?: string; +} + +/** + * The RadioItem component is used to render a radio item. + */ +export const RadioItem = ({ + className, + wrapperProps, + labelProps, + indicatorProps, + label, + id, + ...props +}: RadioItemProps) => { + const { wrapper, label: labelClass, radio, indicator } = radioTheme(); + + // Generate a unique id for the radio item from the label if the id is not provided + const kebabCaseLabel = label?.toLowerCase().replace(/\s+/g, '-'); + const componentId = id ?? kebabCaseLabel; + + return ( +
+ + + + {label && ( + + )} +
+ ); +}; diff --git a/packages/frontend/src/components/shared/Radio/index.ts b/packages/frontend/src/components/shared/Radio/index.ts new file mode 100644 index 00000000..6d49f1a8 --- /dev/null +++ b/packages/frontend/src/components/shared/Radio/index.ts @@ -0,0 +1,2 @@ +export * from './Radio'; +export * from './RadioItem'; diff --git a/packages/frontend/src/components/shared/Tabs/Tabs.theme.ts b/packages/frontend/src/components/shared/Tabs/Tabs.theme.ts index 4667fe83..7c39b077 100644 --- a/packages/frontend/src/components/shared/Tabs/Tabs.theme.ts +++ b/packages/frontend/src/components/shared/Tabs/Tabs.theme.ts @@ -9,6 +9,8 @@ export const tabsTheme = tv({ // Horizontal – default 'px-1', 'pb-5', + 'cursor-default', + 'select-none', 'text-elements-low-em', 'border-b-2', 'border-transparent', diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index bfe02385..7e7f84db 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -18,6 +18,8 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; +import { RADIO_OPTIONS } from './renders/radio'; +import { Radio } from 'components/shared/Radio'; import { renderInlineNotificationWithDescriptions, renderInlineNotifications, @@ -28,6 +30,7 @@ import { renderTooltips } from './renders/tooltip'; const Page = () => { const [singleDate, setSingleDate] = useState(); const [dateRange, setDateRange] = useState(); + const [selectedRadio, setSelectedRadio] = useState(''); return (
@@ -160,6 +163,27 @@ const Page = () => {
+ {/* Radio */} +
+

Radio

+
+ + +
+
+ +
+ {/* Inline notification */}

Inline Notification

diff --git a/packages/frontend/src/pages/components/renders/radio.ts b/packages/frontend/src/pages/components/renders/radio.ts new file mode 100644 index 00000000..ff262f66 --- /dev/null +++ b/packages/frontend/src/pages/components/renders/radio.ts @@ -0,0 +1,16 @@ +import { RadioOption } from 'components/shared/Radio'; + +export const RADIO_OPTIONS: RadioOption[] = [ + { + label: 'Label 1', + value: '1', + }, + { + label: 'Label 2', + value: '2', + }, + { + label: 'Label 3', + value: '3', + }, +]; diff --git a/yarn.lock b/yarn.lock index f4d59093..b789155b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3439,6 +3439,23 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-slot" "1.0.2" +"@radix-ui/react-radio-group@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-1.1.3.tgz#3197f5dcce143bcbf961471bf89320735c0212d3" + integrity sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-direction" "1.0.1" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-roving-focus" "1.0.4" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-use-previous" "1.0.1" + "@radix-ui/react-use-size" "1.0.1" + "@radix-ui/react-roving-focus@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974" From 7d1810ebd9b07650c6bbda69a1262aea04d63322 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Thu, 22 Feb 2024 17:42:13 +0700 Subject: [PATCH 25/27] [T-4866: feat] Switch component (#92) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️ feat: create switch component * 📝 docs: add switch to the example page * 🔧 chore: install `@radix-ui/react-switch` * 🎨 style: add inset shadow * 🎨 style: addjust input outline when error and focus --- packages/frontend/package.json | 1 + .../components/shared/Input/Input.theme.ts | 1 + .../components/shared/Switch/Switch.theme.ts | 84 ++++++++++++++++++ .../src/components/shared/Switch/Switch.tsx | 85 +++++++++++++++++++ .../src/components/shared/Switch/index.ts | 1 + .../frontend/src/pages/components/index.tsx | 24 ++++++ packages/frontend/tailwind.config.js | 1 + yarn.lock | 14 +++ 8 files changed, 211 insertions(+) create mode 100644 packages/frontend/src/components/shared/Switch/Switch.theme.ts create mode 100644 packages/frontend/src/components/shared/Switch/Switch.tsx create mode 100644 packages/frontend/src/components/shared/Switch/index.ts diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 2e040901..131ada73 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -7,6 +7,7 @@ "@material-tailwind/react": "^2.1.7", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", diff --git a/packages/frontend/src/components/shared/Input/Input.theme.ts b/packages/frontend/src/components/shared/Input/Input.theme.ts index 8def6a0d..bf5ce915 100644 --- a/packages/frontend/src/components/shared/Input/Input.theme.ts +++ b/packages/frontend/src/components/shared/Input/Input.theme.ts @@ -50,6 +50,7 @@ export const inputTheme = tv( 'outline-offset-0', 'outline-border-danger', 'shadow-none', + 'focus:outline-border-danger', ], helperText: 'text-elements-danger', }, diff --git a/packages/frontend/src/components/shared/Switch/Switch.theme.ts b/packages/frontend/src/components/shared/Switch/Switch.theme.ts new file mode 100644 index 00000000..87fbd092 --- /dev/null +++ b/packages/frontend/src/components/shared/Switch/Switch.theme.ts @@ -0,0 +1,84 @@ +import { tv, type VariantProps } from 'tailwind-variants'; + +export const switchTheme = tv({ + slots: { + wrapper: ['flex', 'items-start', 'gap-4', 'w-[375px]'], + switch: [ + 'h-6', + 'w-12', + 'rounded-full', + 'transition-all', + 'duration-500', + 'relative', + 'cursor-default', + 'shadow-inset', + 'focus-ring', + 'outline-none', + ], + thumb: [ + 'block', + 'h-4', + 'w-4', + 'translate-x-1', + 'transition-transform', + 'duration-100', + 'will-change-transform', + 'rounded-full', + 'shadow-button', + 'data-[state=checked]:translate-x-7', + 'bg-controls-elevated', + ], + label: [ + 'flex', + 'flex-1', + 'flex-col', + 'px-1', + 'gap-1', + 'text-sm', + 'text-elements-high-em', + 'tracking-[-0.006em]', + ], + description: ['text-xs', 'text-elements-low-em'], + }, + variants: { + checked: { + true: { + switch: [ + 'bg-controls-primary', + 'hover:bg-controls-primary-hovered', + 'focus-visible:bg-controls-primary-hovered', + ], + }, + false: { + switch: [ + 'bg-controls-inset', + 'hover:bg-controls-inset-hovered', + 'focus-visible:bg-controls-inset-hovered', + ], + }, + }, + disabled: { + true: { + switch: ['bg-controls-disabled', 'cursor-not-allowed'], + thumb: ['bg-elements-on-disabled'], + }, + }, + fullWidth: { + true: { + wrapper: ['w-full', 'justify-between'], + }, + }, + }, + compoundVariants: [ + { + checked: true, + disabled: true, + class: { + switch: ['bg-controls-disabled-active'], + thumb: ['bg-snowball-900'], + }, + }, + ], +}); + +export type SwitchVariants = VariantProps; diff --git a/packages/frontend/src/components/shared/Switch/Switch.tsx b/packages/frontend/src/components/shared/Switch/Switch.tsx new file mode 100644 index 00000000..32d35957 --- /dev/null +++ b/packages/frontend/src/components/shared/Switch/Switch.tsx @@ -0,0 +1,85 @@ +import React, { type ComponentPropsWithoutRef } from 'react'; +import { type SwitchProps as SwitchRadixProps } from '@radix-ui/react-switch'; +import * as SwitchRadix from '@radix-ui/react-switch'; + +import { switchTheme, type SwitchVariants } from './Switch.theme'; + +interface SwitchProps + extends Omit, + SwitchVariants { + /** + * The label of the switch. + */ + label?: string; + /** + * The description of the switch. + */ + description?: string; + /** + * Custom wrapper props for the switch. + */ + wrapperProps?: ComponentPropsWithoutRef<'div'>; + /** + * Function that is called when the checked state of the switch changes. + * @param checked The new checked state of the switch. + */ + onCheckedChange?(checked: boolean): void; +} + +/** + * A switch is a component used for toggling between two states. + */ +export const Switch = ({ + className, + checked, + label, + description, + disabled, + name, + wrapperProps, + fullWidth, + ...props +}: SwitchProps) => { + const { + wrapper, + switch: switchClass, + thumb, + label: labelClass, + description: descriptionClass, + } = switchTheme({ + checked, + disabled, + fullWidth, + }); + + const switchComponent = ( + + + + ); + + // If a label is provided, wrap the switch in a label element. + if (label) { + return ( +
+ + {switchComponent} +
+ ); + } + + return switchComponent; +}; diff --git a/packages/frontend/src/components/shared/Switch/index.ts b/packages/frontend/src/components/shared/Switch/index.ts new file mode 100644 index 00000000..1b19c1d3 --- /dev/null +++ b/packages/frontend/src/components/shared/Switch/index.ts @@ -0,0 +1 @@ +export * from './Switch'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index 7e7f84db..c3643c24 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -18,6 +18,7 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; +import { Switch } from 'components/shared/Switch'; import { RADIO_OPTIONS } from './renders/radio'; import { Radio } from 'components/shared/Radio'; import { @@ -30,6 +31,7 @@ import { renderTooltips } from './renders/tooltip'; const Page = () => { const [singleDate, setSingleDate] = useState(); const [dateRange, setDateRange] = useState(); + const [switchValue, setSwitchValue] = useState(false); const [selectedRadio, setSelectedRadio] = useState(''); return ( @@ -163,6 +165,28 @@ const Page = () => {
+ {/* Switch */} +
+

Switch

+
+ + + + +
+
+ +
+ {/* Radio */}

Radio

diff --git a/packages/frontend/tailwind.config.js b/packages/frontend/tailwind.config.js index 9a814026..cce3380a 100644 --- a/packages/frontend/tailwind.config.js +++ b/packages/frontend/tailwind.config.js @@ -154,6 +154,7 @@ export default withMT({ calendar: '0px 3px 20px rgba(8, 47, 86, 0.1), 0px 0px 4px rgba(8, 47, 86, 0.14)', field: '0px 1px 2px rgba(0, 0, 0, 0.04)', + inset: 'inset 0px 1px 0px rgba(8, 47, 86, 0.06)', }, spacing: { 2.5: '0.625rem', diff --git a/yarn.lock b/yarn.lock index b789155b..f6800333 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3480,6 +3480,20 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.1" +"@radix-ui/react-switch@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-switch/-/react-switch-1.0.3.tgz#6119f16656a9eafb4424c600fdb36efa5ec5837e" + integrity sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-use-previous" "1.0.1" + "@radix-ui/react-use-size" "1.0.1" + "@radix-ui/react-tabs@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz#993608eec55a5d1deddd446fa9978d2bc1053da2" From 9b6c777f5f2abe67e1bc58b99423f0cd73b9f349 Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Fri, 23 Feb 2024 10:20:08 +0700 Subject: [PATCH 26/27] [T-4865: feat] Segmented controls component (#91) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️ feat: create segmented controls component * 📝 docs: add segmented controls component to example page * ♻️ refactor: put the icon size to icon theme * 🐛 fix: remove `value` from `useCallback` dependency --- .../SegmentedControlItem.tsx | 77 +++++++++++++++ .../SegmentedControls.theme.ts | 76 +++++++++++++++ .../SegmentedControls/SegmentedControls.tsx | 93 +++++++++++++++++++ .../shared/SegmentedControls/index.ts | 2 + .../frontend/src/pages/components/index.tsx | 24 +++++ .../components/renders/segmentedControls.tsx | 44 +++++++++ 6 files changed, 316 insertions(+) create mode 100644 packages/frontend/src/components/shared/SegmentedControls/SegmentedControlItem.tsx create mode 100644 packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.theme.ts create mode 100644 packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.tsx create mode 100644 packages/frontend/src/components/shared/SegmentedControls/index.ts create mode 100644 packages/frontend/src/pages/components/renders/segmentedControls.tsx diff --git a/packages/frontend/src/components/shared/SegmentedControls/SegmentedControlItem.tsx b/packages/frontend/src/components/shared/SegmentedControls/SegmentedControlItem.tsx new file mode 100644 index 00000000..3fe5bda6 --- /dev/null +++ b/packages/frontend/src/components/shared/SegmentedControls/SegmentedControlItem.tsx @@ -0,0 +1,77 @@ +import React, { + forwardRef, + type ComponentPropsWithoutRef, + type ReactNode, +} from 'react'; + +import { + segmentedControlsTheme, + type SegmentedControlsVariants, +} from './SegmentedControls.theme'; +import { cloneIcon } from 'utils/cloneIcon'; + +/** + * Interface for the props of a segmented control item component. + */ +export interface SegmentedControlItemProps + extends Omit, 'type' | 'children'>, + SegmentedControlsVariants { + /** + * The optional left icon element for a component. + */ + leftIcon?: ReactNode; + /** + * The optional right icon element to display. + */ + rightIcon?: ReactNode; + /** + * Indicates whether the item is active or not. + */ + active?: boolean; + /** + * Optional prop that represents the children of a React component. + */ + children?: ReactNode; +} + +/** + * A functional component that represents an item in a segmented control. + * @returns The rendered segmented control item. + */ +const SegmentedControlItem = forwardRef< + HTMLButtonElement, + SegmentedControlItemProps +>( + ( + { + className, + children, + size, + type, + leftIcon, + rightIcon, + active = false, + ...props + }, + ref, + ) => { + const { item, icon } = segmentedControlsTheme({ size, type }); + + return ( + + ); + }, +); + +SegmentedControlItem.displayName = 'SegmentedControlItem'; + +export { SegmentedControlItem }; diff --git a/packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.theme.ts b/packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.theme.ts new file mode 100644 index 00000000..ed54a072 --- /dev/null +++ b/packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.theme.ts @@ -0,0 +1,76 @@ +import { tv, type VariantProps } from 'tailwind-variants'; + +/** + * Defines the theme for a segmented controls. + */ +export const segmentedControlsTheme = tv({ + slots: { + parent: [ + 'flex', + 'items-center', + 'bg-base-bg-emphasized', + 'gap-0.5', + 'rounded-lg', + ], + item: [ + 'flex', + 'items-center', + 'justify-center', + 'gap-2', + 'text-elements-mid-em', + 'bg-transparent', + 'border', + 'border-transparent', + 'cursor-default', + 'whitespace-nowrap', + 'rounded-lg', + 'focus-ring', + 'hover:bg-controls-tertiary-hovered', + 'focus-visible:z-20', + 'focus-visible:bg-controls-tertiary-hovered', + 'disabled:text-controls-disabled', + 'disabled:bg-transparent', + 'disabled:cursor-not-allowed', + 'disabled:border-transparent', + 'data-[active=true]:bg-controls-tertiary', + 'data-[active=true]:text-elements-high-em', + 'data-[active=true]:border-border-interactive/10', + 'data-[active=true]:shadow-field', + 'data-[active=true]:hover:bg-controls-tertiary-hovered', + ], + icon: [], + }, + variants: { + size: { + sm: { + item: ['px-3', 'py-2', 'text-xs'], + icon: ['h-4', 'w-4'], + }, + md: { + item: ['px-4', 'py-3', 'text-sm', 'tracking-[-0.006em]'], + icon: ['h-5', 'w-5'], + }, + }, + type: { + 'fixed-width': { + parent: ['w-fit'], + item: ['w-fit'], + }, + 'full-width': { + parent: ['w-full'], + item: ['w-full'], + }, + }, + }, + defaultVariants: { + size: 'md', + type: 'fixed-width', + }, +}); + +/** + * Defines the type for the variants of a segmented controls. + */ +export type SegmentedControlsVariants = VariantProps< + typeof segmentedControlsTheme +>; diff --git a/packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.tsx b/packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.tsx new file mode 100644 index 00000000..f96fe8c2 --- /dev/null +++ b/packages/frontend/src/components/shared/SegmentedControls/SegmentedControls.tsx @@ -0,0 +1,93 @@ +import React, { + useCallback, + type ComponentPropsWithoutRef, + type ReactNode, +} from 'react'; + +import { + SegmentedControlItem, + type SegmentedControlItemProps, +} from './SegmentedControlItem'; +import { + segmentedControlsTheme, + type SegmentedControlsVariants, +} from './SegmentedControls.theme'; + +/** + * Represents an option for a segmented control. + */ +export interface SegmentedControlsOption + extends Omit { + /** + * The label of the item. + */ + label: ReactNode; + /** + * The value of the item. + * + */ + value: string; +} + +/** + * Represents the props for the SegmentedControls component. + */ +export interface SegmentedControlsProps + extends Omit, 'onChange'>, + SegmentedControlsVariants { + /** + * An array of options for a segmented control component. + */ + options: SegmentedControlsOption[]; + /** + * An optional string value. + */ + value?: T; + /** + * Optional callback function to handle changes in state. + */ + onChange?: (v: T) => void; +} + +/** + * A component that renders segmented controls with customizable options. + */ +export function SegmentedControls({ + className, + options, + value, + type, + size, + onChange, + ...props +}: SegmentedControlsProps) { + const { parent } = segmentedControlsTheme({ size, type }); + + /** + * Handles the change event for a given option. + */ + const handleChange = useCallback( + (option: T) => { + if (!option) return; + onChange?.(option); + }, + [onChange], + ); + + return ( +
+ {options.map((option, index) => ( + handleChange(option.value as T)} + {...option} + > + {option.label} + + ))} +
+ ); +} diff --git a/packages/frontend/src/components/shared/SegmentedControls/index.ts b/packages/frontend/src/components/shared/SegmentedControls/index.ts new file mode 100644 index 00000000..5d569509 --- /dev/null +++ b/packages/frontend/src/components/shared/SegmentedControls/index.ts @@ -0,0 +1,2 @@ +export * from './SegmentedControlItem'; +export * from './SegmentedControls'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index fc6acbec..e6dd56bc 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -18,6 +18,8 @@ import { renderTabs, renderVerticalTabs, } from './renders/tabs'; +import { SegmentedControls } from 'components/shared/SegmentedControls'; +import { SEGMENTED_CONTROLS_OPTIONS } from './renders/segmentedControls'; import { Switch } from 'components/shared/Switch'; import { RADIO_OPTIONS } from './renders/radio'; import { Radio } from 'components/shared/Radio'; @@ -32,6 +34,8 @@ import { renderTooltips } from './renders/tooltip'; const Page = () => { const [singleDate, setSingleDate] = useState(); const [dateRange, setDateRange] = useState(); + const [selectedSegmentedControl, setSelectedSegmentedControl] = + useState('Test 1'); const [switchValue, setSwitchValue] = useState(false); const [selectedRadio, setSelectedRadio] = useState(''); @@ -176,6 +180,26 @@ const Page = () => {
+ {/* Segmented Controls */} +
+

Segmented Controls

+
+ + +
+
+ +
+ {/* Switch */}

Switch

diff --git a/packages/frontend/src/pages/components/renders/segmentedControls.tsx b/packages/frontend/src/pages/components/renders/segmentedControls.tsx new file mode 100644 index 00000000..8f4a019f --- /dev/null +++ b/packages/frontend/src/pages/components/renders/segmentedControls.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Badge } from 'components/shared/Badge'; +import { SegmentedControlsOption } from 'components/shared/SegmentedControls'; + +export const SEGMENTED_CONTROLS_OPTIONS: SegmentedControlsOption[] = [ + { label: 'Test 1', value: 'Test 1' }, + { + label: 'Test 2', + value: 'Test 2', + leftIcon: ( + + 1 + + ), + }, + { + label: 'Test 3', + value: 'Test 3', + rightIcon: ( + + 1 + + ), + }, + { + label: 'Test 4', + value: 'Test 4', + leftIcon: ( + + 1 + + ), + rightIcon: ( + + 1 + + ), + }, + { + label: 'Test 5', + value: 'Test 5', + disabled: true, + }, +]; From 12fba5ee0cc27324778511a4258097808ad1323a Mon Sep 17 00:00:00 2001 From: Wahyu Kurniawan Date: Fri, 23 Feb 2024 10:22:33 +0700 Subject: [PATCH 27/27] [T-4845: feat] Date picker component (#97) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⚡️ feat: create calendar icon for date picker * ⚡️ feat: create date picker component * 📝 docs: add date picker to the example page * 🔧 chore: install `@radix-ui/react-popover` --- packages/frontend/package.json | 1 + .../shared/CustomIcon/CalendarIcon.tsx | 21 ++++ .../src/components/shared/CustomIcon/index.ts | 1 + .../shared/DatePicker/DatePicker.theme.ts | 9 ++ .../shared/DatePicker/DatePicker.tsx | 100 +++++++++++++++++ .../src/components/shared/DatePicker/index.ts | 1 + .../frontend/src/pages/components/index.tsx | 13 ++- yarn.lock | 101 +++++++++++++++++- 8 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 packages/frontend/src/components/shared/CustomIcon/CalendarIcon.tsx create mode 100644 packages/frontend/src/components/shared/DatePicker/DatePicker.theme.ts create mode 100644 packages/frontend/src/components/shared/DatePicker/DatePicker.tsx create mode 100644 packages/frontend/src/components/shared/DatePicker/index.ts diff --git a/packages/frontend/package.json b/packages/frontend/package.json index cbb0b2e4..53252773 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -7,6 +7,7 @@ "@material-tailwind/react": "^2.1.7", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-tabs": "^1.0.4", diff --git a/packages/frontend/src/components/shared/CustomIcon/CalendarIcon.tsx b/packages/frontend/src/components/shared/CustomIcon/CalendarIcon.tsx new file mode 100644 index 00000000..6a51210a --- /dev/null +++ b/packages/frontend/src/components/shared/CustomIcon/CalendarIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { CustomIcon, CustomIconProps } from './CustomIcon'; + +export const CalendarIcon = (props: CustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/frontend/src/components/shared/CustomIcon/index.ts b/packages/frontend/src/components/shared/CustomIcon/index.ts index cb5f9f37..46fd03ad 100644 --- a/packages/frontend/src/components/shared/CustomIcon/index.ts +++ b/packages/frontend/src/components/shared/CustomIcon/index.ts @@ -9,6 +9,7 @@ export * from './WarningIcon'; export * from './SearchIcon'; export * from './CrossIcon'; export * from './GlobeIcon'; +export * from './CalendarIcon'; export * from './CheckRoundFilledIcon'; export * from './InfoRoundFilledIcon'; export * from './LoadingIcon'; diff --git a/packages/frontend/src/components/shared/DatePicker/DatePicker.theme.ts b/packages/frontend/src/components/shared/DatePicker/DatePicker.theme.ts new file mode 100644 index 00000000..522bd348 --- /dev/null +++ b/packages/frontend/src/components/shared/DatePicker/DatePicker.theme.ts @@ -0,0 +1,9 @@ +import { VariantProps, tv } from 'tailwind-variants'; + +export const datePickerTheme = tv({ + slots: { + input: [], + }, +}); + +export type DatePickerTheme = VariantProps; diff --git a/packages/frontend/src/components/shared/DatePicker/DatePicker.tsx b/packages/frontend/src/components/shared/DatePicker/DatePicker.tsx new file mode 100644 index 00000000..69b44e84 --- /dev/null +++ b/packages/frontend/src/components/shared/DatePicker/DatePicker.tsx @@ -0,0 +1,100 @@ +import React, { useCallback, useState } from 'react'; +import { Input, InputProps } from 'components/shared/Input'; +import * as Popover from '@radix-ui/react-popover'; +import { datePickerTheme } from './DatePicker.theme'; +import { Calendar, CalendarProps } from 'components/shared/Calendar'; +import { CalendarIcon } from 'components/shared/CustomIcon'; +import { Value } from 'react-calendar/dist/cjs/shared/types'; +import { format } from 'date-fns'; + +export interface DatePickerProps + extends Omit { + /** + * The props for the calendar component. + */ + calendarProps?: CalendarProps; + /** + * Optional callback function that is called when the value of the input changes. + * @param {string} value - The new value of the input. + * @returns None + */ + onChange?: (value: Value) => void; + /** + * The value of the input. + */ + value?: Value; + /** + * Whether to allow the selection of a date range. + */ + selectRange?: boolean; +} + +/** + * A date picker component that allows users to select a date from a calendar. + * @param {DatePickerProps} props - The props for the date picker component. + * @returns The rendered date picker component. + */ +export const DatePicker = ({ + className, + calendarProps, + value, + onChange, + selectRange = false, + ...props +}: DatePickerProps) => { + const { input } = datePickerTheme(); + + const [open, setOpen] = useState(false); + + /** + * Renders the value of the date based on the current state of `props.value`. + * @returns {string | undefined} - The formatted date value or `undefined` if `props.value` is falsy. + */ + const renderValue = useCallback(() => { + if (!value) return undefined; + if (Array.isArray(value)) { + return value + .map((date) => format(date as Date, 'dd/MM/yyyy')) + .join(' - '); + } + return format(value, 'dd/MM/yyyy'); + }, [value]); + + /** + * Handles the selection of a date from the calendar. + */ + const handleSelect = useCallback( + (date: Value) => { + setOpen(false); + onChange?.(date); + }, + [setOpen, onChange], + ); + + return ( + + + setOpen(true)} />} + readOnly + placeholder="Select a date..." + value={renderValue()} + className={input({ className })} + onClick={() => setOpen(true)} + /> + + + setOpen(false)}> + setOpen(false)} + onSelect={handleSelect} + /> + + + + ); +}; diff --git a/packages/frontend/src/components/shared/DatePicker/index.ts b/packages/frontend/src/components/shared/DatePicker/index.ts new file mode 100644 index 00000000..a48b62e4 --- /dev/null +++ b/packages/frontend/src/components/shared/DatePicker/index.ts @@ -0,0 +1 @@ +export * from './DatePicker'; diff --git a/packages/frontend/src/pages/components/index.tsx b/packages/frontend/src/pages/components/index.tsx index e6dd56bc..9c1d2569 100644 --- a/packages/frontend/src/pages/components/index.tsx +++ b/packages/frontend/src/pages/components/index.tsx @@ -28,6 +28,7 @@ import { renderInlineNotifications, } from './renders/inlineNotifications'; import { renderInputs } from './renders/input'; +import { DatePicker } from 'components/shared/DatePicker'; import { renderToast, renderToastsWithCta } from './renders/toast'; import { renderTooltips } from './renders/tooltip'; @@ -61,7 +62,7 @@ const Page = () => {
- {/* Button */} + {/* Tooltip */}

Tooltip

@@ -152,6 +153,16 @@ const Page = () => { />
+ +

Date Picker

+
+ + +
diff --git a/yarn.lock b/yarn.lock index 476291c8..ce8ae5dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3389,6 +3389,23 @@ "@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-escape-keydown" "1.0.3" +"@radix-ui/react-focus-guards@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad" + integrity sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-focus-scope@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525" + integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-id@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" @@ -3397,6 +3414,28 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-popover@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.0.7.tgz#23eb7e3327330cb75ec7b4092d685398c1654e3c" + integrity sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-focus-guards" "1.0.1" + "@radix-ui/react-focus-scope" "1.0.4" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-popper" "1.1.3" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-use-controllable-state" "1.0.1" + aria-hidden "^1.1.1" + react-remove-scroll "2.5.5" + "@radix-ui/react-popper@1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42" @@ -5452,7 +5491,7 @@ argparse@^2.0.1: resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-hidden@^1.1.3: +aria-hidden@^1.1.1, aria-hidden@^1.1.3: version "1.2.3" resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.3.tgz#14aeb7fb692bbb72d69bebfa47279c1fd725e954" integrity sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ== @@ -7438,6 +7477,11 @@ detect-newline@^3.0.0: resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + detect-node@^2.0.4: version "2.1.0" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" @@ -9028,6 +9072,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== + get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz" @@ -9857,6 +9906,13 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + ip@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" @@ -13919,6 +13975,25 @@ react-refresh@^0.11.0: resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== +react-remove-scroll-bar@^2.3.3: + version "2.3.5" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.5.tgz#cd2543b3ed7716c7c5b446342d21b0e0b303f47c" + integrity sha512-3cqjOqg6s0XbOjWvmasmqHch+RLxIEk2r/70rzGXuz3iIGQsQheEQyqYCBb5EECoD01Vo2SIbDqW4paLeLTASw== + dependencies: + react-style-singleton "^2.2.1" + tslib "^2.0.0" + +react-remove-scroll@2.5.5: + version "2.5.5" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77" + integrity sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw== + dependencies: + react-remove-scroll-bar "^2.3.3" + react-style-singleton "^2.2.1" + tslib "^2.1.0" + use-callback-ref "^1.3.0" + use-sidecar "^1.1.2" + react-router-dom@^6.20.1: version "6.20.1" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.20.1.tgz#e34f8075b9304221420de3609e072bb349824984" @@ -13989,6 +14064,15 @@ react-scripts@5.0.1: optionalDependencies: fsevents "^2.3.2" +react-style-singleton@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" + integrity sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g== + dependencies: + get-nonce "^1.0.0" + invariant "^2.2.4" + tslib "^2.0.0" + react-syntax-highlighter@^15.5.0: version "15.5.0" resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20" @@ -16150,6 +16234,21 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" +use-callback-ref@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.1.tgz#9be64c3902cbd72b07fe55e56408ae3a26036fd0" + integrity sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ== + dependencies: + tslib "^2.0.0" + +use-sidecar@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2" + integrity sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw== + dependencies: + detect-node-es "^1.1.0" + tslib "^2.0.0" + usehooks-ts@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/usehooks-ts/-/usehooks-ts-2.10.0.tgz#ccd0d63168e4db95b061e26e585b0068d3fad0ed"