@duro-app/ui 5.13.0 → 5.15.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/README.md +49 -0
- package/dist/{Table-Bms2ftt3.js → Table-DnaoH-h5.js} +747 -635
- package/dist/Table-DnaoH-h5.js.map +1 -0
- package/dist/components/Button/Button.d.ts.map +1 -1
- package/dist/components/ColorMode/ColorMode.d.ts +2 -1
- package/dist/components/ColorMode/ColorMode.d.ts.map +1 -1
- package/dist/components/ColorMode/storage.d.ts +4 -0
- package/dist/components/ColorMode/storage.d.ts.map +1 -0
- package/dist/components/ColorMode/styles.css.d.ts +4 -0
- package/dist/components/ColorMode/styles.css.d.ts.map +1 -1
- package/dist/components/ColorMode/useSystemScheme.d.ts +7 -0
- package/dist/components/ColorMode/useSystemScheme.d.ts.map +1 -0
- package/dist/components/Combobox/Combobox.d.ts +7 -2
- package/dist/components/Combobox/Combobox.d.ts.map +1 -1
- package/dist/components/Combobox/comboboxState.d.ts +29 -0
- package/dist/components/Combobox/comboboxState.d.ts.map +1 -0
- package/dist/components/Combobox/native.css.d.ts +27 -0
- package/dist/components/Combobox/native.css.d.ts.map +1 -0
- package/dist/components/Combobox/useComboboxRoot.d.ts +6 -10
- package/dist/components/Combobox/useComboboxRoot.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.d.ts +5 -2
- package/dist/components/Drawer/Drawer.d.ts.map +1 -1
- package/dist/components/Drawer/DrawerSnap.stories.d.ts +2 -0
- package/dist/components/Drawer/DrawerSnap.stories.d.ts.map +1 -1
- package/dist/components/Drawer/native.css.d.ts +4 -0
- package/dist/components/Drawer/native.css.d.ts.map +1 -1
- package/dist/components/Drawer/styles.css.d.ts +4 -0
- package/dist/components/Drawer/styles.css.d.ts.map +1 -1
- package/dist/components/Form/FormSubmitContext.d.ts +8 -0
- package/dist/components/Form/FormSubmitContext.d.ts.map +1 -0
- package/dist/components/Listbox/native.css.d.ts +84 -0
- package/dist/components/Listbox/native.css.d.ts.map +1 -0
- package/dist/components/QrCode/QrCode.d.ts +8 -0
- package/dist/components/QrCode/QrCode.d.ts.map +1 -0
- package/dist/components/QrCode/QrCode.meta.d.ts +3 -0
- package/dist/components/QrCode/QrCode.meta.d.ts.map +1 -0
- package/dist/components/QrCode/QrCode.stories.d.ts +8 -0
- package/dist/components/QrCode/QrCode.stories.d.ts.map +1 -0
- package/dist/components/QrCode/geometry.d.ts +14 -0
- package/dist/components/QrCode/geometry.d.ts.map +1 -0
- package/dist/components/QrCode/styles.css.d.ts +9 -0
- package/dist/components/QrCode/styles.css.d.ts.map +1 -0
- package/dist/components/QrCode/types.d.ts +7 -0
- package/dist/components/QrCode/types.d.ts.map +1 -0
- package/dist/components/Select/Select.d.ts +9 -2
- package/dist/components/Select/Select.d.ts.map +1 -1
- package/dist/components/Select/Select.stories.d.ts +11 -0
- package/dist/components/Select/Select.stories.d.ts.map +1 -1
- package/dist/components/Select/native.css.d.ts +11 -0
- package/dist/components/Select/native.css.d.ts.map +1 -0
- package/dist/components/Select/selectState.d.ts +21 -0
- package/dist/components/Select/selectState.d.ts.map +1 -0
- package/dist/components/Select/styles.css.d.ts +10 -0
- package/dist/components/Select/styles.css.d.ts.map +1 -1
- package/dist/components/Select/useSelectRoot.d.ts +6 -8
- package/dist/components/Select/useSelectRoot.d.ts.map +1 -1
- package/dist/components/ThemeProvider/ThemeOverrides.stories.d.ts +13 -0
- package/dist/components/ThemeProvider/ThemeOverrides.stories.d.ts.map +1 -0
- package/dist/components/ThemeProvider/ThemeProvider.d.ts +9 -2
- package/dist/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
- package/dist/components/ThemeProvider/overrideRule.d.ts +7 -0
- package/dist/components/ThemeProvider/overrideRule.d.ts.map +1 -0
- package/dist/components/ThemeProvider/overrides.d.ts +16 -0
- package/dist/components/ThemeProvider/overrides.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6230 -4656
- package/dist/index.js.map +1 -1
- package/dist/shared/textOf.d.ts +7 -0
- package/dist/shared/textOf.d.ts.map +1 -0
- package/dist/table.js +1 -1
- package/package.json +5 -2
- package/src/components/Button/Button.tsx +12 -1
- package/src/components/ColorMode/ColorMode.meta.ts +1 -1
- package/src/components/ColorMode/ColorMode.tsx +16 -37
- package/src/components/ColorMode/storage.native.ts +8 -0
- package/src/components/ColorMode/storage.ts +19 -0
- package/src/components/ColorMode/styles.css.ts +6 -0
- package/src/components/ColorMode/useSystemScheme.native.ts +6 -0
- package/src/components/ColorMode/useSystemScheme.ts +19 -0
- package/src/components/Combobox/Combobox.meta.ts +1 -1
- package/src/components/Combobox/Combobox.native.tsx +295 -0
- package/src/components/Combobox/Combobox.tsx +14 -8
- package/src/components/Combobox/comboboxState.ts +107 -0
- package/src/components/Combobox/native.css.ts +33 -0
- package/src/components/Combobox/useComboboxRoot.ts +17 -91
- package/src/components/Drawer/Drawer.meta.ts +1 -1
- package/src/components/Drawer/Drawer.native.tsx +29 -7
- package/src/components/Drawer/Drawer.tsx +11 -2
- package/src/components/Drawer/DrawerSnap.stories.tsx +22 -2
- package/src/components/Drawer/native.css.ts +4 -0
- package/src/components/Drawer/styles.css.ts +4 -0
- package/src/components/Form/Form.meta.ts +1 -1
- package/src/components/Form/Form.native.tsx +120 -0
- package/src/components/Form/FormSubmitContext.ts +12 -0
- package/src/components/Listbox/Listbox.meta.ts +1 -1
- package/src/components/Listbox/Listbox.native.tsx +153 -0
- package/src/components/Listbox/native.css.ts +91 -0
- package/src/components/QrCode/QrCode.meta.ts +18 -0
- package/src/components/QrCode/QrCode.native.tsx +31 -0
- package/src/components/QrCode/QrCode.stories.tsx +44 -0
- package/src/components/QrCode/QrCode.tsx +30 -0
- package/src/components/QrCode/geometry.ts +34 -0
- package/src/components/QrCode/styles.css.ts +12 -0
- package/src/components/QrCode/types.ts +6 -0
- package/src/components/ScrollArea/ScrollArea.meta.ts +1 -1
- package/src/components/ScrollArea/ScrollArea.native.tsx +61 -0
- package/src/components/Select/Select.meta.ts +1 -1
- package/src/components/Select/Select.native.tsx +224 -0
- package/src/components/Select/Select.stories.tsx +49 -1
- package/src/components/Select/Select.tsx +20 -3
- package/src/components/Select/native.css.ts +13 -0
- package/src/components/Select/selectState.ts +36 -0
- package/src/components/Select/styles.css.ts +11 -0
- package/src/components/Select/useSelectRoot.ts +9 -30
- package/src/components/ThemeProvider/ThemeOverrides.stories.tsx +113 -0
- package/src/components/ThemeProvider/ThemeProvider.meta.ts +1 -1
- package/src/components/ThemeProvider/ThemeProvider.native.tsx +36 -7
- package/src/components/ThemeProvider/ThemeProvider.tsx +42 -6
- package/src/components/ThemeProvider/overrideRule.ts +39 -0
- package/src/components/ThemeProvider/overrides.ts +27 -0
- package/src/form.native.ts +7 -0
- package/src/index.native.ts +16 -0
- package/src/index.ts +2 -0
- package/src/shared/focusInput.native.ts +18 -0
- package/src/shared/textOf.ts +13 -0
- package/dist/Table-Bms2ftt3.js.map +0 -1
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
import {createContext, type ReactNode, useContext, useState} from 'react'
|
|
1
|
+
import {createContext, type ReactNode, useContext, useId, useMemo, useState} from 'react'
|
|
2
2
|
import {css, html} from 'react-strict-dom'
|
|
3
|
+
import type {ColorToken} from '@duro-app/tokens/keys'
|
|
3
4
|
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
4
5
|
import {lightTheme, lightShadows} from '@duro-app/tokens/themes/light.css'
|
|
5
6
|
import {highContrastTheme, highContrastShadows} from '@duro-app/tokens/themes/high-contrast.css'
|
|
6
7
|
import {layers} from '@duro-app/tokens/tokens/layers.css'
|
|
8
|
+
import {mergeOverrides, OverridesContext, type ColorOverrides} from './overrides'
|
|
9
|
+
import {overrideRule} from './overrideRule'
|
|
7
10
|
|
|
8
11
|
export type ThemeName = 'dark' | 'light' | 'high-contrast'
|
|
12
|
+
export type {ColorOverrides}
|
|
9
13
|
|
|
10
14
|
interface ThemeProviderProps {
|
|
11
15
|
theme?: ThemeName
|
|
16
|
+
/**
|
|
17
|
+
* Colours that replace the theme's own, by token key (`{accent: '#ff5a1f'}`).
|
|
18
|
+
* They update in place, without a re-mount. A nested provider on the same
|
|
19
|
+
* theme keeps these and lays its own over them.
|
|
20
|
+
*/
|
|
21
|
+
overrides?: ColorOverrides
|
|
12
22
|
children: ReactNode
|
|
13
23
|
}
|
|
14
24
|
|
|
@@ -49,14 +59,40 @@ export function usePortalMount(): HTMLElement | null {
|
|
|
49
59
|
return useContext(PortalMountContext)
|
|
50
60
|
}
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
62
|
+
/** `var(--bg-x1y2)` → `--bg-x1y2`: the compiled variable a token key names. */
|
|
63
|
+
function variableOf(key: ColorToken): string | null {
|
|
64
|
+
const ref = String(colors[key] ?? '')
|
|
65
|
+
const match = /^var\((--[\w-]+)\)$/.exec(ref)
|
|
66
|
+
return match ? match[1] : null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function ThemeProvider({theme = 'dark', overrides, children}: ThemeProviderProps) {
|
|
70
|
+
const themeStyles = themeMap[theme]
|
|
71
|
+
const style = [themeStyles?.[0], themeStyles?.[1], styles.root] as DivStyle
|
|
55
72
|
const [mount, setMount] = useState<HTMLElement | null>(null)
|
|
73
|
+
const scope = useId()
|
|
74
|
+
const outer = useContext(OverridesContext)
|
|
75
|
+
const effective = useMemo(
|
|
76
|
+
() => mergeOverrides(outer, theme, overrides),
|
|
77
|
+
[outer, theme, overrides],
|
|
78
|
+
)
|
|
79
|
+
const rule = useMemo(
|
|
80
|
+
() =>
|
|
81
|
+
Object.keys(effective.colors).length > 0
|
|
82
|
+
? overrideRule(scope, effective.colors, variableOf)
|
|
83
|
+
: null,
|
|
84
|
+
[scope, effective],
|
|
85
|
+
)
|
|
56
86
|
|
|
57
87
|
return (
|
|
58
|
-
<html.div data-theme={theme} style={
|
|
59
|
-
<
|
|
88
|
+
<html.div data-theme={theme} data-duro-theme={rule ? scope : undefined} style={style}>
|
|
89
|
+
{/* A plain, updating <style> (react-strict-dom has no html.style): no
|
|
90
|
+
href or precedence, which would make React hoist it once and ignore
|
|
91
|
+
later changes. */}
|
|
92
|
+
{rule ? <style>{rule}</style> : null}
|
|
93
|
+
<OverridesContext.Provider value={effective}>
|
|
94
|
+
<PortalMountContext.Provider value={mount}>{children}</PortalMountContext.Provider>
|
|
95
|
+
</OverridesContext.Provider>
|
|
60
96
|
<html.div ref={setMount} data-portal-mount style={styles.portalMount} />
|
|
61
97
|
</html.div>
|
|
62
98
|
)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The web ThemeProvider's override rule, pure: it runs the same on the server
|
|
3
|
+
* and in the browser (no `CSS` global), so a server-rendered rule hydrates.
|
|
4
|
+
*/
|
|
5
|
+
import type {ColorToken} from '@duro-app/tokens/keys'
|
|
6
|
+
import type {ColorOverrides} from './overrides'
|
|
7
|
+
|
|
8
|
+
/** A value that is a colour and nothing else, so it cannot leave its rule. */
|
|
9
|
+
export function isSafeColor(value: string): boolean {
|
|
10
|
+
// The same answer on the server and in the browser (no CSS.supports), so a
|
|
11
|
+
// server-rendered rule hydrates unchanged: a hex, a colour function, or a
|
|
12
|
+
// keyword, with nothing that could close the declaration or the rule.
|
|
13
|
+
if (/[;{}<>"'\\]/.test(value)) return false
|
|
14
|
+
return (
|
|
15
|
+
/^#[0-9a-f]{3,8}$/i.test(value) ||
|
|
16
|
+
/^(rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\([^()]*\)$/i.test(value) ||
|
|
17
|
+
/^[a-z]+$/i.test(value)
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The scoped rule for a provider's overrides, or null when it has none. */
|
|
22
|
+
export function overrideRule(
|
|
23
|
+
scope: string,
|
|
24
|
+
overrides: ColorOverrides,
|
|
25
|
+
variableOf: (key: ColorToken) => string | null,
|
|
26
|
+
): string | null {
|
|
27
|
+
const declarations = Object.entries(overrides).flatMap(([key, value]) => {
|
|
28
|
+
const variable = variableOf(key as ColorToken)
|
|
29
|
+
return variable && typeof value === 'string' && isSafeColor(value)
|
|
30
|
+
? [`${variable}: ${value};`]
|
|
31
|
+
: []
|
|
32
|
+
})
|
|
33
|
+
if (declarations.length === 0) return null
|
|
34
|
+
// Doubled, so it outranks the theme classes (one class each) wherever the
|
|
35
|
+
// stylesheets land.
|
|
36
|
+
// useId output holds neither a quote nor a backslash: safe inside quotes.
|
|
37
|
+
const selector = `[data-duro-theme="${scope}"]`
|
|
38
|
+
return `${selector}${selector} { ${declarations.join(' ')} }`
|
|
39
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {createContext} from 'react'
|
|
2
|
+
import type {ColorToken} from '@duro-app/tokens/keys'
|
|
3
|
+
|
|
4
|
+
/** Colours that replace a theme's own, by token key. */
|
|
5
|
+
export type ColorOverrides = Partial<Record<ColorToken, string>>
|
|
6
|
+
|
|
7
|
+
/** The overrides in force at a provider, and the theme they were made for. */
|
|
8
|
+
export interface EffectiveOverrides {
|
|
9
|
+
theme: string | null
|
|
10
|
+
colors: ColorOverrides
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const OverridesContext = createContext<EffectiveOverrides>({theme: null, colors: {}})
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* One rule on both platforms (plan N0, slice C): a provider on the same theme
|
|
17
|
+
* as the one above it keeps that one's overrides and lays its own over them;
|
|
18
|
+
* a provider on another theme starts from its own theme's colours.
|
|
19
|
+
*/
|
|
20
|
+
export function mergeOverrides(
|
|
21
|
+
outer: EffectiveOverrides,
|
|
22
|
+
theme: string,
|
|
23
|
+
own: ColorOverrides | undefined,
|
|
24
|
+
): EffectiveOverrides {
|
|
25
|
+
const inherited = outer.theme === theme ? outer.colors : {}
|
|
26
|
+
return {theme, colors: own ? {...inherited, ...own} : inherited}
|
|
27
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@duro-app/ui/form` on React Native — resolved via the `react-native` export
|
|
3
|
+
* condition. Form validates the same Effect Schema; a `type="submit"` Button
|
|
4
|
+
* inside it submits through FormSubmitContext, since native has no DOM
|
|
5
|
+
* submit event. See `form.ts` for why Form is a subpath.
|
|
6
|
+
*/
|
|
7
|
+
export {Form, type FormProps} from './components/Form/Form'
|
package/src/index.native.ts
CHANGED
|
@@ -36,6 +36,7 @@ export {Callout, type CalloutVariant} from './components/Callout/Callout'
|
|
|
36
36
|
export {EmptyState} from './components/EmptyState/EmptyState'
|
|
37
37
|
export {Icon, type IconName} from './components/Icon'
|
|
38
38
|
export {Meter, type MeterProps, type MeterTone} from './components/Meter/Meter'
|
|
39
|
+
export {QrCode, type QrCodeProps} from './components/QrCode/QrCode'
|
|
39
40
|
export {Spinner, type SpinnerSize} from './components/Spinner/Spinner'
|
|
40
41
|
export {
|
|
41
42
|
StatusIcon,
|
|
@@ -51,6 +52,13 @@ export {Tag, type TagVariant} from './components/Tag/Tag'
|
|
|
51
52
|
export {SideNav} from './components/SideNav/SideNav'
|
|
52
53
|
export {Tabs} from './components/Tabs/Tabs'
|
|
53
54
|
export {Tree} from './components/Tree/Tree'
|
|
55
|
+
// 5.14: pickers open a bottom sheet (a Drawer at the medium and large detents)
|
|
56
|
+
export {Select} from './components/Select/Select'
|
|
57
|
+
export {Combobox} from './components/Combobox/Combobox'
|
|
58
|
+
export {Listbox} from './components/Listbox/Listbox'
|
|
59
|
+
// The platform's own scroll indicator: Scrollbar and Thumb draw nothing.
|
|
60
|
+
export {ScrollArea} from './components/ScrollArea/ScrollArea'
|
|
61
|
+
export type {ListboxAnchorProps, ListboxMaxHeight} from './components/Listbox/Listbox'
|
|
54
62
|
// Always stacked on native: a phone is below every collapseBelow.
|
|
55
63
|
export {SplitPane} from './components/SplitPane/SplitPane'
|
|
56
64
|
// Always the phone shape on native: the bar on top, the rail in a left Drawer.
|
|
@@ -97,9 +105,17 @@ export type {GridTrack} from './shared/length'
|
|
|
97
105
|
// no display:contents / portal mount; applies the theme to a flex container).
|
|
98
106
|
export {
|
|
99
107
|
ThemeProvider,
|
|
108
|
+
type ColorOverrides,
|
|
100
109
|
usePortalMount,
|
|
101
110
|
useResolvedColors,
|
|
102
111
|
type ThemeName,
|
|
103
112
|
} from './components/ThemeProvider/ThemeProvider'
|
|
113
|
+
// 'system' follows RN Appearance; persistence is the app's (preference + onPreferenceChange).
|
|
114
|
+
export {
|
|
115
|
+
ColorModeProvider,
|
|
116
|
+
ColorModeToggle,
|
|
117
|
+
useColorMode,
|
|
118
|
+
type ColorModePreference,
|
|
119
|
+
} from './components/ColorMode'
|
|
104
120
|
// Where toasts draw on native: mount one inside a natively presented route.
|
|
105
121
|
export {PortalHost} from './shared/portal/PortalHost'
|
package/src/index.ts
CHANGED
|
@@ -83,6 +83,7 @@ export {TextLink, type TextLinkVariant} from './components/TextLink/TextLink'
|
|
|
83
83
|
export type {ListboxAnchorProps, ListboxMaxHeight} from './components/Listbox/Listbox'
|
|
84
84
|
export {Select} from './components/Select/Select'
|
|
85
85
|
export {SideNav} from './components/SideNav/SideNav'
|
|
86
|
+
export {QrCode, type QrCodeProps} from './components/QrCode/QrCode'
|
|
86
87
|
export {Spinner, type SpinnerSize} from './components/Spinner/Spinner'
|
|
87
88
|
export {
|
|
88
89
|
ToastProvider,
|
|
@@ -125,6 +126,7 @@ export {VirtualList, type VirtualListProps} from './components/VirtualList/Virtu
|
|
|
125
126
|
export {Textarea, type TextareaVariant} from './components/Textarea/Textarea'
|
|
126
127
|
export {
|
|
127
128
|
ThemeProvider,
|
|
129
|
+
type ColorOverrides,
|
|
128
130
|
usePortalMount,
|
|
129
131
|
type ThemeName,
|
|
130
132
|
} from './components/ThemeProvider/ThemeProvider'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface StrictInputRef {
|
|
2
|
+
getNativeRef?: () => {focus?: () => void} | null
|
|
3
|
+
focus?: () => void
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Focus an html.input on native. react-strict-dom 0.0.55 hands out a copy of
|
|
8
|
+
* the TextInput instance, whose `focus()` does nothing (#506); the copy keeps
|
|
9
|
+
* TextInput's own `getNativeRef()`, which returns the real host.
|
|
10
|
+
*
|
|
11
|
+
* holds-until: react-strict-dom releases #515 (focus() on the ref itself).
|
|
12
|
+
*/
|
|
13
|
+
export function focusInput(input: unknown): void {
|
|
14
|
+
const ref = input as StrictInputRef | null
|
|
15
|
+
const host = ref?.getNativeRef?.()
|
|
16
|
+
if (host?.focus) host.focus()
|
|
17
|
+
else ref?.focus?.()
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {isValidElement, type ReactNode} from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The text a React node would render, read from its props: a native picker's
|
|
5
|
+
* option label, where there is no DOM `textContent` to read it from.
|
|
6
|
+
*/
|
|
7
|
+
export function textOf(node: ReactNode): string {
|
|
8
|
+
if (node == null || typeof node === 'boolean') return ''
|
|
9
|
+
if (typeof node === 'string' || typeof node === 'number') return String(node)
|
|
10
|
+
if (Array.isArray(node)) return node.map(textOf).join('')
|
|
11
|
+
if (isValidElement<{children?: ReactNode}>(node)) return textOf(node.props.children)
|
|
12
|
+
return ''
|
|
13
|
+
}
|