@cerberus-design/react 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/legacy/_tsup-dts-rollup.d.ts +336 -4
- package/build/legacy/aria-helpers/tabs.aria.js +2 -2
- package/build/legacy/aria-helpers/trap-focus.aria.js +7 -0
- package/build/legacy/aria-helpers/trap-focus.aria.js.map +1 -0
- package/build/legacy/chunk-2F5TB2EV.js +25 -0
- package/build/legacy/chunk-2F5TB2EV.js.map +1 -0
- package/build/legacy/chunk-4CAT3FHV.js +11 -0
- package/build/legacy/chunk-4CAT3FHV.js.map +1 -0
- package/build/legacy/chunk-4M3EUP57.js +22 -0
- package/build/legacy/chunk-4M3EUP57.js.map +1 -0
- package/build/{modern/chunk-X4YQ27D5.js → legacy/chunk-5GEC53G7.js} +5 -5
- package/build/legacy/{chunk-YJCWUN33.js → chunk-67S42J4B.js} +5 -16
- package/build/legacy/chunk-67S42J4B.js.map +1 -0
- package/build/legacy/{chunk-3CBN7U25.js → chunk-6TXQZ3PB.js} +6 -3
- package/build/legacy/chunk-6TXQZ3PB.js.map +1 -0
- package/build/legacy/{chunk-DQOYTLGB.js → chunk-7KJIZIAU.js} +9 -5
- package/build/legacy/chunk-7KJIZIAU.js.map +1 -0
- package/build/legacy/chunk-C45DY4VE.js +17 -0
- package/build/legacy/chunk-C45DY4VE.js.map +1 -0
- package/build/{modern/chunk-HE3HFKYU.js → legacy/chunk-CU7HXAKM.js} +5 -5
- package/build/legacy/{chunk-HE3HFKYU.js.map → chunk-CU7HXAKM.js.map} +1 -1
- package/build/legacy/chunk-D3ZXZA3U.js +155 -0
- package/build/legacy/chunk-D3ZXZA3U.js.map +1 -0
- package/build/legacy/chunk-DGPLSWFJ.js +208 -0
- package/build/legacy/chunk-DGPLSWFJ.js.map +1 -0
- package/build/legacy/{chunk-5XNLWIZO.js → chunk-EVEEQRH6.js} +2 -2
- package/build/legacy/chunk-EVEEQRH6.js.map +1 -0
- package/build/legacy/chunk-G3JEWPLM.js +29 -0
- package/build/legacy/chunk-G3JEWPLM.js.map +1 -0
- package/build/legacy/chunk-JI4YTPEJ.js +47 -0
- package/build/legacy/chunk-JI4YTPEJ.js.map +1 -0
- package/build/legacy/chunk-KESKDLX6.js +30 -0
- package/build/legacy/chunk-KESKDLX6.js.map +1 -0
- package/build/legacy/{chunk-S7HBD2A7.js → chunk-KFUXGX33.js} +2 -2
- package/build/legacy/chunk-OGSAAB6K.js +12 -0
- package/build/legacy/chunk-OGSAAB6K.js.map +1 -0
- package/build/{modern/chunk-G2QMBSK5.js → legacy/chunk-PMCYXRAH.js} +2 -2
- package/build/legacy/chunk-PMCYXRAH.js.map +1 -0
- package/build/legacy/{chunk-734PGVLT.js → chunk-TAZI77TP.js} +2 -2
- package/build/legacy/chunk-TPFNVGYA.js +21 -0
- package/build/legacy/chunk-TPFNVGYA.js.map +1 -0
- package/build/legacy/chunk-TZNYJ3G7.js +25 -0
- package/build/legacy/chunk-TZNYJ3G7.js.map +1 -0
- package/build/legacy/chunk-UPODPCRD.js +12 -0
- package/build/legacy/chunk-UPODPCRD.js.map +1 -0
- package/build/legacy/chunk-VULPMZUW.js +18 -0
- package/build/legacy/chunk-VULPMZUW.js.map +1 -0
- package/build/{modern/chunk-5GSXIYD3.js → legacy/chunk-X2JMXTBH.js} +6 -8
- package/build/{modern/chunk-5GSXIYD3.js.map → legacy/chunk-X2JMXTBH.js.map} +1 -1
- package/build/legacy/components/FeatureFlag.js +10 -0
- package/build/legacy/components/FeatureFlag.js.map +1 -0
- package/build/legacy/components/Input.js +4 -4
- package/build/legacy/components/Label.js +2 -2
- package/build/legacy/components/Modal.js +7 -0
- package/build/legacy/components/Modal.js.map +1 -0
- package/build/legacy/components/ModalDescription.js +7 -0
- package/build/legacy/components/ModalDescription.js.map +1 -0
- package/build/legacy/components/ModalHeader.js +7 -0
- package/build/legacy/components/ModalHeader.js.map +1 -0
- package/build/legacy/components/ModalHeading.js +7 -0
- package/build/legacy/components/ModalHeading.js.map +1 -0
- package/build/legacy/components/ModalIcon.js +7 -0
- package/build/legacy/components/ModalIcon.js.map +1 -0
- package/build/legacy/components/NavMenuList.js +1 -1
- package/build/legacy/components/Portal.js +7 -0
- package/build/legacy/components/Portal.js.map +1 -0
- package/build/legacy/components/Tab.js +3 -3
- package/build/legacy/components/TabList.js +2 -2
- package/build/legacy/components/TabPanel.js +2 -2
- package/build/legacy/components/Tag.js +1 -1
- package/build/legacy/components/Toggle.js +3 -3
- package/build/legacy/config/cerbIcons.js +1 -1
- package/build/legacy/config/defineIcons.js +2 -2
- package/build/legacy/context/confirm-modal.js +22 -0
- package/build/legacy/context/confirm-modal.js.map +1 -0
- package/build/legacy/context/feature-flags.js +10 -0
- package/build/legacy/context/feature-flags.js.map +1 -0
- package/build/legacy/context/prompt-modal.js +25 -0
- package/build/legacy/context/prompt-modal.js.map +1 -0
- package/build/legacy/context/tabs.js +1 -1
- package/build/legacy/hooks/useModal.js +8 -0
- package/build/legacy/hooks/useModal.js.map +1 -0
- package/build/legacy/index.js +89 -35
- package/build/modern/_tsup-dts-rollup.d.ts +336 -4
- package/build/modern/aria-helpers/tabs.aria.js +2 -2
- package/build/modern/aria-helpers/trap-focus.aria.js +7 -0
- package/build/modern/aria-helpers/trap-focus.aria.js.map +1 -0
- package/build/modern/chunk-2F5TB2EV.js +25 -0
- package/build/modern/chunk-2F5TB2EV.js.map +1 -0
- package/build/modern/chunk-4CAT3FHV.js +11 -0
- package/build/modern/chunk-4CAT3FHV.js.map +1 -0
- package/build/modern/chunk-4M3EUP57.js +22 -0
- package/build/modern/chunk-4M3EUP57.js.map +1 -0
- package/build/{legacy/chunk-X4YQ27D5.js → modern/chunk-5GEC53G7.js} +5 -5
- package/build/modern/{chunk-YJCWUN33.js → chunk-67S42J4B.js} +5 -16
- package/build/modern/chunk-67S42J4B.js.map +1 -0
- package/build/modern/{chunk-3CBN7U25.js → chunk-6TXQZ3PB.js} +6 -3
- package/build/modern/chunk-6TXQZ3PB.js.map +1 -0
- package/build/modern/{chunk-DQOYTLGB.js → chunk-7KJIZIAU.js} +9 -5
- package/build/modern/chunk-7KJIZIAU.js.map +1 -0
- package/build/modern/chunk-C45DY4VE.js +17 -0
- package/build/modern/chunk-C45DY4VE.js.map +1 -0
- package/build/modern/chunk-C5HLLGME.js +23 -0
- package/build/modern/chunk-C5HLLGME.js.map +1 -0
- package/build/{legacy/chunk-HE3HFKYU.js → modern/chunk-CU7HXAKM.js} +5 -5
- package/build/modern/{chunk-HE3HFKYU.js.map → chunk-CU7HXAKM.js.map} +1 -1
- package/build/modern/chunk-G3JEWPLM.js +29 -0
- package/build/modern/chunk-G3JEWPLM.js.map +1 -0
- package/build/modern/chunk-HBEEHHON.js +46 -0
- package/build/modern/chunk-HBEEHHON.js.map +1 -0
- package/build/modern/chunk-JIZQFTW6.js +29 -0
- package/build/modern/chunk-JIZQFTW6.js.map +1 -0
- package/build/modern/{chunk-S7HBD2A7.js → chunk-KFUXGX33.js} +2 -2
- package/build/modern/chunk-OGSAAB6K.js +12 -0
- package/build/modern/chunk-OGSAAB6K.js.map +1 -0
- package/build/{legacy/chunk-G2QMBSK5.js → modern/chunk-PMCYXRAH.js} +2 -2
- package/build/modern/chunk-PMCYXRAH.js.map +1 -0
- package/build/modern/chunk-TAVCJ54A.js +154 -0
- package/build/modern/chunk-TAVCJ54A.js.map +1 -0
- package/build/modern/{chunk-734PGVLT.js → chunk-TAZI77TP.js} +2 -2
- package/build/modern/chunk-TPFNVGYA.js +21 -0
- package/build/modern/chunk-TPFNVGYA.js.map +1 -0
- package/build/modern/chunk-UPODPCRD.js +12 -0
- package/build/modern/chunk-UPODPCRD.js.map +1 -0
- package/build/modern/chunk-VULPMZUW.js +18 -0
- package/build/modern/chunk-VULPMZUW.js.map +1 -0
- package/build/modern/chunk-WWG5QWXY.js +207 -0
- package/build/modern/chunk-WWG5QWXY.js.map +1 -0
- package/build/{legacy/chunk-5GSXIYD3.js → modern/chunk-X2JMXTBH.js} +6 -8
- package/build/{legacy/chunk-5GSXIYD3.js.map → modern/chunk-X2JMXTBH.js.map} +1 -1
- package/build/modern/{chunk-SLIOCQBZ.js → chunk-Z6IWNVPN.js} +2 -2
- package/build/modern/chunk-Z6IWNVPN.js.map +1 -0
- package/build/modern/components/FeatureFlag.js +10 -0
- package/build/modern/components/FeatureFlag.js.map +1 -0
- package/build/modern/components/Input.js +4 -4
- package/build/modern/components/Label.js +2 -2
- package/build/modern/components/Modal.js +7 -0
- package/build/modern/components/Modal.js.map +1 -0
- package/build/modern/components/ModalDescription.js +7 -0
- package/build/modern/components/ModalDescription.js.map +1 -0
- package/build/modern/components/ModalHeader.js +7 -0
- package/build/modern/components/ModalHeader.js.map +1 -0
- package/build/modern/components/ModalHeading.js +7 -0
- package/build/modern/components/ModalHeading.js.map +1 -0
- package/build/modern/components/ModalIcon.js +7 -0
- package/build/modern/components/ModalIcon.js.map +1 -0
- package/build/modern/components/NavMenuList.js +1 -1
- package/build/modern/components/Portal.js +7 -0
- package/build/modern/components/Portal.js.map +1 -0
- package/build/modern/components/Tab.js +3 -3
- package/build/modern/components/TabList.js +2 -2
- package/build/modern/components/TabPanel.js +2 -2
- package/build/modern/components/Tag.js +1 -1
- package/build/modern/components/Toggle.js +3 -3
- package/build/modern/config/cerbIcons.js +1 -1
- package/build/modern/config/defineIcons.js +2 -2
- package/build/modern/context/confirm-modal.js +22 -0
- package/build/modern/context/confirm-modal.js.map +1 -0
- package/build/modern/context/feature-flags.js +10 -0
- package/build/modern/context/feature-flags.js.map +1 -0
- package/build/modern/context/prompt-modal.js +25 -0
- package/build/modern/context/prompt-modal.js.map +1 -0
- package/build/modern/context/tabs.js +1 -1
- package/build/modern/hooks/useModal.js +8 -0
- package/build/modern/hooks/useModal.js.map +1 -0
- package/build/modern/index.js +89 -35
- package/package.json +3 -2
- package/src/aria-helpers/trap-focus.aria.ts +29 -0
- package/src/components/FeatureFlag.tsx +14 -0
- package/src/components/Label.tsx +1 -1
- package/src/components/Modal.tsx +37 -0
- package/src/components/ModalDescription.tsx +23 -0
- package/src/components/ModalHeader.tsx +37 -0
- package/src/components/ModalHeading.tsx +23 -0
- package/src/components/ModalIcon.tsx +28 -0
- package/src/components/NavMenuList.tsx +1 -1
- package/src/components/Portal.tsx +22 -0
- package/src/components/Tab.tsx +3 -84
- package/src/components/TabList.tsx +2 -4
- package/src/components/TabPanel.tsx +3 -14
- package/src/components/Tag.tsx +1 -1
- package/src/config/cerbIcons.ts +11 -2
- package/src/config/defineIcons.ts +6 -3
- package/src/context/confirm-modal.tsx +185 -0
- package/src/context/feature-flags.tsx +60 -0
- package/src/context/prompt-modal.tsx +232 -0
- package/src/context/tabs.tsx +15 -5
- package/src/hooks/useModal.ts +34 -0
- package/src/index.ts +12 -0
- package/build/legacy/chunk-3CBN7U25.js.map +0 -1
- package/build/legacy/chunk-5MNCW677.js +0 -11
- package/build/legacy/chunk-5MNCW677.js.map +0 -1
- package/build/legacy/chunk-5XNLWIZO.js.map +0 -1
- package/build/legacy/chunk-DQOYTLGB.js.map +0 -1
- package/build/legacy/chunk-G2QMBSK5.js.map +0 -1
- package/build/legacy/chunk-YA2UV2AB.js +0 -126
- package/build/legacy/chunk-YA2UV2AB.js.map +0 -1
- package/build/legacy/chunk-YJCWUN33.js.map +0 -1
- package/build/modern/chunk-3CBN7U25.js.map +0 -1
- package/build/modern/chunk-5MNCW677.js +0 -11
- package/build/modern/chunk-5MNCW677.js.map +0 -1
- package/build/modern/chunk-DQOYTLGB.js.map +0 -1
- package/build/modern/chunk-G2QMBSK5.js.map +0 -1
- package/build/modern/chunk-SLIOCQBZ.js.map +0 -1
- package/build/modern/chunk-SUP7U42W.js +0 -125
- package/build/modern/chunk-SUP7U42W.js.map +0 -1
- package/build/modern/chunk-YJCWUN33.js.map +0 -1
- /package/build/legacy/{chunk-X4YQ27D5.js.map → chunk-5GEC53G7.js.map} +0 -0
- /package/build/legacy/{chunk-S7HBD2A7.js.map → chunk-KFUXGX33.js.map} +0 -0
- /package/build/legacy/{chunk-734PGVLT.js.map → chunk-TAZI77TP.js.map} +0 -0
- /package/build/modern/{chunk-X4YQ27D5.js.map → chunk-5GEC53G7.js.map} +0 -0
- /package/build/modern/{chunk-S7HBD2A7.js.map → chunk-KFUXGX33.js.map} +0 -0
- /package/build/modern/{chunk-734PGVLT.js.map → chunk-TAZI77TP.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { cx } from '@cerberus/styled-system/css'
|
|
4
4
|
import { useMemo, type HTMLAttributes } from 'react'
|
|
5
5
|
import { useTabsContext } from '../context/tabs'
|
|
6
6
|
import { Show } from './Show'
|
|
@@ -26,7 +26,7 @@ export interface TabPanelProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
26
26
|
*/
|
|
27
27
|
export function TabPanel(props: TabPanelProps) {
|
|
28
28
|
const { tab, ...nativeProps } = props
|
|
29
|
-
const { active } = useTabsContext()
|
|
29
|
+
const { active, styles } = useTabsContext()
|
|
30
30
|
const isActive = useMemo(() => active === tab, [active, tab])
|
|
31
31
|
|
|
32
32
|
return (
|
|
@@ -35,18 +35,7 @@ export function TabPanel(props: TabPanelProps) {
|
|
|
35
35
|
{...nativeProps}
|
|
36
36
|
{...(isActive && { tabIndex: 0 })}
|
|
37
37
|
aria-labelledby={tab}
|
|
38
|
-
className={cx(
|
|
39
|
-
nativeProps.className,
|
|
40
|
-
css({
|
|
41
|
-
rounded: 'md',
|
|
42
|
-
_focusVisible: {
|
|
43
|
-
boxShadow: 'none',
|
|
44
|
-
outline: '3px solid',
|
|
45
|
-
outlineColor: 'action.border.focus',
|
|
46
|
-
outlineOffset: '2px',
|
|
47
|
-
},
|
|
48
|
-
}),
|
|
49
|
-
)}
|
|
38
|
+
className={cx(nativeProps.className, styles.tabPanel)}
|
|
50
39
|
id={`panel:${tab}`}
|
|
51
40
|
role="tabpanel"
|
|
52
41
|
/>
|
package/src/components/Tag.tsx
CHANGED
|
@@ -37,7 +37,7 @@ export type TagProps = StaticTagProps | ClickableTagProps
|
|
|
37
37
|
*/
|
|
38
38
|
export function Tag(props: PropsWithChildren<TagProps>): JSX.Element {
|
|
39
39
|
const { shape: initShape, gradient, onClick, usage, ...nativeProps } = props
|
|
40
|
-
const palette = props?.palette ?? '
|
|
40
|
+
const palette = props?.palette ?? 'page'
|
|
41
41
|
const isClosable = Boolean(onClick)
|
|
42
42
|
const shape = isClosable ? 'pill' : initShape
|
|
43
43
|
const closableStyles = isClosable ? closableCss : ''
|
package/src/config/cerbIcons.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Checkmark,
|
|
3
|
+
Information,
|
|
4
|
+
WarningFilled,
|
|
5
|
+
type CarbonIconType,
|
|
6
|
+
} from '@cerberus/icons'
|
|
2
7
|
import type { ElementType } from 'react'
|
|
3
8
|
|
|
4
9
|
export interface DefinedIcons {
|
|
10
|
+
confirmModal?: CarbonIconType | ElementType
|
|
11
|
+
promptModal?: CarbonIconType | ElementType
|
|
5
12
|
invalid: CarbonIconType | ElementType
|
|
6
|
-
toggleChecked
|
|
13
|
+
toggleChecked?: CarbonIconType | ElementType
|
|
7
14
|
}
|
|
8
15
|
|
|
9
16
|
export const defaultIcons: DefinedIcons = {
|
|
17
|
+
confirmModal: Information,
|
|
18
|
+
promptModal: Information,
|
|
10
19
|
invalid: WarningFilled,
|
|
11
20
|
toggleChecked: Checkmark,
|
|
12
21
|
}
|
|
@@ -8,12 +8,15 @@ function _validateIconsProperties(icons: DefinedIcons) {
|
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function defineIcons(icons: DefinedIcons): DefinedIcons {
|
|
11
|
+
export function defineIcons(icons: DefinedIcons): Required<DefinedIcons> {
|
|
12
12
|
_validateIconsProperties(icons)
|
|
13
|
-
$cerberusIcons =
|
|
13
|
+
$cerberusIcons = {
|
|
14
|
+
...defaultIcons,
|
|
15
|
+
...icons,
|
|
16
|
+
} as Required<DefinedIcons>
|
|
14
17
|
return $cerberusIcons
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
// Default icons
|
|
18
21
|
|
|
19
|
-
export let $cerberusIcons
|
|
22
|
+
export let $cerberusIcons = defaultIcons as Required<DefinedIcons>
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type MouseEvent,
|
|
11
|
+
type PropsWithChildren,
|
|
12
|
+
} from 'react'
|
|
13
|
+
import { Portal } from '../components/Portal'
|
|
14
|
+
import { Button } from '../components/Button'
|
|
15
|
+
import { css } from '@cerberus-design/styled-system/css'
|
|
16
|
+
import { hstack } from '@cerberus-design/styled-system/patterns'
|
|
17
|
+
import { $cerberusIcons } from '../config/defineIcons'
|
|
18
|
+
import { trapFocus } from '../aria-helpers/trap-focus.aria'
|
|
19
|
+
import { ModalIcon } from '../components/ModalIcon'
|
|
20
|
+
import { Show } from '../components/Show'
|
|
21
|
+
import { Modal } from '../components/Modal'
|
|
22
|
+
import { useModal } from '../hooks/useModal'
|
|
23
|
+
import { ModalHeader } from '../components/ModalHeader'
|
|
24
|
+
import { ModalHeading } from '../components/ModalHeading'
|
|
25
|
+
import { ModalDescription } from '../components/ModalDescription'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* This module provides a context and hook for the confirm modal.
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
export interface ShowConfirmModalOptions {
|
|
33
|
+
kind?: 'destructive' | 'non-destructive'
|
|
34
|
+
heading: string
|
|
35
|
+
description?: string
|
|
36
|
+
actionText: string
|
|
37
|
+
cancelText: string
|
|
38
|
+
}
|
|
39
|
+
export type ShowResult =
|
|
40
|
+
| ((value: boolean | PromiseLike<boolean>) => void)
|
|
41
|
+
| null
|
|
42
|
+
|
|
43
|
+
export interface ConfirmModalValue {
|
|
44
|
+
show: (options: ShowConfirmModalOptions) => Promise<boolean>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const ConfirmModalContext = createContext<ConfirmModalValue | null>(null)
|
|
48
|
+
|
|
49
|
+
export interface ConfirmModalProviderProps {}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Provides a confirm modal to the app.
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* // Wrap the Provider around the root of the feature.
|
|
56
|
+
* <ConfirmModal>
|
|
57
|
+
* <SomeFeatureSection />
|
|
58
|
+
* </ConfirmModal>
|
|
59
|
+
*
|
|
60
|
+
* // Use the hook to show the confirm modal.
|
|
61
|
+
* const confirm = useConfirmModal()
|
|
62
|
+
*
|
|
63
|
+
* const handleClick = useCallback(async () => {
|
|
64
|
+
* const userConsent = await confirm.show({
|
|
65
|
+
* heading: 'Add new payment method?',
|
|
66
|
+
* description:
|
|
67
|
+
* 'This will add a new payment method to your account to be billed for future purchases.',
|
|
68
|
+
* actionText: 'Yes, add payment method',
|
|
69
|
+
* cancelText: 'No, cancel',
|
|
70
|
+
* })
|
|
71
|
+
* setConsent(userConsent)
|
|
72
|
+
* }, [confirm])
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function ConfirmModal(
|
|
76
|
+
props: PropsWithChildren<ConfirmModalProviderProps>,
|
|
77
|
+
) {
|
|
78
|
+
const { modalRef, show, close } = useModal()
|
|
79
|
+
const resolveRef = useRef<ShowResult>(null)
|
|
80
|
+
const [content, setContent] = useState<ShowConfirmModalOptions | null>(null)
|
|
81
|
+
const focusTrap = trapFocus(modalRef)
|
|
82
|
+
const ConfirmIcon = $cerberusIcons.confirmModal
|
|
83
|
+
|
|
84
|
+
const palette = useMemo(
|
|
85
|
+
() => (content?.kind === 'destructive' ? 'danger' : 'action'),
|
|
86
|
+
[content],
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const handleChoice = useCallback(
|
|
90
|
+
(e: MouseEvent<HTMLButtonElement>) => {
|
|
91
|
+
const target = e.currentTarget as HTMLButtonElement
|
|
92
|
+
if (target.value === 'true') {
|
|
93
|
+
resolveRef.current?.(true)
|
|
94
|
+
}
|
|
95
|
+
resolveRef.current?.(false)
|
|
96
|
+
close()
|
|
97
|
+
},
|
|
98
|
+
[close],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
const handleShow = useCallback(
|
|
102
|
+
(options: ShowConfirmModalOptions) => {
|
|
103
|
+
return new Promise<boolean>((resolve) => {
|
|
104
|
+
setContent({ ...options, kind: options.kind || 'non-destructive' })
|
|
105
|
+
show()
|
|
106
|
+
resolveRef.current = resolve
|
|
107
|
+
})
|
|
108
|
+
},
|
|
109
|
+
[show],
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
const value = useMemo(
|
|
113
|
+
() => ({
|
|
114
|
+
show: handleShow,
|
|
115
|
+
}),
|
|
116
|
+
[handleShow],
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<ConfirmModalContext.Provider value={value}>
|
|
121
|
+
{props.children}
|
|
122
|
+
|
|
123
|
+
<Portal>
|
|
124
|
+
<Modal onKeyDown={focusTrap} ref={modalRef}>
|
|
125
|
+
<ModalHeader>
|
|
126
|
+
<Show
|
|
127
|
+
when={palette === 'danger'}
|
|
128
|
+
fallback={
|
|
129
|
+
<ModalIcon palette="action">
|
|
130
|
+
<ConfirmIcon size={24} />
|
|
131
|
+
</ModalIcon>
|
|
132
|
+
}
|
|
133
|
+
>
|
|
134
|
+
<ModalIcon palette="danger">
|
|
135
|
+
<ConfirmIcon size={24} />
|
|
136
|
+
</ModalIcon>
|
|
137
|
+
</Show>
|
|
138
|
+
<ModalHeading>{content?.heading}</ModalHeading>
|
|
139
|
+
<ModalDescription>{content?.description}</ModalDescription>
|
|
140
|
+
</ModalHeader>
|
|
141
|
+
|
|
142
|
+
<div
|
|
143
|
+
className={hstack({
|
|
144
|
+
gap: '4',
|
|
145
|
+
})}
|
|
146
|
+
>
|
|
147
|
+
<Button
|
|
148
|
+
autoFocus
|
|
149
|
+
className={css({
|
|
150
|
+
w: '1/2',
|
|
151
|
+
})}
|
|
152
|
+
name="confirm"
|
|
153
|
+
onClick={handleChoice}
|
|
154
|
+
palette={palette}
|
|
155
|
+
value="true"
|
|
156
|
+
>
|
|
157
|
+
{content?.actionText}
|
|
158
|
+
</Button>
|
|
159
|
+
<Button
|
|
160
|
+
className={css({
|
|
161
|
+
w: '1/2',
|
|
162
|
+
})}
|
|
163
|
+
name="cancel"
|
|
164
|
+
onClick={handleChoice}
|
|
165
|
+
usage="outlined"
|
|
166
|
+
value="false"
|
|
167
|
+
>
|
|
168
|
+
{content?.cancelText}
|
|
169
|
+
</Button>
|
|
170
|
+
</div>
|
|
171
|
+
</Modal>
|
|
172
|
+
</Portal>
|
|
173
|
+
</ConfirmModalContext.Provider>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function useConfirmModal(): ConfirmModalValue {
|
|
178
|
+
const context = useContext(ConfirmModalContext)
|
|
179
|
+
if (context === null) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
'useConfirmModal must be used within a ConfirmModal Provider',
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
return context
|
|
185
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, type PropsWithChildren } from 'react'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This module provides a context and hook for feature flags.
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface FeatureFlagValue {
|
|
11
|
+
[key: string]: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const FeatureFlagContext = createContext<FeatureFlagValue | null>(null)
|
|
15
|
+
|
|
16
|
+
export interface FeatureFlagProviderProps {
|
|
17
|
+
flags: FeatureFlagValue
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Provides feature flags to the application.
|
|
22
|
+
* @param flags - The flags data for the provider.
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* // This should be a JSON file or a server response.
|
|
26
|
+
* const flags = {
|
|
27
|
+
* featureOne: true,
|
|
28
|
+
* featureTwo: false
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* // Wrap the Provider around the root of your application.
|
|
32
|
+
* <FeatureFlags flags={flags}>
|
|
33
|
+
* <FeatureFlag flag="featureOne">
|
|
34
|
+
* This is visible.
|
|
35
|
+
* </FeatureFlag>
|
|
36
|
+
* <FeatureFlag flag="featureTwo">
|
|
37
|
+
* This is hidden.
|
|
38
|
+
* </FeatureFlag>
|
|
39
|
+
* </FeatureFlags>
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function FeatureFlags(
|
|
43
|
+
props: PropsWithChildren<FeatureFlagProviderProps>,
|
|
44
|
+
) {
|
|
45
|
+
return (
|
|
46
|
+
<FeatureFlagContext.Provider value={props.flags}>
|
|
47
|
+
{props.children}
|
|
48
|
+
</FeatureFlagContext.Provider>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useFeatureFlags(key: string): boolean {
|
|
53
|
+
const context = useContext(FeatureFlagContext)
|
|
54
|
+
if (context === null) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'useFeatureFlag must be used within a FeatureFlags Provider',
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
return context[key] ?? false
|
|
60
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type ChangeEvent,
|
|
11
|
+
type MouseEvent,
|
|
12
|
+
type PropsWithChildren,
|
|
13
|
+
} from 'react'
|
|
14
|
+
import { Portal } from '../components/Portal'
|
|
15
|
+
import { Button } from '../components/Button'
|
|
16
|
+
import { css } from '@cerberus-design/styled-system/css'
|
|
17
|
+
import { hstack, vstack } from '@cerberus-design/styled-system/patterns'
|
|
18
|
+
import { trapFocus } from '../aria-helpers/trap-focus.aria'
|
|
19
|
+
import { Input } from '../components/Input'
|
|
20
|
+
import { Field } from './field'
|
|
21
|
+
import { Label } from '../components/Label'
|
|
22
|
+
import { $cerberusIcons } from '../config/defineIcons'
|
|
23
|
+
import { ModalIcon } from '../components/ModalIcon'
|
|
24
|
+
import { Show } from '../components/Show'
|
|
25
|
+
import { useModal } from '../hooks/useModal'
|
|
26
|
+
import { Modal } from '../components/Modal'
|
|
27
|
+
import { ModalHeader } from '../components/ModalHeader'
|
|
28
|
+
import { ModalHeading } from '../components/ModalHeading'
|
|
29
|
+
import { ModalDescription } from '../components/ModalDescription'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* This module provides a context and hook for the prompt modal.
|
|
33
|
+
* @module
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export interface ShowPromptModalOptions {
|
|
37
|
+
kind?: 'destructive' | 'non-destructive'
|
|
38
|
+
heading: string
|
|
39
|
+
description?: string
|
|
40
|
+
key: string
|
|
41
|
+
actionText: string
|
|
42
|
+
cancelText: string
|
|
43
|
+
}
|
|
44
|
+
export type PromptShowResult =
|
|
45
|
+
| ((value: string | PromiseLike<string>) => void)
|
|
46
|
+
| null
|
|
47
|
+
|
|
48
|
+
export interface PromptModalValue {
|
|
49
|
+
show: (options: ShowPromptModalOptions) => Promise<string>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const PromptModalContext = createContext<PromptModalValue | null>(null)
|
|
53
|
+
|
|
54
|
+
export interface PromptModalProviderProps {}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Provides a prompt modal to the app.
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* // Wrap the Provider around the root of the feature.
|
|
61
|
+
* <PromptModal>
|
|
62
|
+
* <SomeFeatureSection />
|
|
63
|
+
* </PromptModal>
|
|
64
|
+
*
|
|
65
|
+
* // Use the hook to show the prompt modal.
|
|
66
|
+
* const prompt = usePromptModal()
|
|
67
|
+
*
|
|
68
|
+
* const handleClick = useCallback(async () => {
|
|
69
|
+
* const accepted = await prompt.show({
|
|
70
|
+
* kind: 'destructive',
|
|
71
|
+
* heading: 'Delete channel?',
|
|
72
|
+
* description:
|
|
73
|
+
* 'This will permanently delete a channel on your account. There is no going back.',
|
|
74
|
+
* key: CHANNEL_NAME,
|
|
75
|
+
* actionText: 'Yes, delete channel',
|
|
76
|
+
* cancelText: 'No, cancel',
|
|
77
|
+
* })
|
|
78
|
+
* // do something with accepted
|
|
79
|
+
* }, [prompt])
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export function PromptModal(
|
|
83
|
+
props: PropsWithChildren<PromptModalProviderProps>,
|
|
84
|
+
) {
|
|
85
|
+
const { modalRef, show, close } = useModal()
|
|
86
|
+
const resolveRef = useRef<PromptShowResult>(null)
|
|
87
|
+
const [content, setContent] = useState<ShowPromptModalOptions | null>(null)
|
|
88
|
+
const [inputValue, setInputValue] = useState<string>('')
|
|
89
|
+
const focusTrap = trapFocus(modalRef)
|
|
90
|
+
const PromptIcon = $cerberusIcons.promptModal
|
|
91
|
+
|
|
92
|
+
const isValid = useMemo(
|
|
93
|
+
() => inputValue === content?.key,
|
|
94
|
+
[inputValue, content],
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const palette = useMemo(
|
|
98
|
+
() => (content?.kind === 'destructive' ? 'danger' : 'action'),
|
|
99
|
+
[content],
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
const handleChange = useCallback(
|
|
103
|
+
(e: ChangeEvent<HTMLInputElement>) => {
|
|
104
|
+
setInputValue(e.currentTarget.value)
|
|
105
|
+
},
|
|
106
|
+
[content],
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
const handleChoice = useCallback(
|
|
110
|
+
(e: MouseEvent<HTMLButtonElement>) => {
|
|
111
|
+
const target = e.currentTarget as HTMLButtonElement
|
|
112
|
+
if (target.value === 'true') {
|
|
113
|
+
resolveRef.current?.(inputValue)
|
|
114
|
+
}
|
|
115
|
+
close()
|
|
116
|
+
},
|
|
117
|
+
[inputValue, close],
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const handleShow = useCallback(
|
|
121
|
+
(options: ShowPromptModalOptions) => {
|
|
122
|
+
return new Promise<string>((resolve) => {
|
|
123
|
+
setContent({ ...options, kind: options.kind || 'non-destructive' })
|
|
124
|
+
show()
|
|
125
|
+
resolveRef.current = resolve
|
|
126
|
+
})
|
|
127
|
+
},
|
|
128
|
+
[show],
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const value = useMemo(
|
|
132
|
+
() => ({
|
|
133
|
+
show: handleShow,
|
|
134
|
+
}),
|
|
135
|
+
[handleShow],
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<PromptModalContext.Provider value={value}>
|
|
140
|
+
{props.children}
|
|
141
|
+
|
|
142
|
+
<Portal>
|
|
143
|
+
<Modal onKeyDown={focusTrap} ref={modalRef}>
|
|
144
|
+
<ModalHeader>
|
|
145
|
+
<Show
|
|
146
|
+
when={palette === 'danger'}
|
|
147
|
+
fallback={
|
|
148
|
+
<ModalIcon palette="action">
|
|
149
|
+
<PromptIcon size={24} />
|
|
150
|
+
</ModalIcon>
|
|
151
|
+
}
|
|
152
|
+
>
|
|
153
|
+
<ModalIcon palette="danger">
|
|
154
|
+
<PromptIcon size={24} />
|
|
155
|
+
</ModalIcon>
|
|
156
|
+
</Show>
|
|
157
|
+
<ModalHeading>{content?.heading}</ModalHeading>
|
|
158
|
+
<ModalDescription>{content?.description}</ModalDescription>
|
|
159
|
+
</ModalHeader>
|
|
160
|
+
|
|
161
|
+
<div
|
|
162
|
+
className={vstack({
|
|
163
|
+
alignItems: 'flex-start',
|
|
164
|
+
mt: '4',
|
|
165
|
+
mb: '8',
|
|
166
|
+
})}
|
|
167
|
+
>
|
|
168
|
+
<Field invalid={!isValid}>
|
|
169
|
+
<Label htmlFor="confirm" size="md">
|
|
170
|
+
Type
|
|
171
|
+
<strong
|
|
172
|
+
className={css({
|
|
173
|
+
textTransform: 'uppercase',
|
|
174
|
+
})}
|
|
175
|
+
>
|
|
176
|
+
{content?.key}
|
|
177
|
+
</strong>
|
|
178
|
+
to confirm
|
|
179
|
+
</Label>
|
|
180
|
+
<Input
|
|
181
|
+
id="confirm"
|
|
182
|
+
name="confirm"
|
|
183
|
+
onChange={handleChange}
|
|
184
|
+
type="text"
|
|
185
|
+
/>
|
|
186
|
+
</Field>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div
|
|
190
|
+
className={hstack({
|
|
191
|
+
justifyContent: 'stretch',
|
|
192
|
+
gap: '4',
|
|
193
|
+
})}
|
|
194
|
+
>
|
|
195
|
+
<Button
|
|
196
|
+
autoFocus
|
|
197
|
+
className={css({
|
|
198
|
+
w: '1/2',
|
|
199
|
+
})}
|
|
200
|
+
disabled={!isValid}
|
|
201
|
+
name="confirm"
|
|
202
|
+
onClick={handleChoice}
|
|
203
|
+
palette={palette}
|
|
204
|
+
value="true"
|
|
205
|
+
>
|
|
206
|
+
{content?.actionText}
|
|
207
|
+
</Button>
|
|
208
|
+
<Button
|
|
209
|
+
className={css({
|
|
210
|
+
w: '1/2',
|
|
211
|
+
})}
|
|
212
|
+
name="cancel"
|
|
213
|
+
onClick={handleChoice}
|
|
214
|
+
usage="outlined"
|
|
215
|
+
value="false"
|
|
216
|
+
>
|
|
217
|
+
{content?.cancelText}
|
|
218
|
+
</Button>
|
|
219
|
+
</div>
|
|
220
|
+
</Modal>
|
|
221
|
+
</Portal>
|
|
222
|
+
</PromptModalContext.Provider>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function usePromptModal(): PromptModalValue {
|
|
227
|
+
const context = useContext(PromptModalContext)
|
|
228
|
+
if (context === null) {
|
|
229
|
+
throw new Error('usePromptModal must be used within a PromptModal Provider')
|
|
230
|
+
}
|
|
231
|
+
return context
|
|
232
|
+
}
|
package/src/context/tabs.tsx
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
tabs,
|
|
5
|
+
type TabsVariantProps,
|
|
6
|
+
} from '@cerberus-design/styled-system/recipes'
|
|
7
|
+
import type { Pretty } from '@cerberus-design/styled-system/types'
|
|
3
8
|
import {
|
|
4
9
|
createContext,
|
|
5
10
|
useContext,
|
|
@@ -20,6 +25,7 @@ export interface TabsContextValue {
|
|
|
20
25
|
tabs: MutableRefObject<HTMLButtonElement[]>
|
|
21
26
|
id: string
|
|
22
27
|
active: string
|
|
28
|
+
styles: Pretty<Record<'tabList' | 'tab' | 'tabPanel', string>>
|
|
23
29
|
onTabUpdate: (active: string) => void
|
|
24
30
|
}
|
|
25
31
|
|
|
@@ -33,6 +39,7 @@ export interface TabsProps {
|
|
|
33
39
|
|
|
34
40
|
/**
|
|
35
41
|
* The Tabs component provides a context to manage tab state.
|
|
42
|
+
* @param id - the id of the tabs component,
|
|
36
43
|
* @param active - the default active tab id,
|
|
37
44
|
* @param cache - whether to cache the active tab state in local storage
|
|
38
45
|
* @example
|
|
@@ -49,22 +56,25 @@ export interface TabsProps {
|
|
|
49
56
|
* </Tabs>
|
|
50
57
|
* ```
|
|
51
58
|
*/
|
|
52
|
-
export function Tabs(
|
|
53
|
-
|
|
59
|
+
export function Tabs(
|
|
60
|
+
props: PropsWithChildren<TabsProps & TabsVariantProps>,
|
|
61
|
+
): JSX.Element {
|
|
62
|
+
const { cache, active, id, palette } = props
|
|
54
63
|
const [activeTab, setActiveTab] = useState(() => (cache ? '' : active ?? ''))
|
|
55
|
-
const
|
|
64
|
+
const tabsList = useRef<HTMLButtonElement[]>([])
|
|
56
65
|
const uuid = useMemo(() => {
|
|
57
66
|
return id ? `cerberus-tabs-${id}` : 'cerberus-tabs'
|
|
58
67
|
}, [id])
|
|
59
68
|
|
|
60
69
|
const value = useMemo(
|
|
61
70
|
() => ({
|
|
62
|
-
tabs,
|
|
71
|
+
tabs: tabsList,
|
|
63
72
|
id: uuid,
|
|
64
73
|
active: activeTab,
|
|
74
|
+
styles: tabs({ palette }),
|
|
65
75
|
onTabUpdate: setActiveTab,
|
|
66
76
|
}),
|
|
67
|
-
[activeTab, setActiveTab, uuid,
|
|
77
|
+
[activeTab, setActiveTab, palette, uuid, tabsList],
|
|
68
78
|
)
|
|
69
79
|
|
|
70
80
|
// Get the active tab from local storage
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo, useRef, type RefObject } from 'react'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This module provides a hook for using a custom modal.
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface UseModalReturnValue {
|
|
11
|
+
modalRef: RefObject<HTMLDialogElement>
|
|
12
|
+
show: () => void
|
|
13
|
+
close: () => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useModal(): UseModalReturnValue {
|
|
17
|
+
const modalRef = useRef<HTMLDialogElement | null>(null)
|
|
18
|
+
|
|
19
|
+
const show = useCallback(() => {
|
|
20
|
+
modalRef.current?.showModal()
|
|
21
|
+
}, [])
|
|
22
|
+
|
|
23
|
+
const close = useCallback(() => {
|
|
24
|
+
modalRef.current?.close()
|
|
25
|
+
}, [])
|
|
26
|
+
|
|
27
|
+
return useMemo(() => {
|
|
28
|
+
return {
|
|
29
|
+
modalRef,
|
|
30
|
+
show,
|
|
31
|
+
close,
|
|
32
|
+
}
|
|
33
|
+
}, [modalRef, show, close])
|
|
34
|
+
}
|