@duro-app/ui 0.26.0 → 0.27.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/CheckboxGroup/CheckboxGroup.d.ts.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.d.ts.map +1 -1
- package/dist/components/ScrollArea/ScrollAreaContext.d.ts +1 -0
- package/dist/components/ScrollArea/ScrollAreaContext.d.ts.map +1 -1
- package/dist/components/ScrollArea/useScrollAreaRoot.d.ts.map +1 -1
- package/dist/components/Tag/Tag.d.ts.map +1 -1
- package/dist/components/Tag/styles.css.d.ts +8 -6
- package/dist/components/Tag/styles.css.d.ts.map +1 -1
- package/dist/components/TagGroup/styles.css.d.ts +21 -35
- package/dist/components/TagGroup/styles.css.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +1200 -1204
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/CheckboxGroup/CheckboxGroup.tsx +0 -1
- package/src/components/Field/styles.css.ts +1 -1
- package/src/components/ScrollArea/ScrollArea.tsx +27 -3
- package/src/components/ScrollArea/ScrollAreaContext.ts +1 -0
- package/src/components/ScrollArea/useScrollAreaRoot.ts +3 -1
- package/src/components/Tag/Tag.tsx +26 -24
- package/src/components/Tag/styles.css.ts +20 -15
- package/src/components/TagGroup/TagGroup.stories.tsx +5 -5
- package/src/components/TagGroup/TagGroup.tsx +1 -1
- package/src/components/TagGroup/styles.css.ts +34 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duro-app/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@duro-app/tokens": "^0.
|
|
56
|
+
"@duro-app/tokens": "^0.27.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@babel/preset-typescript": "^7.28.0",
|
|
@@ -28,11 +28,15 @@ interface ViewportProps {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function Viewport({children, maxHeight}: ViewportProps) {
|
|
31
|
-
const {viewportRef} = useScrollArea()
|
|
31
|
+
const {viewportRef, viewportId} = useScrollArea()
|
|
32
32
|
|
|
33
33
|
return (
|
|
34
34
|
<html.div
|
|
35
35
|
ref={viewportRef}
|
|
36
|
+
id={viewportId}
|
|
37
|
+
tabIndex={0}
|
|
38
|
+
role="region"
|
|
39
|
+
aria-label="Scrollable content"
|
|
36
40
|
style={[styles.viewport, maxHeight != null && styles.viewportMaxHeight(maxHeight)]}
|
|
37
41
|
>
|
|
38
42
|
{children}
|
|
@@ -65,7 +69,16 @@ interface ScrollbarProps {
|
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
function Scrollbar({orientation = 'vertical', children}: ScrollbarProps) {
|
|
68
|
-
const {
|
|
72
|
+
const {
|
|
73
|
+
viewportId,
|
|
74
|
+
scrolling,
|
|
75
|
+
scrollTop,
|
|
76
|
+
scrollLeft,
|
|
77
|
+
scrollHeight,
|
|
78
|
+
scrollWidth,
|
|
79
|
+
clientHeight,
|
|
80
|
+
clientWidth,
|
|
81
|
+
} = useScrollArea()
|
|
69
82
|
|
|
70
83
|
// Hide scrollbar when content fits
|
|
71
84
|
const hasOverflow =
|
|
@@ -73,11 +86,22 @@ function Scrollbar({orientation = 'vertical', children}: ScrollbarProps) {
|
|
|
73
86
|
|
|
74
87
|
if (!hasOverflow) return null
|
|
75
88
|
|
|
89
|
+
const isVertical = orientation === 'vertical'
|
|
90
|
+
const maxScroll = isVertical ? scrollHeight - clientHeight : scrollWidth - clientWidth
|
|
91
|
+
const scrollOffset = isVertical ? scrollTop : scrollLeft
|
|
92
|
+
const valueNow = maxScroll > 0 ? Math.round((scrollOffset / maxScroll) * 100) : 0
|
|
93
|
+
|
|
76
94
|
return (
|
|
77
95
|
<html.div
|
|
96
|
+
role="scrollbar"
|
|
97
|
+
aria-controls={viewportId}
|
|
98
|
+
aria-orientation={orientation}
|
|
99
|
+
aria-valuenow={valueNow}
|
|
100
|
+
aria-valuemin={0}
|
|
101
|
+
aria-valuemax={100}
|
|
78
102
|
style={[
|
|
79
103
|
styles.scrollbar,
|
|
80
|
-
|
|
104
|
+
isVertical ? styles.scrollbarVertical : styles.scrollbarHorizontal,
|
|
81
105
|
scrolling ? styles.scrollbarVisible : styles.scrollbarHidden,
|
|
82
106
|
]}
|
|
83
107
|
>
|
|
@@ -3,6 +3,7 @@ import {createContext, useContext} from 'react'
|
|
|
3
3
|
export interface ScrollAreaContextValue {
|
|
4
4
|
viewportRef: React.RefObject<HTMLDivElement | null>
|
|
5
5
|
contentRef: React.RefObject<HTMLDivElement | null>
|
|
6
|
+
viewportId: string
|
|
6
7
|
scrollTop: number
|
|
7
8
|
scrollLeft: number
|
|
8
9
|
scrollHeight: number
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {useState, useCallback, useRef, useEffect} from 'react'
|
|
1
|
+
import {useState, useCallback, useRef, useEffect, useId} from 'react'
|
|
2
2
|
import type {ScrollAreaContextValue} from './ScrollAreaContext'
|
|
3
3
|
|
|
4
4
|
export function useScrollAreaRoot(): ScrollAreaContextValue {
|
|
5
|
+
const viewportId = useId()
|
|
5
6
|
const viewportRef = useRef<HTMLDivElement | null>(null)
|
|
6
7
|
const contentRef = useRef<HTMLDivElement | null>(null)
|
|
7
8
|
const [scrollTop, setScrollTop] = useState(0)
|
|
@@ -61,6 +62,7 @@ export function useScrollAreaRoot(): ScrollAreaContextValue {
|
|
|
61
62
|
return {
|
|
62
63
|
viewportRef,
|
|
63
64
|
contentRef,
|
|
65
|
+
viewportId,
|
|
64
66
|
scrollTop,
|
|
65
67
|
scrollLeft,
|
|
66
68
|
scrollHeight,
|
|
@@ -91,31 +91,33 @@ export function Tag({
|
|
|
91
91
|
{children}
|
|
92
92
|
</html.span>
|
|
93
93
|
{removable && (
|
|
94
|
-
<html.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<svg
|
|
106
|
-
width={size === 'sm' ? 10 : 12}
|
|
107
|
-
height={size === 'sm' ? 10 : 12}
|
|
108
|
-
viewBox="0 0 12 12"
|
|
109
|
-
fill="none"
|
|
94
|
+
<html.span {...(isGrouped ? {role: 'gridcell' as any} : {})}>
|
|
95
|
+
<html.button
|
|
96
|
+
type="button"
|
|
97
|
+
aria-label={removeLabel}
|
|
98
|
+
disabled={disabled}
|
|
99
|
+
onClick={(e) => {
|
|
100
|
+
;(e as any).stopPropagation?.()
|
|
101
|
+
onRemove()
|
|
102
|
+
}}
|
|
103
|
+
tabIndex={-1}
|
|
104
|
+
style={[styles.removeButton, disabled && styles.removeButtonDisabled]}
|
|
110
105
|
>
|
|
111
|
-
<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
106
|
+
<svg
|
|
107
|
+
width={size === 'sm' ? 10 : 12}
|
|
108
|
+
height={size === 'sm' ? 10 : 12}
|
|
109
|
+
viewBox="0 0 12 12"
|
|
110
|
+
fill="none"
|
|
111
|
+
>
|
|
112
|
+
<path
|
|
113
|
+
d="M3 3l6 6M9 3l-6 6"
|
|
114
|
+
stroke="currentColor"
|
|
115
|
+
strokeWidth={1.5}
|
|
116
|
+
strokeLinecap="round"
|
|
117
|
+
/>
|
|
118
|
+
</svg>
|
|
119
|
+
</html.button>
|
|
120
|
+
</html.span>
|
|
119
121
|
)}
|
|
120
122
|
</>
|
|
121
123
|
)
|
|
@@ -7,13 +7,15 @@ export const styles = css.create({
|
|
|
7
7
|
base: {
|
|
8
8
|
display: 'inline-flex',
|
|
9
9
|
alignItems: 'center',
|
|
10
|
-
gap: spacing.
|
|
10
|
+
gap: spacing.sm,
|
|
11
11
|
fontFamily: typography.fontFamily,
|
|
12
|
-
fontWeight: typography.
|
|
12
|
+
fontWeight: typography.fontWeightNormal,
|
|
13
13
|
lineHeight: 1,
|
|
14
|
-
borderRadius:
|
|
14
|
+
borderRadius: 4,
|
|
15
15
|
whiteSpace: 'nowrap',
|
|
16
|
-
borderWidth:
|
|
16
|
+
borderWidth: 1,
|
|
17
|
+
borderStyle: 'solid',
|
|
18
|
+
borderColor: colors.border,
|
|
17
19
|
outlineWidth: {
|
|
18
20
|
default: 0,
|
|
19
21
|
':focus-visible': 2,
|
|
@@ -32,29 +34,32 @@ export const styles = css.create({
|
|
|
32
34
|
},
|
|
33
35
|
},
|
|
34
36
|
sizeMd: {
|
|
37
|
+
paddingTop: spacing.sm,
|
|
38
|
+
paddingBottom: spacing.sm,
|
|
39
|
+
paddingLeft: spacing.ms,
|
|
40
|
+
paddingRight: spacing.ms,
|
|
41
|
+
fontSize: typography.fontSizeSm,
|
|
42
|
+
},
|
|
43
|
+
sizeSm: {
|
|
35
44
|
paddingTop: spacing.xs,
|
|
36
45
|
paddingBottom: spacing.xs,
|
|
37
46
|
paddingLeft: spacing.sm,
|
|
38
47
|
paddingRight: spacing.sm,
|
|
39
48
|
fontSize: typography.fontSizeXs,
|
|
40
49
|
},
|
|
41
|
-
sizeSm: {
|
|
42
|
-
paddingTop: 2,
|
|
43
|
-
paddingBottom: 2,
|
|
44
|
-
paddingLeft: spacing.xs,
|
|
45
|
-
paddingRight: spacing.xs,
|
|
46
|
-
fontSize: '0.625rem',
|
|
47
|
-
},
|
|
48
50
|
// Reduce right padding when removable to keep visual balance with the button
|
|
49
51
|
removableMd: {
|
|
50
|
-
paddingRight: spacing.
|
|
52
|
+
paddingRight: spacing.sm,
|
|
51
53
|
},
|
|
52
54
|
removableSm: {
|
|
53
|
-
paddingRight:
|
|
55
|
+
paddingRight: spacing.xs,
|
|
54
56
|
},
|
|
55
57
|
default: {
|
|
56
|
-
backgroundColor:
|
|
57
|
-
|
|
58
|
+
backgroundColor: {
|
|
59
|
+
default: colors.bg,
|
|
60
|
+
':hover': colors.bgCardHover,
|
|
61
|
+
},
|
|
62
|
+
color: colors.text,
|
|
58
63
|
},
|
|
59
64
|
success: {
|
|
60
65
|
backgroundColor: colors.successBg,
|
|
@@ -44,6 +44,7 @@ function EditableExample() {
|
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
46
|
<TagGroup.Root value={tags} onValueChange={setTags}>
|
|
47
|
+
<TagGroup.Input placeholder="Add tag..." />
|
|
47
48
|
<TagGroup.List aria-label="Tags">
|
|
48
49
|
{tags.map((t) => (
|
|
49
50
|
<Tag key={t} value={t}>
|
|
@@ -51,7 +52,6 @@ function EditableExample() {
|
|
|
51
52
|
</Tag>
|
|
52
53
|
))}
|
|
53
54
|
</TagGroup.List>
|
|
54
|
-
<TagGroup.Input placeholder="Add tag..." />
|
|
55
55
|
</TagGroup.Root>
|
|
56
56
|
)
|
|
57
57
|
}
|
|
@@ -76,6 +76,7 @@ export const WithDefaultValue: Story = {
|
|
|
76
76
|
const [tags, setTags] = useState(['one', 'two'])
|
|
77
77
|
return (
|
|
78
78
|
<TagGroup.Root defaultValue={['one', 'two']} onValueChange={setTags}>
|
|
79
|
+
<TagGroup.Input placeholder="Add..." />
|
|
79
80
|
<TagGroup.List aria-label="Items">
|
|
80
81
|
{tags.map((t) => (
|
|
81
82
|
<Tag key={t} value={t}>
|
|
@@ -83,7 +84,6 @@ export const WithDefaultValue: Story = {
|
|
|
83
84
|
</Tag>
|
|
84
85
|
))}
|
|
85
86
|
</TagGroup.List>
|
|
86
|
-
<TagGroup.Input placeholder="Add..." />
|
|
87
87
|
</TagGroup.Root>
|
|
88
88
|
)
|
|
89
89
|
}
|
|
@@ -102,12 +102,14 @@ function EmailExample() {
|
|
|
102
102
|
<Stack gap="md">
|
|
103
103
|
<Field.Root required>
|
|
104
104
|
<Field.Label>Invite emails</Field.Label>
|
|
105
|
+
<Field.Description>Press Enter or comma to add each email</Field.Description>
|
|
105
106
|
<TagGroup.Root
|
|
106
107
|
name="emails"
|
|
107
108
|
value={emails}
|
|
108
109
|
onValueChange={setEmails}
|
|
109
110
|
onValidate={(v) => (v.includes('@') ? true : 'Invalid email')}
|
|
110
111
|
>
|
|
112
|
+
<TagGroup.Input placeholder="Add email..." />
|
|
111
113
|
<TagGroup.List aria-label="Emails">
|
|
112
114
|
{emails.map((e) => (
|
|
113
115
|
<Tag key={e} value={e}>
|
|
@@ -115,9 +117,7 @@ function EmailExample() {
|
|
|
115
117
|
</Tag>
|
|
116
118
|
))}
|
|
117
119
|
</TagGroup.List>
|
|
118
|
-
<TagGroup.Input placeholder="Add email..." />
|
|
119
120
|
</TagGroup.Root>
|
|
120
|
-
<Field.Description>Press Enter or comma to add each email</Field.Description>
|
|
121
121
|
</Field.Root>
|
|
122
122
|
</Stack>
|
|
123
123
|
)
|
|
@@ -148,6 +148,7 @@ function DisabledExample() {
|
|
|
148
148
|
const tags = ['react', 'vue', 'svelte']
|
|
149
149
|
return (
|
|
150
150
|
<TagGroup.Root value={tags} disabled>
|
|
151
|
+
<TagGroup.Input placeholder="Disabled..." />
|
|
151
152
|
<TagGroup.List aria-label="Frameworks">
|
|
152
153
|
{tags.map((t) => (
|
|
153
154
|
<Tag key={t} value={t}>
|
|
@@ -155,7 +156,6 @@ function DisabledExample() {
|
|
|
155
156
|
</Tag>
|
|
156
157
|
))}
|
|
157
158
|
</TagGroup.List>
|
|
158
|
-
<TagGroup.Input placeholder="Disabled..." />
|
|
159
159
|
</TagGroup.Root>
|
|
160
160
|
)
|
|
161
161
|
}
|
|
@@ -284,7 +284,7 @@ function Input({placeholder}: InputProps) {
|
|
|
284
284
|
type="text"
|
|
285
285
|
value={inputValue}
|
|
286
286
|
onChange={(e: any) => setInputValue(e.target.value)}
|
|
287
|
-
placeholder={
|
|
287
|
+
placeholder={placeholder}
|
|
288
288
|
disabled={ctx?.disabled}
|
|
289
289
|
style={styles.input}
|
|
290
290
|
/>
|
|
@@ -9,85 +9,71 @@ export const styles = css.create({
|
|
|
9
9
|
flexDirection: 'column',
|
|
10
10
|
},
|
|
11
11
|
container: {
|
|
12
|
+
display: 'flex',
|
|
13
|
+
flexDirection: 'column',
|
|
14
|
+
gap: spacing.sm,
|
|
15
|
+
},
|
|
16
|
+
containerError: {},
|
|
17
|
+
containerDisabled: {
|
|
18
|
+
opacity: 0.5,
|
|
19
|
+
cursor: 'not-allowed',
|
|
20
|
+
},
|
|
21
|
+
// Static display (no Input) — no gap needed
|
|
22
|
+
containerStatic: {
|
|
23
|
+
gap: 0,
|
|
24
|
+
},
|
|
25
|
+
list: {
|
|
12
26
|
display: 'flex',
|
|
13
27
|
flexWrap: 'wrap',
|
|
14
|
-
alignItems: 'center',
|
|
15
28
|
gap: spacing.xs,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
},
|
|
31
|
+
input: {
|
|
32
|
+
width: '100%',
|
|
20
33
|
borderWidth: 1,
|
|
21
34
|
borderStyle: 'solid',
|
|
22
35
|
borderColor: {
|
|
23
36
|
default: colors.border,
|
|
24
|
-
':focus
|
|
37
|
+
':focus': colors.accent,
|
|
25
38
|
},
|
|
26
39
|
borderRadius: radii.sm,
|
|
27
40
|
backgroundColor: colors.bg,
|
|
41
|
+
fontFamily: typography.fontFamily,
|
|
42
|
+
fontSize: typography.fontSizeSm,
|
|
43
|
+
lineHeight: typography.lineHeight,
|
|
44
|
+
color: colors.text,
|
|
45
|
+
paddingTop: spacing.sm,
|
|
46
|
+
paddingBottom: spacing.sm,
|
|
47
|
+
paddingLeft: spacing.sm,
|
|
48
|
+
paddingRight: spacing.sm,
|
|
28
49
|
minHeight: 40,
|
|
29
50
|
transitionProperty: 'border-color',
|
|
30
51
|
transitionDuration: '150ms',
|
|
31
52
|
transitionTimingFunction: 'ease',
|
|
32
53
|
outlineWidth: {
|
|
33
54
|
default: 0,
|
|
34
|
-
':focus
|
|
55
|
+
':focus': 2,
|
|
35
56
|
},
|
|
36
57
|
outlineStyle: {
|
|
37
58
|
default: 'none',
|
|
38
|
-
':focus
|
|
59
|
+
':focus': 'solid',
|
|
39
60
|
},
|
|
40
61
|
outlineColor: {
|
|
41
62
|
default: 'transparent',
|
|
42
|
-
':focus
|
|
63
|
+
':focus': colors.accent,
|
|
43
64
|
},
|
|
44
65
|
outlineOffset: {
|
|
45
66
|
default: 0,
|
|
46
|
-
':focus
|
|
67
|
+
':focus': 1,
|
|
47
68
|
},
|
|
69
|
+
boxSizing: 'border-box',
|
|
48
70
|
},
|
|
49
|
-
|
|
71
|
+
inputError: {
|
|
50
72
|
borderColor: {
|
|
51
73
|
default: colors.error,
|
|
52
|
-
':focus
|
|
74
|
+
':focus': colors.error,
|
|
53
75
|
},
|
|
54
76
|
},
|
|
55
|
-
containerDisabled: {
|
|
56
|
-
opacity: 0.5,
|
|
57
|
-
cursor: 'not-allowed',
|
|
58
|
-
},
|
|
59
|
-
// Static display (no Input) — no input-field border
|
|
60
|
-
containerStatic: {
|
|
61
|
-
borderWidth: 0,
|
|
62
|
-
paddingTop: 0,
|
|
63
|
-
paddingBottom: 0,
|
|
64
|
-
paddingLeft: 0,
|
|
65
|
-
paddingRight: 0,
|
|
66
|
-
minHeight: 0,
|
|
67
|
-
backgroundColor: 'transparent',
|
|
68
|
-
outlineWidth: 0,
|
|
69
|
-
},
|
|
70
|
-
list: {
|
|
71
|
-
display: 'flex',
|
|
72
|
-
flexWrap: 'wrap',
|
|
73
|
-
gap: spacing.xs,
|
|
74
|
-
alignItems: 'center',
|
|
75
|
-
},
|
|
76
|
-
input: {
|
|
77
|
-
flex: 1,
|
|
78
|
-
minWidth: 120,
|
|
79
|
-
borderWidth: 0,
|
|
80
|
-
backgroundColor: 'transparent',
|
|
81
|
-
fontFamily: typography.fontFamily,
|
|
82
|
-
fontSize: typography.fontSizeSm,
|
|
83
|
-
lineHeight: typography.lineHeight,
|
|
84
|
-
color: colors.text,
|
|
85
|
-
outlineWidth: 0,
|
|
86
|
-
paddingTop: spacing.xs,
|
|
87
|
-
paddingBottom: spacing.xs,
|
|
88
|
-
paddingLeft: spacing.xs,
|
|
89
|
-
paddingRight: spacing.xs,
|
|
90
|
-
},
|
|
91
77
|
liveRegion: {
|
|
92
78
|
position: 'absolute',
|
|
93
79
|
width: 1,
|