@duro-app/ui 0.42.0 → 0.43.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duro-app/ui",
3
- "version": "0.42.0",
3
+ "version": "0.43.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.42.0"
58
+ "@duro-app/tokens": "^0.43.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
- export const ShortText: Story = {
53
- name: 'Short text (still wraps)',
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: 'info',
56
- children: 'System update available.',
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 role="note" style={[styles.base, styles[variant]]}>
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
- overflow: 'hidden', // contain the float
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
- float: 'left',
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,