@duro-app/ui 5.12.0 → 5.13.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/dist/Table-Bms2ftt3.js +1694 -0
- package/dist/Table-Bms2ftt3.js.map +1 -0
- package/dist/components/AppShell/native.css.d.ts +53 -0
- package/dist/components/AppShell/native.css.d.ts.map +1 -0
- package/dist/components/Drawer/Drawer.d.ts +7 -35
- package/dist/components/Drawer/Drawer.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.meta.d.ts.map +1 -1
- package/dist/components/Drawer/DrawerScroll.stories.d.ts.map +1 -1
- package/dist/components/Drawer/DrawerSnap.stories.d.ts +20 -0
- package/dist/components/Drawer/DrawerSnap.stories.d.ts.map +1 -0
- package/dist/components/Drawer/native.css.d.ts +55 -0
- package/dist/components/Drawer/native.css.d.ts.map +1 -0
- package/dist/components/Drawer/parts.css.d.ts +96 -0
- package/dist/components/Drawer/parts.css.d.ts.map +1 -0
- package/dist/components/Drawer/parts.d.ts +56 -0
- package/dist/components/Drawer/parts.d.ts.map +1 -0
- package/dist/components/Drawer/sheetPhysics.d.ts +56 -0
- package/dist/components/Drawer/sheetPhysics.d.ts.map +1 -0
- package/dist/components/Drawer/styles.css.d.ts +13 -58
- package/dist/components/Drawer/styles.css.d.ts.map +1 -1
- package/dist/components/Drawer/touchInput.d.ts +35 -0
- package/dist/components/Drawer/touchInput.d.ts.map +1 -0
- package/dist/components/Drawer/useDrawerRoot.d.ts +33 -0
- package/dist/components/Drawer/useDrawerRoot.d.ts.map +1 -0
- package/dist/components/Drawer/useSheetHeights.d.ts +7 -0
- package/dist/components/Drawer/useSheetHeights.d.ts.map +1 -0
- package/dist/components/Drawer/useSwipeDismiss.d.ts +13 -2
- package/dist/components/Drawer/useSwipeDismiss.d.ts.map +1 -1
- package/dist/components/Menu/Menu.d.ts +7 -1
- package/dist/components/Menu/Menu.d.ts.map +1 -1
- package/dist/components/Menu/native.css.d.ts +14 -0
- package/dist/components/Menu/native.css.d.ts.map +1 -0
- package/dist/components/Menu/useMenuOpen.d.ts +16 -0
- package/dist/components/Menu/useMenuOpen.d.ts.map +1 -0
- package/dist/components/Menu/useMenuRoot.d.ts +3 -6
- package/dist/components/Menu/useMenuRoot.d.ts.map +1 -1
- package/dist/components/Popover/native.css.d.ts +11 -0
- package/dist/components/Popover/native.css.d.ts.map +1 -0
- package/dist/components/SideNav/Chevron.d.ts +4 -0
- package/dist/components/SideNav/Chevron.d.ts.map +1 -0
- package/dist/components/SideNav/SideNav.d.ts.map +1 -1
- package/dist/components/SideNav/styles.css.d.ts +6 -0
- package/dist/components/SideNav/styles.css.d.ts.map +1 -1
- package/dist/components/SideNav/types.d.ts +6 -0
- package/dist/components/SideNav/types.d.ts.map +1 -0
- package/dist/components/SplitPane/SplitPane.d.ts +38 -0
- package/dist/components/SplitPane/SplitPane.d.ts.map +1 -0
- package/dist/components/SplitPane/SplitPane.meta.d.ts +3 -0
- package/dist/components/SplitPane/SplitPane.meta.d.ts.map +1 -0
- package/dist/components/SplitPane/SplitPane.stories.d.ts +17 -0
- package/dist/components/SplitPane/SplitPane.stories.d.ts.map +1 -0
- package/dist/components/SplitPane/styles.css.d.ts +78 -0
- package/dist/components/SplitPane/styles.css.d.ts.map +1 -0
- package/dist/components/Tabs/TabLayout.d.ts +7 -0
- package/dist/components/Tabs/TabLayout.d.ts.map +1 -0
- package/dist/components/Tabs/Tabs.d.ts.map +1 -1
- package/dist/components/Tabs/indicator.d.ts +28 -0
- package/dist/components/Tabs/indicator.d.ts.map +1 -0
- package/dist/components/Tabs/styles.css.d.ts +4 -0
- package/dist/components/Tabs/styles.css.d.ts.map +1 -1
- package/dist/components/Tabs/useTabIndicator.d.ts +4 -0
- package/dist/components/Tabs/useTabIndicator.d.ts.map +1 -0
- package/dist/components/Tabs/useTabKeys.d.ts +13 -0
- package/dist/components/Tabs/useTabKeys.d.ts.map +1 -0
- package/dist/components/Tag/RemoveGlyph.d.ts +4 -0
- package/dist/components/Tag/RemoveGlyph.d.ts.map +1 -0
- package/dist/components/Tag/Tag.d.ts +2 -24
- package/dist/components/Tag/Tag.d.ts.map +1 -1
- package/dist/components/Tag/styles.css.d.ts +8 -0
- package/dist/components/Tag/styles.css.d.ts.map +1 -1
- package/dist/components/Tag/types.d.ts +39 -0
- package/dist/components/Tag/types.d.ts.map +1 -0
- package/dist/components/Toast/ToastProvider.d.ts +2 -21
- package/dist/components/Toast/ToastProvider.d.ts.map +1 -1
- package/dist/components/Toast/native.css.d.ts +81 -0
- package/dist/components/Toast/native.css.d.ts.map +1 -0
- package/dist/components/Toast/store.d.ts +47 -0
- package/dist/components/Toast/store.d.ts.map +1 -0
- package/dist/components/Tooltip/TooltipHint.d.ts +9 -0
- package/dist/components/Tooltip/TooltipHint.d.ts.map +1 -0
- package/dist/components/Tree/Tree.d.ts.map +1 -1
- package/dist/components/Tree/styles.css.d.ts +6 -0
- package/dist/components/Tree/styles.css.d.ts.map +1 -1
- package/dist/components/Tree/useTreeKeyboard.d.ts +19 -0
- package/dist/components/Tree/useTreeKeyboard.d.ts.map +1 -0
- package/dist/components/VirtualList/VirtualList.d.ts +33 -0
- package/dist/components/VirtualList/VirtualList.d.ts.map +1 -0
- package/dist/components/VirtualList/VirtualList.meta.d.ts +3 -0
- package/dist/components/VirtualList/VirtualList.meta.d.ts.map +1 -0
- package/dist/components/VirtualList/VirtualList.stories.d.ts +15 -0
- package/dist/components/VirtualList/VirtualList.stories.d.ts.map +1 -0
- package/dist/components/VirtualList/styles.css.d.ts +28 -0
- package/dist/components/VirtualList/styles.css.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4935 -4260
- package/dist/index.js.map +1 -1
- package/dist/shared/ButtonHost.d.ts +11 -0
- package/dist/shared/ButtonHost.d.ts.map +1 -0
- package/dist/shared/pressableChild.d.ts +14 -0
- package/dist/shared/pressableChild.d.ts.map +1 -0
- package/dist/table.js +1 -1
- package/package.json +2 -2
- package/src/components/AppShell/AppShell.meta.ts +1 -1
- package/src/components/AppShell/AppShell.native.tsx +174 -0
- package/src/components/AppShell/native.css.ts +56 -0
- package/src/components/Button/Button.tsx +3 -3
- package/src/components/Drawer/Drawer.meta.ts +2 -1
- package/src/components/Drawer/Drawer.native.tsx +469 -0
- package/src/components/Drawer/Drawer.tsx +58 -145
- package/src/components/Drawer/DrawerScroll.stories.tsx +4 -54
- package/src/components/Drawer/DrawerSnap.stories.tsx +205 -0
- package/src/components/Drawer/native.css.ts +58 -0
- package/src/components/Drawer/parts.css.ts +125 -0
- package/src/components/Drawer/parts.tsx +110 -0
- package/src/components/Drawer/sheetPhysics.ts +103 -0
- package/src/components/Drawer/styles.css.ts +15 -74
- package/src/components/Drawer/touchInput.ts +81 -0
- package/src/components/Drawer/useDrawerRoot.ts +90 -0
- package/src/components/Drawer/useSheetHeights.ts +42 -0
- package/src/components/Drawer/useSwipeDismiss.ts +146 -23
- package/src/components/Menu/Menu.meta.ts +1 -1
- package/src/components/Menu/Menu.native.tsx +268 -0
- package/src/components/Menu/Menu.tsx +11 -2
- package/src/components/Menu/native.css.ts +18 -0
- package/src/components/Menu/useMenuOpen.ts +33 -0
- package/src/components/Menu/useMenuRoot.ts +7 -26
- package/src/components/Popover/Popover.meta.ts +1 -1
- package/src/components/Popover/Popover.native.tsx +205 -0
- package/src/components/Popover/native.css.ts +13 -0
- package/src/components/SideNav/Chevron.native.tsx +20 -0
- package/src/components/SideNav/Chevron.tsx +20 -0
- package/src/components/SideNav/SideNav.meta.ts +1 -1
- package/src/components/SideNav/SideNav.tsx +57 -41
- package/src/components/SideNav/styles.css.ts +8 -0
- package/src/components/SideNav/types.ts +6 -0
- package/src/components/SplitPane/SplitPane.meta.ts +40 -0
- package/src/components/SplitPane/SplitPane.native.tsx +47 -0
- package/src/components/SplitPane/SplitPane.stories.tsx +148 -0
- package/src/components/SplitPane/SplitPane.tsx +218 -0
- package/src/components/SplitPane/styles.css.ts +81 -0
- package/src/components/Tabs/TabLayout.native.tsx +9 -0
- package/src/components/Tabs/TabLayout.tsx +6 -0
- package/src/components/Tabs/Tabs.meta.ts +1 -1
- package/src/components/Tabs/Tabs.tsx +38 -136
- package/src/components/Tabs/indicator.ts +36 -0
- package/src/components/Tabs/styles.css.ts +7 -0
- package/src/components/Tabs/useTabIndicator.native.ts +35 -0
- package/src/components/Tabs/useTabIndicator.ts +49 -0
- package/src/components/Tabs/useTabKeys.native.ts +2 -0
- package/src/components/Tabs/useTabKeys.ts +99 -0
- package/src/components/Tag/RemoveGlyph.native.tsx +17 -0
- package/src/components/Tag/RemoveGlyph.tsx +10 -0
- package/src/components/Tag/Tag.meta.ts +1 -1
- package/src/components/Tag/Tag.native.tsx +70 -0
- package/src/components/Tag/Tag.tsx +9 -37
- package/src/components/Tag/styles.css.ts +9 -0
- package/src/components/Tag/types.ts +43 -0
- package/src/components/Toast/Toast.meta.ts +1 -1
- package/src/components/Toast/ToastProvider.native.tsx +226 -0
- package/src/components/Toast/ToastProvider.tsx +22 -84
- package/src/components/Toast/native.css.ts +78 -0
- package/src/components/Toast/store.ts +96 -0
- package/src/components/Toggle/Toggle.tsx +3 -3
- package/src/components/Tooltip/Tooltip.meta.ts +1 -1
- package/src/components/Tooltip/Tooltip.native.tsx +36 -0
- package/src/components/Tooltip/TooltipHint.ts +13 -0
- package/src/components/Tree/Tree.meta.ts +1 -1
- package/src/components/Tree/Tree.tsx +23 -135
- package/src/components/Tree/styles.css.ts +6 -0
- package/src/components/Tree/useTreeKeyboard.native.ts +5 -0
- package/src/components/Tree/useTreeKeyboard.ts +152 -0
- package/src/components/VirtualList/VirtualList.meta.ts +39 -0
- package/src/components/VirtualList/VirtualList.stories.tsx +95 -0
- package/src/components/VirtualList/VirtualList.tsx +103 -0
- package/src/components/VirtualList/styles.css.ts +31 -0
- package/src/hooks/useContainerBelow.native.ts +30 -0
- package/src/index.native.ts +33 -0
- package/src/index.ts +8 -1
- package/src/shared/ButtonHost.native.tsx +70 -0
- package/src/shared/ButtonHost.tsx +12 -0
- package/src/shared/portal/AnchoredOverlay.native.tsx +117 -0
- package/src/shared/pressableChild.ts +33 -0
- package/src/shared/useKeyboardHeight.native.ts +18 -0
- package/src/shared/useScreenReaderEnabled.native.ts +19 -0
- package/dist/Table-ColfpgKV.js +0 -1552
- package/dist/Table-ColfpgKV.js.map +0 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {createContext, useContext} from 'react'
|
|
2
|
+
import type {Orientation} from '../../shared/types'
|
|
3
|
+
|
|
4
|
+
/** Where the sliding indicator sits along the list, in px. */
|
|
5
|
+
export interface IndicatorBox {
|
|
6
|
+
offset: number
|
|
7
|
+
size: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** A tab's box inside its list, as native `onLayout` reports it. */
|
|
11
|
+
export interface TabBox {
|
|
12
|
+
x: number
|
|
13
|
+
y: number
|
|
14
|
+
width: number
|
|
15
|
+
height: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Native: each Tab reports its box here, so the List places the indicator. */
|
|
19
|
+
export const TabLayoutContext = createContext<((value: string, box: TabBox) => void) | null>(null)
|
|
20
|
+
|
|
21
|
+
export function useReportTabLayout() {
|
|
22
|
+
return useContext(TabLayoutContext)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TabIndicatorInput {
|
|
26
|
+
listRef: React.RefObject<HTMLDivElement | null>
|
|
27
|
+
activeValue: string | null
|
|
28
|
+
orientation: Orientation
|
|
29
|
+
setIndicatorActive: (active: boolean) => void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface TabIndicator {
|
|
33
|
+
indicator: IndicatorBox | null
|
|
34
|
+
/** Native only: what the Tabs report their layout to. */
|
|
35
|
+
reportTabLayout: ((value: string, box: TabBox) => void) | null
|
|
36
|
+
}
|
|
@@ -32,6 +32,13 @@ export const styles = css.create({
|
|
|
32
32
|
borderRightColor: colors.border,
|
|
33
33
|
gap: 0,
|
|
34
34
|
},
|
|
35
|
+
// RSD on native lays out only display: flex, not the web's inline-flex,
|
|
36
|
+
// and its Animated colour transition ends on a near-black colour: native
|
|
37
|
+
// tabs transition nothing that changes (colour and underline switch at once).
|
|
38
|
+
nativeFlex: {
|
|
39
|
+
display: 'flex',
|
|
40
|
+
transitionProperty: 'opacity',
|
|
41
|
+
},
|
|
35
42
|
tab: {
|
|
36
43
|
display: 'inline-flex',
|
|
37
44
|
alignItems: 'center',
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {useCallback, useEffect, useState} from 'react'
|
|
2
|
+
import type {TabBox, TabIndicator, TabIndicatorInput} from './indicator'
|
|
3
|
+
|
|
4
|
+
/** Native: the indicator follows the active tab's `onLayout` box. */
|
|
5
|
+
export function useTabIndicator({
|
|
6
|
+
activeValue,
|
|
7
|
+
orientation,
|
|
8
|
+
setIndicatorActive,
|
|
9
|
+
}: TabIndicatorInput): TabIndicator {
|
|
10
|
+
const [boxes, setBoxes] = useState<ReadonlyMap<string, TabBox>>(() => new Map())
|
|
11
|
+
const reportTabLayout = useCallback((value: string, box: TabBox) => {
|
|
12
|
+
setBoxes((prev) => {
|
|
13
|
+
const old = prev.get(value)
|
|
14
|
+
if (
|
|
15
|
+
old &&
|
|
16
|
+
old.x === box.x &&
|
|
17
|
+
old.y === box.y &&
|
|
18
|
+
old.width === box.width &&
|
|
19
|
+
old.height === box.height
|
|
20
|
+
) {
|
|
21
|
+
return prev
|
|
22
|
+
}
|
|
23
|
+
return new Map(prev).set(value, box)
|
|
24
|
+
})
|
|
25
|
+
}, [])
|
|
26
|
+
const box = activeValue ? boxes.get(activeValue) : undefined
|
|
27
|
+
const indicator = box
|
|
28
|
+
? orientation === 'horizontal'
|
|
29
|
+
? {offset: box.x, size: box.width}
|
|
30
|
+
: {offset: box.y, size: box.height}
|
|
31
|
+
: null
|
|
32
|
+
const active = indicator !== null
|
|
33
|
+
useEffect(() => setIndicatorActive(active), [active, setIndicatorActive])
|
|
34
|
+
return {indicator, reportTabLayout}
|
|
35
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {useLayoutEffect, useState} from 'react'
|
|
2
|
+
import type {IndicatorBox, TabIndicator, TabIndicatorInput} from './indicator'
|
|
3
|
+
|
|
4
|
+
/** Web: the active tab measured in the DOM, kept current by a ResizeObserver. */
|
|
5
|
+
export function useTabIndicator({
|
|
6
|
+
listRef,
|
|
7
|
+
activeValue,
|
|
8
|
+
orientation,
|
|
9
|
+
setIndicatorActive,
|
|
10
|
+
}: TabIndicatorInput): TabIndicator {
|
|
11
|
+
const [indicator, setIndicator] = useState<IndicatorBox | null>(null)
|
|
12
|
+
|
|
13
|
+
// Measure the active tab and drive the sliding indicator. useLayoutEffect so
|
|
14
|
+
// the first position is set before paint (the indicator element mounts already
|
|
15
|
+
// in place — no slide-in from zero; only later tab changes animate). Observes
|
|
16
|
+
// the active tab (its own size, e.g. a changing "(3)" count) and the list
|
|
17
|
+
// (container resize / reflow). A non-active sibling resizing while you're on
|
|
18
|
+
// another tab is the one case not caught; it self-corrects on the next
|
|
19
|
+
// selection or resize.
|
|
20
|
+
useLayoutEffect(() => {
|
|
21
|
+
const list = listRef.current
|
|
22
|
+
if (!list || !activeValue) {
|
|
23
|
+
setIndicator(null)
|
|
24
|
+
setIndicatorActive(false)
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
const el = list.querySelector<HTMLElement>(`[data-tab-value="${CSS.escape(activeValue)}"]`)
|
|
28
|
+
if (!el) {
|
|
29
|
+
setIndicator(null)
|
|
30
|
+
setIndicatorActive(false)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
const measure = () => {
|
|
34
|
+
setIndicator(
|
|
35
|
+
orientation === 'horizontal'
|
|
36
|
+
? {offset: el.offsetLeft, size: el.offsetWidth}
|
|
37
|
+
: {offset: el.offsetTop, size: el.offsetHeight},
|
|
38
|
+
)
|
|
39
|
+
setIndicatorActive(true)
|
|
40
|
+
}
|
|
41
|
+
measure()
|
|
42
|
+
const ro = new ResizeObserver(measure)
|
|
43
|
+
ro.observe(el)
|
|
44
|
+
ro.observe(list)
|
|
45
|
+
return () => ro.disconnect()
|
|
46
|
+
}, [listRef, activeValue, orientation, setIndicatorActive])
|
|
47
|
+
|
|
48
|
+
return {indicator, reportTabLayout: null}
|
|
49
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {useEffect} from 'react'
|
|
2
|
+
import type {Orientation} from '../../shared/types'
|
|
3
|
+
|
|
4
|
+
interface TabKeysInput {
|
|
5
|
+
listRef: React.RefObject<HTMLDivElement | null>
|
|
6
|
+
orientation: Orientation
|
|
7
|
+
activeValue: string | null
|
|
8
|
+
onSelect: (value: string) => void
|
|
9
|
+
tabsRef: React.RefObject<Map<string, boolean>>
|
|
10
|
+
orderRef: React.RefObject<string[]>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Web: arrow keys, Home and End move the selection and focus along the list. */
|
|
14
|
+
export function useTabKeys({
|
|
15
|
+
listRef,
|
|
16
|
+
orientation,
|
|
17
|
+
activeValue,
|
|
18
|
+
onSelect,
|
|
19
|
+
tabsRef,
|
|
20
|
+
orderRef,
|
|
21
|
+
}: TabKeysInput) {
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const el = listRef.current
|
|
24
|
+
if (!el) return
|
|
25
|
+
const listEl = el
|
|
26
|
+
|
|
27
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
28
|
+
const order = orderRef.current
|
|
29
|
+
const tabs = tabsRef.current
|
|
30
|
+
if (order.length === 0) return
|
|
31
|
+
|
|
32
|
+
const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp'
|
|
33
|
+
const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown'
|
|
34
|
+
|
|
35
|
+
let targetValue: string | null = null
|
|
36
|
+
|
|
37
|
+
switch (e.key) {
|
|
38
|
+
case nextKey: {
|
|
39
|
+
e.preventDefault()
|
|
40
|
+
const currentIdx = activeValue ? order.indexOf(activeValue) : -1
|
|
41
|
+
for (let i = 1; i <= order.length; i++) {
|
|
42
|
+
const idx = (currentIdx + i) % order.length
|
|
43
|
+
const val = order[idx]
|
|
44
|
+
if (!tabs.get(val)) {
|
|
45
|
+
targetValue = val
|
|
46
|
+
break
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
break
|
|
50
|
+
}
|
|
51
|
+
case prevKey: {
|
|
52
|
+
e.preventDefault()
|
|
53
|
+
const currentIdx = activeValue ? order.indexOf(activeValue) : 0
|
|
54
|
+
for (let i = 1; i <= order.length; i++) {
|
|
55
|
+
const idx = (currentIdx - i + order.length) % order.length
|
|
56
|
+
const val = order[idx]
|
|
57
|
+
if (!tabs.get(val)) {
|
|
58
|
+
targetValue = val
|
|
59
|
+
break
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
break
|
|
63
|
+
}
|
|
64
|
+
case 'Home': {
|
|
65
|
+
e.preventDefault()
|
|
66
|
+
for (const val of order) {
|
|
67
|
+
if (!tabs.get(val)) {
|
|
68
|
+
targetValue = val
|
|
69
|
+
break
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
case 'End': {
|
|
75
|
+
e.preventDefault()
|
|
76
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
77
|
+
if (!tabs.get(order[i])) {
|
|
78
|
+
targetValue = order[i]
|
|
79
|
+
break
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
break
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (targetValue) {
|
|
87
|
+
onSelect(targetValue)
|
|
88
|
+
// Focus the newly activated tab button
|
|
89
|
+
const tabEl = listEl.querySelector(
|
|
90
|
+
`[data-tab-value="${targetValue}"]`,
|
|
91
|
+
) as HTMLElement | null
|
|
92
|
+
tabEl?.focus()
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
el.addEventListener('keydown', handleKeyDown)
|
|
97
|
+
return () => el.removeEventListener('keydown', handleKeyDown)
|
|
98
|
+
}, [listRef, orientation, activeValue, onSelect, tabsRef, orderRef])
|
|
99
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Svg, {Path} from 'react-native-svg'
|
|
2
|
+
import {useResolvedColors} from '../ThemeProvider/ThemeProvider'
|
|
3
|
+
import type {RemoveGlyphProps} from './types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The same cross as the web's, drawn with react-native-svg (the native icon
|
|
7
|
+
* renderer, plan N0 Decision 9) in the tag's resolved text colour: a raw
|
|
8
|
+
* host has no CSS `currentColor` to inherit.
|
|
9
|
+
*/
|
|
10
|
+
export function RemoveGlyph({size, color}: RemoveGlyphProps) {
|
|
11
|
+
const stroke = useResolvedColors()[color]
|
|
12
|
+
return (
|
|
13
|
+
<Svg width={size} height={size} viewBox="0 0 12 12" fill="none" aria-hidden>
|
|
14
|
+
<Path d="M3 3l6 6M9 3l-6 6" stroke={stroke} strokeWidth={1.5} strokeLinecap="round" />
|
|
15
|
+
</Svg>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type {RemoveGlyphProps} from './types'
|
|
2
|
+
|
|
3
|
+
/** The tag's remove cross: 1.5 strokes on a 12 box, drawn in the tag's text colour. */
|
|
4
|
+
export function RemoveGlyph({size}: RemoveGlyphProps) {
|
|
5
|
+
return (
|
|
6
|
+
<svg width={size} height={size} viewBox="0 0 12 12" fill="none">
|
|
7
|
+
<path d="M3 3l6 6M9 3l-6 6" stroke="currentColor" strokeWidth={1.5} strokeLinecap="round" />
|
|
8
|
+
</svg>
|
|
9
|
+
)
|
|
10
|
+
}
|
|
@@ -2,7 +2,7 @@ import type {ComponentMeta} from '../component-meta'
|
|
|
2
2
|
|
|
3
3
|
export const meta: ComponentMeta = {
|
|
4
4
|
description:
|
|
5
|
-
'Interactive tag/chip with optional remove button. Works standalone or inside TagGroup for collection management. Visually similar to Badge but supports interaction.',
|
|
5
|
+
'Interactive tag/chip with optional remove button. Works standalone or inside TagGroup for collection management. Visually similar to Badge but supports interaction. On React Native a Tag stands alone (TagGroup is web only): the label and, when removable, a remove button named from a string label.',
|
|
6
6
|
whenToUse: [
|
|
7
7
|
'Removable items in a list (emails, categories)',
|
|
8
8
|
'Inside TagGroup for editable tag collections',
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {html} from 'react-strict-dom'
|
|
2
|
+
import {SIZES_PX} from '@duro-app/tokens/keys'
|
|
3
|
+
import {styles} from './styles.css'
|
|
4
|
+
import {RemoveGlyph} from './RemoveGlyph'
|
|
5
|
+
import {variantText, type TagProps} from './types'
|
|
6
|
+
|
|
7
|
+
export type {TagProps, TagSize, TagVariant} from './types'
|
|
8
|
+
|
|
9
|
+
const sizeMap = {
|
|
10
|
+
sm: styles.sizeSm,
|
|
11
|
+
md: styles.sizeMd,
|
|
12
|
+
} as const
|
|
13
|
+
|
|
14
|
+
const removableSizeMap = {
|
|
15
|
+
sm: styles.removableSm,
|
|
16
|
+
md: styles.removableMd,
|
|
17
|
+
} as const
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Native Tag (plan N0, Slice B): the label and, when removable, the remove
|
|
21
|
+
* button. TagGroup is not ported (its grid keyboard model is web only,
|
|
22
|
+
* Decision 6), so `value` is unused here; the remove button's name comes from
|
|
23
|
+
* a string label.
|
|
24
|
+
*/
|
|
25
|
+
export function Tag({
|
|
26
|
+
variant = 'default',
|
|
27
|
+
size = 'md',
|
|
28
|
+
removable = false,
|
|
29
|
+
onRemove,
|
|
30
|
+
disabled = false,
|
|
31
|
+
wrap = false,
|
|
32
|
+
children,
|
|
33
|
+
}: TagProps) {
|
|
34
|
+
const removeLabel = typeof children === 'string' ? `Remove ${children}` : 'Remove'
|
|
35
|
+
// A div, not the web's span: a native span is a Text, which would lay the
|
|
36
|
+
// remove button out inline, raised off the label's centre line.
|
|
37
|
+
return (
|
|
38
|
+
<html.div
|
|
39
|
+
style={[
|
|
40
|
+
styles.base,
|
|
41
|
+
styles.nativeFlex,
|
|
42
|
+
sizeMap[size],
|
|
43
|
+
removable && removableSizeMap[size],
|
|
44
|
+
styles[variant],
|
|
45
|
+
disabled && styles.disabled,
|
|
46
|
+
wrap && styles.wrap,
|
|
47
|
+
]}
|
|
48
|
+
>
|
|
49
|
+
<html.span>{children}</html.span>
|
|
50
|
+
{removable && (
|
|
51
|
+
<html.button
|
|
52
|
+
type="button"
|
|
53
|
+
aria-label={removeLabel}
|
|
54
|
+
disabled={disabled}
|
|
55
|
+
onClick={disabled ? undefined : onRemove}
|
|
56
|
+
style={[
|
|
57
|
+
styles.removeButton,
|
|
58
|
+
styles.nativeButtonFlex,
|
|
59
|
+
disabled && styles.removeButtonDisabled,
|
|
60
|
+
]}
|
|
61
|
+
>
|
|
62
|
+
<RemoveGlyph
|
|
63
|
+
size={size === 'sm' ? SIZES_PX.glyphXs : SIZES_PX.glyphSm}
|
|
64
|
+
color={variantText[variant]}
|
|
65
|
+
/>
|
|
66
|
+
</html.button>
|
|
67
|
+
)}
|
|
68
|
+
</html.div>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
@@ -1,31 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {useCallback, useRef} from 'react'
|
|
2
2
|
import {html} from 'react-strict-dom'
|
|
3
|
+
import {SIZES_PX} from '@duro-app/tokens/keys'
|
|
3
4
|
import {useTagGroup} from '../TagGroup/TagGroupContext'
|
|
4
5
|
import {styles} from './styles.css'
|
|
6
|
+
import {RemoveGlyph} from './RemoveGlyph'
|
|
7
|
+
import {variantText, type TagProps} from './types'
|
|
5
8
|
|
|
6
|
-
export type
|
|
7
|
-
export type TagSize = 'sm' | 'md'
|
|
8
|
-
|
|
9
|
-
interface TagProps {
|
|
10
|
-
/** Value identifier — required when used inside TagGroup */
|
|
11
|
-
value?: string
|
|
12
|
-
variant?: TagVariant
|
|
13
|
-
size?: TagSize
|
|
14
|
-
/** Show remove button. Auto-derived from TagGroup context when inside one. */
|
|
15
|
-
removable?: boolean
|
|
16
|
-
/** Called when remove button is clicked. Auto-wired to TagGroup when inside one. */
|
|
17
|
-
onRemove?: () => void
|
|
18
|
-
disabled?: boolean
|
|
19
|
-
/**
|
|
20
|
-
* Allow the label to wrap / break instead of staying on one line. Tags are
|
|
21
|
-
* `white-space: nowrap` by default (they read as pills); set this for long
|
|
22
|
-
* identifier-style values (e.g. `lldap.group.member.remove`) sitting in a
|
|
23
|
-
* constrained column, so they break within their cell instead of leaking
|
|
24
|
-
* into the next one.
|
|
25
|
-
*/
|
|
26
|
-
wrap?: boolean
|
|
27
|
-
children: ReactNode
|
|
28
|
-
}
|
|
9
|
+
export type {TagProps, TagSize, TagVariant} from './types'
|
|
29
10
|
|
|
30
11
|
const sizeMap = {
|
|
31
12
|
sm: styles.sizeSm,
|
|
@@ -112,19 +93,10 @@ export function Tag({
|
|
|
112
93
|
tabIndex={-1}
|
|
113
94
|
style={[styles.removeButton, disabled && styles.removeButtonDisabled]}
|
|
114
95
|
>
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
fill="none"
|
|
120
|
-
>
|
|
121
|
-
<path
|
|
122
|
-
d="M3 3l6 6M9 3l-6 6"
|
|
123
|
-
stroke="currentColor"
|
|
124
|
-
strokeWidth={1.5}
|
|
125
|
-
strokeLinecap="round"
|
|
126
|
-
/>
|
|
127
|
-
</svg>
|
|
96
|
+
<RemoveGlyph
|
|
97
|
+
size={size === 'sm' ? SIZES_PX.glyphXs : SIZES_PX.glyphSm}
|
|
98
|
+
color={variantText[variant]}
|
|
99
|
+
/>
|
|
128
100
|
</html.button>
|
|
129
101
|
</html.span>
|
|
130
102
|
)}
|
|
@@ -110,6 +110,15 @@ export const styles = css.create({
|
|
|
110
110
|
},
|
|
111
111
|
outlineWidth: 0,
|
|
112
112
|
},
|
|
113
|
+
// RSD on native lays out only display: flex, not the web's inline-flex.
|
|
114
|
+
nativeFlex: {
|
|
115
|
+
display: 'flex',
|
|
116
|
+
flexDirection: 'row',
|
|
117
|
+
alignSelf: 'flex-start',
|
|
118
|
+
},
|
|
119
|
+
nativeButtonFlex: {
|
|
120
|
+
display: 'flex',
|
|
121
|
+
},
|
|
113
122
|
removeButtonDisabled: {
|
|
114
123
|
cursor: 'not-allowed',
|
|
115
124
|
opacity: 0.4,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {ReactNode} from 'react'
|
|
2
|
+
import type {ColorToken} from '@duro-app/tokens/keys'
|
|
3
|
+
|
|
4
|
+
export type TagVariant = 'default' | 'success' | 'warning' | 'error' | 'info'
|
|
5
|
+
|
|
6
|
+
/** Each variant's text colour, which the remove cross draws in. */
|
|
7
|
+
export const variantText = {
|
|
8
|
+
default: 'text',
|
|
9
|
+
success: 'successText',
|
|
10
|
+
warning: 'warningText',
|
|
11
|
+
error: 'errorText',
|
|
12
|
+
info: 'infoText',
|
|
13
|
+
} as const satisfies Record<TagVariant, ColorToken>
|
|
14
|
+
|
|
15
|
+
export type TagSize = 'sm' | 'md'
|
|
16
|
+
|
|
17
|
+
export interface TagProps {
|
|
18
|
+
/** Value identifier — required when used inside TagGroup */
|
|
19
|
+
value?: string
|
|
20
|
+
variant?: TagVariant
|
|
21
|
+
size?: TagSize
|
|
22
|
+
/** Show remove button. Auto-derived from TagGroup context when inside one. */
|
|
23
|
+
removable?: boolean
|
|
24
|
+
/** Called when remove button is clicked. Auto-wired to TagGroup when inside one. */
|
|
25
|
+
onRemove?: () => void
|
|
26
|
+
disabled?: boolean
|
|
27
|
+
/**
|
|
28
|
+
* Allow the label to wrap / break instead of staying on one line. Tags are
|
|
29
|
+
* `white-space: nowrap` by default (they read as pills); set this for long
|
|
30
|
+
* identifier-style values (e.g. `lldap.group.member.remove`) sitting in a
|
|
31
|
+
* constrained column, so they break within their cell instead of leaking
|
|
32
|
+
* into the next one.
|
|
33
|
+
*/
|
|
34
|
+
wrap?: boolean
|
|
35
|
+
children: ReactNode
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RemoveGlyphProps {
|
|
39
|
+
/** px, from the glyph size tokens */
|
|
40
|
+
size: number
|
|
41
|
+
/** Native only: the web inherits the tag's text colour. */
|
|
42
|
+
color: ColorToken
|
|
43
|
+
}
|
|
@@ -2,7 +2,7 @@ import type {ComponentMeta} from '../component-meta'
|
|
|
2
2
|
|
|
3
3
|
export const meta: ComponentMeta = {
|
|
4
4
|
description:
|
|
5
|
-
'Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it).',
|
|
5
|
+
'Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it). On React Native the same API draws through the portal host above every open overlay and the keyboard, as a card with a status-coloured edge, its action and its close in one row; a sideways swipe dismisses, a press holds the countdown, and so does an active screen reader (WCAG 2.2.1).',
|
|
6
6
|
whenToUse: [
|
|
7
7
|
'Confirming an action succeeded (e.g. "Grant revoked")',
|
|
8
8
|
'Reporting an action failed, with the reason',
|