@duro-app/ui 0.42.0 → 0.44.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/Callout/Callout.d.ts +7 -1
- package/dist/components/Callout/Callout.d.ts.map +1 -1
- package/dist/components/Callout/Callout.stories.d.ts +1 -1
- package/dist/components/Callout/Callout.stories.d.ts.map +1 -1
- package/dist/components/Callout/styles.css.d.ts +14 -5
- package/dist/components/Callout/styles.css.d.ts.map +1 -1
- package/dist/components/Table/Table.stories.d.ts +1 -0
- package/dist/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/components/Table/styles.css.d.ts +9 -1
- package/dist/components/Table/styles.css.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +1299 -1273
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Callout/Callout.meta.ts +1 -1
- package/src/components/Callout/Callout.stories.tsx +24 -11
- package/src/components/Callout/Callout.tsx +16 -3
- package/src/components/Callout/styles.css.ts +17 -5
- package/src/components/Table/Table.stories.tsx +57 -0
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Table/styles.css.ts +24 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duro-app/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@tanstack/react-virtual": "^3.14.6",
|
|
58
|
-
"@duro-app/tokens": "^0.
|
|
58
|
+
"@duro-app/tokens": "^0.44.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@babel/preset-typescript": "^7.28.0",
|
|
@@ -2,7 +2,7 @@ import type {ComponentMeta} from '../component-meta'
|
|
|
2
2
|
|
|
3
3
|
export const meta: ComponentMeta = {
|
|
4
4
|
description:
|
|
5
|
-
'Block-level informational message with icon and colored background. More prominent than Alert. Use for important notices, onboarding tips, or contextual guidance that should stand out.',
|
|
5
|
+
'Block-level informational message with icon and colored background. More prominent than Alert. Use for important notices, onboarding tips, or contextual guidance that should stand out. `align` controls icon↔message vertical alignment: `center` (default) for short messages, `start` for long multi-paragraph content.',
|
|
6
6
|
whenToUse: [
|
|
7
7
|
'Important notices that need visual prominence',
|
|
8
8
|
'Onboarding tips or contextual guidance',
|
|
@@ -11,15 +11,21 @@ const meta: Meta<typeof Callout> = {
|
|
|
11
11
|
control: 'select',
|
|
12
12
|
options: ['error', 'success', 'warning', 'info'],
|
|
13
13
|
},
|
|
14
|
+
align: {
|
|
15
|
+
control: 'inline-radio',
|
|
16
|
+
options: ['center', 'start'],
|
|
17
|
+
},
|
|
14
18
|
},
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
export default meta
|
|
18
22
|
type Story = StoryObj<typeof Callout>
|
|
19
23
|
|
|
24
|
+
// Long, multi-paragraph messages align the icon to the first line.
|
|
20
25
|
export const Info: Story = {
|
|
21
26
|
args: {
|
|
22
27
|
variant: 'info',
|
|
28
|
+
align: 'start',
|
|
23
29
|
children:
|
|
24
30
|
'A new version of the application is available. Please save your work and refresh the page to get the latest features and security updates.',
|
|
25
31
|
},
|
|
@@ -28,6 +34,7 @@ export const Info: Story = {
|
|
|
28
34
|
export const Warning: Story = {
|
|
29
35
|
args: {
|
|
30
36
|
variant: 'warning',
|
|
37
|
+
align: 'start',
|
|
31
38
|
children:
|
|
32
39
|
"It looks like your certificate isn't installed yet. Install the .p12 file from your email, then click the button below. After installing the certificate, you may need to close and reopen your browser for it to be recognized.",
|
|
33
40
|
},
|
|
@@ -36,6 +43,7 @@ export const Warning: Story = {
|
|
|
36
43
|
export const Success: Story = {
|
|
37
44
|
args: {
|
|
38
45
|
variant: 'success',
|
|
46
|
+
align: 'start',
|
|
39
47
|
children:
|
|
40
48
|
'Your account has been created and your certificate is installed. You can now access all resources assigned to your groups. Check your email for a welcome guide with next steps.',
|
|
41
49
|
},
|
|
@@ -44,16 +52,24 @@ export const Success: Story = {
|
|
|
44
52
|
export const Error: Story = {
|
|
45
53
|
args: {
|
|
46
54
|
variant: 'error',
|
|
55
|
+
align: 'start',
|
|
47
56
|
children:
|
|
48
57
|
'We could not verify your identity. This may happen if your invite link has expired or was already used. Please contact your administrator to request a new invitation.',
|
|
49
58
|
},
|
|
50
59
|
}
|
|
51
60
|
|
|
52
|
-
|
|
53
|
-
|
|
61
|
+
// Short single-line message — the default `align="center"` vertically centres
|
|
62
|
+
// the icon against the text.
|
|
63
|
+
export const ShortCentered: Story = {
|
|
54
64
|
args: {
|
|
55
|
-
variant: '
|
|
56
|
-
children: '
|
|
65
|
+
variant: 'success',
|
|
66
|
+
children: "You're all caught up — no requests awaiting review.",
|
|
67
|
+
},
|
|
68
|
+
play: async ({canvas}) => {
|
|
69
|
+
await expect(canvas.getByRole('note')).toBeInTheDocument()
|
|
70
|
+
await expect(canvas.getByText(/all caught up/)).toBeInTheDocument()
|
|
71
|
+
// Exactly one icon (svg) — the component's own, no duplicate.
|
|
72
|
+
await expect(canvas.getByRole('note').querySelectorAll('svg')).toHaveLength(1)
|
|
57
73
|
},
|
|
58
74
|
}
|
|
59
75
|
|
|
@@ -72,22 +88,19 @@ const stackStyles = css.create({
|
|
|
72
88
|
export const AllVariants: Story = {
|
|
73
89
|
render: () => (
|
|
74
90
|
<html.div style={stackStyles.stack}>
|
|
75
|
-
<Callout variant="error">
|
|
91
|
+
<Callout variant="error" align="start">
|
|
76
92
|
We could not verify your identity. This may happen if your invite link has expired or was
|
|
77
93
|
already used. Please contact your administrator.
|
|
78
94
|
</Callout>
|
|
79
|
-
<Callout variant="success">
|
|
95
|
+
<Callout variant="success" align="start">
|
|
80
96
|
Your account has been created and your certificate is installed. You can now access all
|
|
81
97
|
resources assigned to your groups.
|
|
82
98
|
</Callout>
|
|
83
|
-
<Callout variant="warning">
|
|
99
|
+
<Callout variant="warning" align="start">
|
|
84
100
|
It looks like your certificate is not installed yet. Install the .p12 file from your email,
|
|
85
101
|
then click the button below.
|
|
86
102
|
</Callout>
|
|
87
|
-
<Callout variant="info">
|
|
88
|
-
A new version of the application is available. Please save your work and refresh the page to
|
|
89
|
-
get the latest features.
|
|
90
|
-
</Callout>
|
|
103
|
+
<Callout variant="info">A new version is available.</Callout>
|
|
91
104
|
</html.div>
|
|
92
105
|
),
|
|
93
106
|
play: async ({canvas}) => {
|
|
@@ -17,6 +17,12 @@ interface CalloutProps {
|
|
|
17
17
|
variant?: CalloutVariant
|
|
18
18
|
/** Built-in icon name, custom ReactNode, or false to hide. Defaults to variant icon. */
|
|
19
19
|
icon?: IconName | ReactNode | false
|
|
20
|
+
/**
|
|
21
|
+
* Vertical alignment of the icon against the message. `center` (default)
|
|
22
|
+
* suits short single/two-line messages; `start` aligns the icon to the first
|
|
23
|
+
* line for long multi-paragraph content.
|
|
24
|
+
*/
|
|
25
|
+
align?: 'center' | 'start'
|
|
20
26
|
children: ReactNode
|
|
21
27
|
}
|
|
22
28
|
|
|
@@ -27,13 +33,20 @@ function resolveIcon(icon: CalloutProps['icon'], variant: CalloutVariant): React
|
|
|
27
33
|
return icon
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
export function Callout({variant = 'info', icon, children}: CalloutProps) {
|
|
36
|
+
export function Callout({variant = 'info', icon, align = 'center', children}: CalloutProps) {
|
|
31
37
|
const resolvedIcon = resolveIcon(icon, variant)
|
|
32
38
|
|
|
33
39
|
return (
|
|
34
|
-
<html.div
|
|
40
|
+
<html.div
|
|
41
|
+
role="note"
|
|
42
|
+
style={[
|
|
43
|
+
styles.base,
|
|
44
|
+
styles[variant],
|
|
45
|
+
align === 'center' ? styles.alignCenter : styles.alignStart,
|
|
46
|
+
]}
|
|
47
|
+
>
|
|
35
48
|
{resolvedIcon && <html.span style={styles.icon}>{resolvedIcon}</html.span>}
|
|
36
|
-
{children}
|
|
49
|
+
<html.div style={styles.content}>{children}</html.div>
|
|
37
50
|
</html.div>
|
|
38
51
|
)
|
|
39
52
|
}
|
|
@@ -5,23 +5,35 @@ import {typography} from '@duro-app/tokens/tokens/typography.css'
|
|
|
5
5
|
|
|
6
6
|
export const styles = css.create({
|
|
7
7
|
base: {
|
|
8
|
+
display: 'flex',
|
|
9
|
+
flexDirection: 'row',
|
|
10
|
+
gap: spacing.sm,
|
|
8
11
|
padding: spacing.md,
|
|
9
12
|
borderRadius: radii.sm,
|
|
10
13
|
borderWidth: 1,
|
|
11
14
|
borderStyle: 'solid',
|
|
12
15
|
fontSize: typography.fontSizeSm,
|
|
13
16
|
lineHeight: typography.lineHeight,
|
|
14
|
-
|
|
17
|
+
},
|
|
18
|
+
// Vertically centres the icon with the message — the right default for the
|
|
19
|
+
// short, single/two-line messages callouts usually carry.
|
|
20
|
+
alignCenter: {
|
|
21
|
+
alignItems: 'center',
|
|
22
|
+
},
|
|
23
|
+
// Aligns the icon to the first line — for long, multi-paragraph messages.
|
|
24
|
+
alignStart: {
|
|
25
|
+
alignItems: 'flex-start',
|
|
15
26
|
},
|
|
16
27
|
icon: {
|
|
17
|
-
|
|
18
|
-
marginRight: spacing.sm,
|
|
19
|
-
marginBottom: spacing.xs,
|
|
20
|
-
marginTop: 2,
|
|
28
|
+
flexShrink: 0,
|
|
21
29
|
display: 'inline-flex',
|
|
22
30
|
alignItems: 'center',
|
|
23
31
|
justifyContent: 'center',
|
|
24
32
|
},
|
|
33
|
+
content: {
|
|
34
|
+
flexGrow: 1,
|
|
35
|
+
minWidth: 0,
|
|
36
|
+
},
|
|
25
37
|
error: {
|
|
26
38
|
backgroundColor: colors.errorBg,
|
|
27
39
|
borderColor: colors.errorBorder,
|
|
@@ -1134,3 +1134,60 @@ export const SlotPropsOnRoot: Story = {
|
|
|
1134
1134
|
await expect(canvas.getAllByText('Name').length).toBeGreaterThan(0)
|
|
1135
1135
|
},
|
|
1136
1136
|
}
|
|
1137
|
+
|
|
1138
|
+
// --- Regression: many columns + long content must wrap, not overlap ---
|
|
1139
|
+
//
|
|
1140
|
+
// In a wide-but-tight container, columns whose combined content exceeds the
|
|
1141
|
+
// width used to keep their intrinsic size (minWidth: auto) and spill over their
|
|
1142
|
+
// grid track into the next column. Cells now shrink to their track and wrap.
|
|
1143
|
+
const overlapWrapStyles = css.create({
|
|
1144
|
+
wide: {width: 760},
|
|
1145
|
+
})
|
|
1146
|
+
|
|
1147
|
+
const LONG_EMAIL = 'a-really-long-service-account.address@subdomain.example-company.com'
|
|
1148
|
+
|
|
1149
|
+
export const ManyColumnsWrapNoOverlap: Story = {
|
|
1150
|
+
render: () => (
|
|
1151
|
+
<html.div style={overlapWrapStyles.wide}>
|
|
1152
|
+
<Table.Root>
|
|
1153
|
+
<Table.Header>
|
|
1154
|
+
<Table.Row>
|
|
1155
|
+
<Table.HeaderCell>Display Name</Table.HeaderCell>
|
|
1156
|
+
<Table.HeaderCell>Type</Table.HeaderCell>
|
|
1157
|
+
<Table.HeaderCell>Email</Table.HeaderCell>
|
|
1158
|
+
<Table.HeaderCell>Status</Table.HeaderCell>
|
|
1159
|
+
<Table.HeaderCell width="max-content">Actions</Table.HeaderCell>
|
|
1160
|
+
</Table.Row>
|
|
1161
|
+
</Table.Header>
|
|
1162
|
+
<Table.Body>
|
|
1163
|
+
<Table.Row>
|
|
1164
|
+
<Table.Cell>SMTP submission service account</Table.Cell>
|
|
1165
|
+
<Table.Cell>
|
|
1166
|
+
<Badge>User</Badge>
|
|
1167
|
+
</Table.Cell>
|
|
1168
|
+
<Table.Cell>{LONG_EMAIL}</Table.Cell>
|
|
1169
|
+
<Table.Cell>
|
|
1170
|
+
<Badge variant="warning">No certs</Badge>
|
|
1171
|
+
</Table.Cell>
|
|
1172
|
+
<Table.Cell isActions>
|
|
1173
|
+
<Button size="small">Send Cert</Button>
|
|
1174
|
+
<Button size="small" variant="secondary">
|
|
1175
|
+
Certificates
|
|
1176
|
+
</Button>
|
|
1177
|
+
<Button size="small" variant="danger">
|
|
1178
|
+
Revoke All
|
|
1179
|
+
</Button>
|
|
1180
|
+
</Table.Cell>
|
|
1181
|
+
</Table.Row>
|
|
1182
|
+
</Table.Body>
|
|
1183
|
+
</Table.Root>
|
|
1184
|
+
</html.div>
|
|
1185
|
+
),
|
|
1186
|
+
play: async ({canvas}) => {
|
|
1187
|
+
const emailCell = canvas.getByText(LONG_EMAIL).closest('[role="cell"]') as HTMLElement
|
|
1188
|
+
await expect(emailCell).toBeTruthy()
|
|
1189
|
+
// The long email wraps inside its column instead of overflowing into the
|
|
1190
|
+
// next one: the cell's content is no wider than the cell box.
|
|
1191
|
+
await expect(emailCell.scrollWidth).toBeLessThanOrEqual(emailCell.clientWidth + 2)
|
|
1192
|
+
},
|
|
1193
|
+
}
|
|
@@ -460,7 +460,7 @@ export function Cell({children, isActions}: {children: ReactNode; isActions?: bo
|
|
|
460
460
|
{responsive && !isActions && label !== '' ? (
|
|
461
461
|
<html.span style={styles.cellLabel}>{label}</html.span>
|
|
462
462
|
) : null}
|
|
463
|
-
{children}
|
|
463
|
+
{isActions ? children : <html.div style={styles.cellValue}>{children}</html.div>}
|
|
464
464
|
</html.div>
|
|
465
465
|
)
|
|
466
466
|
}
|
|
@@ -154,6 +154,10 @@ export const styles = css.create({
|
|
|
154
154
|
fontWeight: typography.fontWeightSemibold,
|
|
155
155
|
color: colors.textMuted,
|
|
156
156
|
textAlign: 'start',
|
|
157
|
+
// Match the body cell: shrink inside the track + wrap, so header labels
|
|
158
|
+
// don't overlap when many columns are cramped.
|
|
159
|
+
minWidth: 0,
|
|
160
|
+
overflowWrap: 'break-word',
|
|
157
161
|
whiteSpace: {
|
|
158
162
|
default: 'normal',
|
|
159
163
|
[`@container (max-width: ${COMPACT_BP})`]: 'nowrap',
|
|
@@ -176,14 +180,13 @@ export const styles = css.create({
|
|
|
176
180
|
[`@container (max-width: ${STACK_BP})`]: 'grid',
|
|
177
181
|
},
|
|
178
182
|
alignItems: 'center',
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
},
|
|
183
|
+
// Always let the cell shrink inside its `minmax(0, 1fr)` track. Previously
|
|
184
|
+
// this was `auto` in the default (wide) band on the assumption there's
|
|
185
|
+
// "enough room" — but a table with many columns (or wide content) whose
|
|
186
|
+
// combined intrinsic width exceeds the container then can't shrink, so
|
|
187
|
+
// content spills its track and overlaps the next column. `0` + the
|
|
188
|
+
// wrapping below keeps every cell inside its column at any width.
|
|
189
|
+
minWidth: 0,
|
|
187
190
|
overflow: {
|
|
188
191
|
// Safety against pathological unbreakable content (long URLs,
|
|
189
192
|
// dataless ids) at narrow widths.
|
|
@@ -191,6 +194,9 @@ export const styles = css.create({
|
|
|
191
194
|
[`@container (max-width: ${COMPACT_BP})`]: 'hidden',
|
|
192
195
|
},
|
|
193
196
|
whiteSpace: 'normal',
|
|
197
|
+
// Break long unbreakable tokens (emails, UUIDs) so they wrap within the
|
|
198
|
+
// column instead of forcing it wider / overflowing.
|
|
199
|
+
overflowWrap: 'break-word',
|
|
194
200
|
gridTemplateColumns: {
|
|
195
201
|
default: null,
|
|
196
202
|
[`@container (max-width: ${STACK_BP})`]: '1fr 2fr',
|
|
@@ -201,6 +207,16 @@ export const styles = css.create({
|
|
|
201
207
|
},
|
|
202
208
|
},
|
|
203
209
|
|
|
210
|
+
// Wrapper around a (non-actions) cell's value. A raw text node is an
|
|
211
|
+
// anonymous flex item with `min-width: auto`, so it refuses to shrink and
|
|
212
|
+
// overflows its column even when the cell itself can shrink. Giving the value
|
|
213
|
+
// its own `min-width: 0` flex item lets the text wrap inside the column.
|
|
214
|
+
cellValue: {
|
|
215
|
+
minWidth: 0,
|
|
216
|
+
flexGrow: 1,
|
|
217
|
+
overflowWrap: 'break-word',
|
|
218
|
+
},
|
|
219
|
+
|
|
204
220
|
// Label rendered as a real <span> inside each cell. Display:none in
|
|
205
221
|
// default/compact, shown only in stack — matches StyleX guidance to
|
|
206
222
|
// prefer real elements over ::before/::after for non-shadow-DOM cases.
|