@duro-app/ui 0.17.1 → 0.18.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/components/ActionBar/ActionBar.d.ts +1 -1
- package/dist/components/ActionBar/ActionBar.d.ts.map +1 -1
- package/dist/components/ActionBar/ActionBarProvider.d.ts +12 -0
- package/dist/components/ActionBar/ActionBarProvider.d.ts.map +1 -0
- package/dist/components/ThemeProvider/ThemeProvider.d.ts +0 -2
- package/dist/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1381 -1312
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/ActionBar/ActionBar.tsx +28 -12
- package/src/components/ActionBar/ActionBarProvider.tsx +100 -0
- package/src/components/ThemeProvider/ThemeProvider.tsx +2 -19
- package/src/index.ts +2 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duro-app/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@duro-app/tokens": "^0.
|
|
55
|
+
"@duro-app/tokens": "^0.18.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@babel/preset-typescript": "^7.28.0",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import {createPortal} from 'react-dom'
|
|
1
|
+
import {useEffect, useCallback, type ReactNode} from 'react'
|
|
3
2
|
import {html} from 'react-strict-dom'
|
|
4
|
-
import {
|
|
3
|
+
import {useActionBarStack} from './ActionBarProvider'
|
|
5
4
|
import {styles} from './styles.css'
|
|
6
5
|
|
|
7
6
|
export interface ActionBarProps {
|
|
@@ -19,7 +18,7 @@ export interface ActionBarProps {
|
|
|
19
18
|
children: ReactNode
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
function
|
|
21
|
+
function ActionBarContent({
|
|
23
22
|
selectedItemCount,
|
|
24
23
|
selectedLabel,
|
|
25
24
|
isEmphasized = false,
|
|
@@ -27,14 +26,10 @@ function ActionBarInner({
|
|
|
27
26
|
dismissible = true,
|
|
28
27
|
children,
|
|
29
28
|
}: ActionBarProps) {
|
|
30
|
-
const container = useOverlayContainer()
|
|
31
|
-
|
|
32
|
-
if (selectedItemCount === 0) return null
|
|
33
|
-
|
|
34
29
|
const countStr = selectedItemCount === 'all' ? 'All' : `${selectedItemCount}`
|
|
35
30
|
const label = selectedLabel ? selectedLabel(countStr) : `${countStr} selected`
|
|
36
31
|
|
|
37
|
-
|
|
32
|
+
return (
|
|
38
33
|
<html.div
|
|
39
34
|
role="toolbar"
|
|
40
35
|
aria-label={label}
|
|
@@ -64,12 +59,33 @@ function ActionBarInner({
|
|
|
64
59
|
)}
|
|
65
60
|
</html.div>
|
|
66
61
|
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ActionBarInner(props: ActionBarProps) {
|
|
65
|
+
const {selectedItemCount} = props
|
|
66
|
+
const stack = useActionBarStack()
|
|
67
|
+
const isActive = selectedItemCount !== 0
|
|
68
|
+
|
|
69
|
+
const renderBar = useCallback(() => <ActionBarContent {...props} />, [props])
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!stack.managed) return
|
|
73
|
+
if (isActive) {
|
|
74
|
+
stack.register(renderBar)
|
|
75
|
+
} else {
|
|
76
|
+
stack.unregister()
|
|
77
|
+
}
|
|
78
|
+
return () => stack.unregister()
|
|
79
|
+
}, [stack, isActive, renderBar])
|
|
67
80
|
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
// If no provider, fall back to standalone rendering
|
|
82
|
+
if (!stack.managed) {
|
|
83
|
+
if (!isActive) return null
|
|
84
|
+
return <ActionBarContent {...props} />
|
|
70
85
|
}
|
|
71
86
|
|
|
72
|
-
|
|
87
|
+
// Managed by provider — rendering happens there
|
|
88
|
+
return null
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
export const ActionBar = ActionBarInner
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useId,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useSyncExternalStore,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from 'react'
|
|
11
|
+
|
|
12
|
+
interface ActionBarEntry {
|
|
13
|
+
id: string
|
|
14
|
+
render: () => ReactNode
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ActionBarStore {
|
|
18
|
+
subscribe: (cb: () => void) => () => void
|
|
19
|
+
getSnapshot: () => readonly ActionBarEntry[]
|
|
20
|
+
push: (id: string, render: () => ReactNode) => void
|
|
21
|
+
update: (id: string, render: () => ReactNode) => void
|
|
22
|
+
remove: (id: string) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createActionBarStore(): ActionBarStore {
|
|
26
|
+
let entries: ActionBarEntry[] = []
|
|
27
|
+
const listeners = new Set<() => void>()
|
|
28
|
+
|
|
29
|
+
const notify = () => listeners.forEach((l) => l())
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
subscribe: (cb) => {
|
|
33
|
+
listeners.add(cb)
|
|
34
|
+
return () => listeners.delete(cb)
|
|
35
|
+
},
|
|
36
|
+
getSnapshot: () => entries,
|
|
37
|
+
push: (id, render) => {
|
|
38
|
+
entries = [...entries, {id, render}]
|
|
39
|
+
notify()
|
|
40
|
+
},
|
|
41
|
+
update: (id, render) => {
|
|
42
|
+
entries = entries.map((e) => (e.id === id ? {id, render} : e))
|
|
43
|
+
notify()
|
|
44
|
+
},
|
|
45
|
+
remove: (id) => {
|
|
46
|
+
entries = entries.filter((e) => e.id !== id)
|
|
47
|
+
notify()
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ActionBarContext = createContext<ActionBarStore | null>(null)
|
|
53
|
+
|
|
54
|
+
export function ActionBarProvider({children}: {children: ReactNode}) {
|
|
55
|
+
const storeRef = useRef<ActionBarStore>(null)
|
|
56
|
+
if (storeRef.current === null) {
|
|
57
|
+
storeRef.current = createActionBarStore()
|
|
58
|
+
}
|
|
59
|
+
const store = storeRef.current
|
|
60
|
+
|
|
61
|
+
const entries = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)
|
|
62
|
+
const topEntry = entries.length > 0 ? entries[entries.length - 1] : null
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<ActionBarContext.Provider value={store}>
|
|
66
|
+
{children}
|
|
67
|
+
{topEntry?.render()}
|
|
68
|
+
</ActionBarContext.Provider>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function useActionBarStack() {
|
|
73
|
+
const store = useContext(ActionBarContext)
|
|
74
|
+
const id = useId()
|
|
75
|
+
|
|
76
|
+
const register = useCallback(
|
|
77
|
+
(render: () => ReactNode) => {
|
|
78
|
+
if (!store) return
|
|
79
|
+
const entries = store.getSnapshot()
|
|
80
|
+
if (entries.some((e) => e.id === id)) {
|
|
81
|
+
store.update(id, render)
|
|
82
|
+
} else {
|
|
83
|
+
store.push(id, render)
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
[store, id],
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const unregister = useCallback(() => {
|
|
90
|
+
store?.remove(id)
|
|
91
|
+
}, [store, id])
|
|
92
|
+
|
|
93
|
+
const isTop = useMemo(() => {
|
|
94
|
+
if (!store) return false
|
|
95
|
+
const entries = store.getSnapshot()
|
|
96
|
+
return entries.length > 0 && entries[entries.length - 1].id === id
|
|
97
|
+
}, [store, id])
|
|
98
|
+
|
|
99
|
+
return {id, register, unregister, managed: store !== null, isTop}
|
|
100
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {type ReactNode
|
|
1
|
+
import {type ReactNode} from 'react'
|
|
2
2
|
import {css, html} from 'react-strict-dom'
|
|
3
3
|
import {colors} from '@duro-app/tokens/tokens/colors.css'
|
|
4
4
|
import {lightTheme, lightShadows} from '@duro-app/tokens/themes/light.css'
|
|
@@ -27,26 +27,9 @@ const styles = css.create({
|
|
|
27
27
|
// concrete VarGroup lacks the generic index signature. This is a known typing gap.
|
|
28
28
|
type DivStyle = Parameters<typeof html.div>[0]['style']
|
|
29
29
|
|
|
30
|
-
/** Context providing a portal target inside the themed tree. */
|
|
31
|
-
const OverlayContainerContext = createContext<React.RefObject<HTMLDivElement | null> | null>(null)
|
|
32
|
-
|
|
33
|
-
/** Returns the overlay container element for portals (inside the ThemeProvider). */
|
|
34
|
-
export function useOverlayContainer(): HTMLElement {
|
|
35
|
-
const ref = useContext(OverlayContainerContext)
|
|
36
|
-
return ref?.current ?? (typeof document !== 'undefined' ? document.body : null!)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
30
|
export function ThemeProvider({theme = 'dark', children}: ThemeProviderProps) {
|
|
40
|
-
const overlayRef = useRef<HTMLDivElement>(null)
|
|
41
31
|
const overrides = themeMap[theme]
|
|
42
32
|
const themeStyles = [overrides?.[0], overrides?.[1], styles.root] as DivStyle
|
|
43
33
|
|
|
44
|
-
return
|
|
45
|
-
<html.div style={themeStyles}>
|
|
46
|
-
<OverlayContainerContext.Provider value={overlayRef}>
|
|
47
|
-
{children}
|
|
48
|
-
<html.div ref={overlayRef} />
|
|
49
|
-
</OverlayContainerContext.Provider>
|
|
50
|
-
</html.div>
|
|
51
|
-
)
|
|
34
|
+
return <html.div style={themeStyles}>{children}</html.div>
|
|
52
35
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import './strict.css'
|
|
|
3
3
|
|
|
4
4
|
// Components
|
|
5
5
|
export {ActionBar, type ActionBarProps} from './components/ActionBar/ActionBar'
|
|
6
|
+
export {ActionBarProvider} from './components/ActionBar/ActionBarProvider'
|
|
6
7
|
export {Alert, type AlertVariant} from './components/Alert/Alert'
|
|
7
8
|
export {Icon, type IconName} from './components/Icon'
|
|
8
9
|
export {Badge, type BadgeVariant, type BadgeSize} from './components/Badge/Badge'
|
|
@@ -35,11 +36,7 @@ export {
|
|
|
35
36
|
export {Table, type TableVariant, type TableSize} from './components/Table/Table'
|
|
36
37
|
export {Tabs} from './components/Tabs/Tabs'
|
|
37
38
|
export {Textarea, type TextareaVariant} from './components/Textarea/Textarea'
|
|
38
|
-
export {
|
|
39
|
-
ThemeProvider,
|
|
40
|
-
useOverlayContainer,
|
|
41
|
-
type ThemeName,
|
|
42
|
-
} from './components/ThemeProvider/ThemeProvider'
|
|
39
|
+
export {ThemeProvider, type ThemeName} from './components/ThemeProvider/ThemeProvider'
|
|
43
40
|
export {Text, type TextVariant, type TextColor} from './components/Text/Text'
|
|
44
41
|
export {Toggle, type ToggleSize} from './components/Toggle/Toggle'
|
|
45
42
|
export {ToggleGroup} from './components/ToggleGroup/ToggleGroup'
|