@duro-app/ui 5.32.0 → 5.34.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.
Files changed (55) hide show
  1. package/dist/{Table-ypIvytAS.js → Table-IuD0tooi.js} +56 -49
  2. package/dist/Table-IuD0tooi.js.map +1 -0
  3. package/dist/{compiler-runtime-BuugS4D0.js → compiler-runtime-CV6vsf8h.js} +80 -73
  4. package/dist/{compiler-runtime-BuugS4D0.js.map → compiler-runtime-CV6vsf8h.js.map} +1 -1
  5. package/dist/components/Avatar/Avatar.d.ts +20 -0
  6. package/dist/components/Avatar/Avatar.d.ts.map +1 -0
  7. package/dist/components/Avatar/Avatar.meta.d.ts +3 -0
  8. package/dist/components/Avatar/Avatar.meta.d.ts.map +1 -0
  9. package/dist/components/Avatar/Avatar.stories.d.ts +13 -0
  10. package/dist/components/Avatar/Avatar.stories.d.ts.map +1 -0
  11. package/dist/components/Avatar/styles.css.d.ts +41 -0
  12. package/dist/components/Avatar/styles.css.d.ts.map +1 -0
  13. package/dist/components/Icon/Icon.d.ts.map +1 -1
  14. package/dist/components/Icon/geometry.d.ts +4 -0
  15. package/dist/components/Icon/geometry.d.ts.map +1 -1
  16. package/dist/components/Icon/phosphor-glyphs.d.ts +1028 -0
  17. package/dist/components/Icon/phosphor-glyphs.d.ts.map +1 -0
  18. package/dist/components/Icon/types.d.ts +3 -1
  19. package/dist/components/Icon/types.d.ts.map +1 -1
  20. package/dist/components/List/ItemLink.d.ts +32 -0
  21. package/dist/components/List/ItemLink.d.ts.map +1 -0
  22. package/dist/components/List/List.d.ts +36 -4
  23. package/dist/components/List/List.d.ts.map +1 -1
  24. package/dist/components/List/List.stories.d.ts +9 -0
  25. package/dist/components/List/List.stories.d.ts.map +1 -1
  26. package/dist/components/List/styles.css.d.ts +39 -0
  27. package/dist/components/List/styles.css.d.ts.map +1 -1
  28. package/dist/form.js +1 -1
  29. package/dist/index.css +1 -1
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +5578 -4353
  33. package/dist/index.js.map +1 -1
  34. package/dist/table.js +2 -2
  35. package/package.json +3 -2
  36. package/src/components/Avatar/Avatar.meta.ts +31 -0
  37. package/src/components/Avatar/Avatar.stories.tsx +79 -0
  38. package/src/components/Avatar/Avatar.tsx +42 -0
  39. package/src/components/Avatar/styles.css.ts +32 -0
  40. package/src/components/Icon/Icon.meta.ts +1 -1
  41. package/src/components/Icon/Icon.native.tsx +2 -2
  42. package/src/components/Icon/Icon.tsx +4 -3
  43. package/src/components/Icon/geometry.ts +7 -0
  44. package/src/components/Icon/phosphor-glyphs.ts +1285 -0
  45. package/src/components/Icon/phosphor-names.json +130 -0
  46. package/src/components/Icon/types.ts +4 -0
  47. package/src/components/List/ItemLink.native.tsx +60 -0
  48. package/src/components/List/ItemLink.tsx +80 -0
  49. package/src/components/List/List.meta.ts +3 -3
  50. package/src/components/List/List.stories.tsx +91 -1
  51. package/src/components/List/List.tsx +130 -22
  52. package/src/components/List/styles.css.ts +45 -0
  53. package/src/index.native.ts +6 -0
  54. package/src/index.ts +6 -0
  55. package/dist/Table-ypIvytAS.js.map +0 -1
package/dist/table.js CHANGED
@@ -1,6 +1,6 @@
1
- import { M as F, q as gt, R as it, H as kt, t as pt, v as bt, w as ft, x as mt, b as ht, g as yt, r as St, y as Tt } from "./Table-ypIvytAS.js";
1
+ import { M as F, q as gt, R as it, H as kt, t as pt, v as bt, w as ft, x as mt, b as ht, g as yt, r as St, y as Tt } from "./Table-IuD0tooi.js";
2
2
  import { jsx as u, jsxs as q } from "react/jsx-runtime";
3
- import { c as H, m as p, d as b } from "./compiler-runtime-BuugS4D0.js";
3
+ import { c as H, m as p, d as b } from "./compiler-runtime-CV6vsf8h.js";
4
4
  import { flexRender as E, useReactTable as rt, getPaginationRowModel as Ct, getFilteredRowModel as vt, getSortedRowModel as at, getCoreRowModel as st } from "@tanstack/react-table";
5
5
  import { useState as G, useCallback as wt, useRef as K, useEffect as Z, useLayoutEffect as zt } from "react";
6
6
  import { useVirtualizer as Vt } from "@tanstack/react-virtual";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duro-app/ui",
3
- "version": "5.32.0",
3
+ "version": "5.34.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -92,11 +92,12 @@
92
92
  "dependencies": {
93
93
  "@tanstack/react-virtual": "^3.14.13",
94
94
  "qrcode": "^1.5.4",
95
- "@duro-app/tokens": "^5.32.0"
95
+ "@duro-app/tokens": "^5.34.0"
96
96
  },
97
97
  "devDependencies": {
98
98
  "@babel/preset-typescript": "^7.28.0",
99
99
  "@hookform/resolvers": "^5.0.0",
100
+ "@phosphor-icons/core": "2.1.1",
100
101
  "@types/qrcode": "^1.5.6",
101
102
  "@types/react": "^19.0.0",
102
103
  "@types/react-dom": "^19.0.0",
@@ -0,0 +1,31 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'A person or entity as a picture, else their initials, else a neutral placeholder. alt is required and is the accessible name in all three states. sm, md and lg map to the controlSm, thumbSm and iconXxl size tokens; shape is circle or square. An image that fails to load falls back to the initials.',
6
+ whenToUse: [
7
+ 'The owner, author or assignee of a record, beside their name',
8
+ 'A List.Leading slot for people or accounts',
9
+ ],
10
+ whenNotToUse: [
11
+ 'A glyph for a thing or a status — use Icon or StatusIcon',
12
+ 'A photo that is the content — use an image element inside Card',
13
+ ],
14
+ relatedTo: [
15
+ {
16
+ component: 'List',
17
+ kind: 'composition',
18
+ relationship: 'Avatar fills List.Leading for people and accounts',
19
+ },
20
+ {
21
+ component: 'Icon',
22
+ kind: 'contrast',
23
+ relationship: 'Icon draws a glyph; Avatar identifies someone',
24
+ },
25
+ ],
26
+ example: `<Inline gap="sm" align="center">
27
+ <Avatar src="/people/ada.png" alt="Ada Lovelace" />
28
+ <Avatar alt="Grace Hopper" initials="GH" size="lg" />
29
+ <Avatar alt="Unassigned" shape="square" />
30
+ </Inline>`,
31
+ }
@@ -0,0 +1,79 @@
1
+ import type {Meta, StoryObj} from '@storybook/react'
2
+ import {expect, waitFor} from 'storybook/test'
3
+ import {Avatar} from './Avatar'
4
+ import {Inline} from '../Inline/Inline'
5
+ import {onThemeSurface} from '../../docs/themedSurface'
6
+
7
+ const meta: Meta<typeof Avatar> = {
8
+ title: 'Components/Avatar',
9
+ component: Avatar,
10
+ decorators: [onThemeSurface],
11
+ argTypes: {
12
+ size: {control: 'select', options: ['sm', 'md', 'lg']},
13
+ shape: {control: 'select', options: ['circle', 'square']},
14
+ },
15
+ }
16
+
17
+ export default meta
18
+ type Story = StoryObj<typeof Avatar>
19
+
20
+ // A 1x1 transparent GIF: a real image that needs no network.
21
+ const PICTURE = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
22
+
23
+ export const Default: Story = {
24
+ args: {alt: 'Grace Hopper', initials: 'GH'},
25
+ }
26
+
27
+ /** The image when there is a src; else the initials; else a neutral placeholder. */
28
+ export const ImageInitialsPlaceholder: Story = {
29
+ render: () => (
30
+ <Inline gap="md" align="center">
31
+ <Avatar src={PICTURE} alt="Ada Lovelace" />
32
+ <Avatar alt="Grace Hopper" initials="GH" />
33
+ <Avatar alt="Unassigned" />
34
+ </Inline>
35
+ ),
36
+ play: async ({canvas}) => {
37
+ const image = canvas.getByRole('img', {name: 'Ada Lovelace'})
38
+ await expect(image.tagName).toBe('IMG')
39
+ await expect(image).toHaveAttribute('src', PICTURE)
40
+
41
+ const initials = canvas.getByRole('img', {name: 'Grace Hopper'})
42
+ await expect(initials.tagName).not.toBe('IMG')
43
+ await expect(initials).toHaveTextContent('GH')
44
+
45
+ const placeholder = canvas.getByRole('img', {name: 'Unassigned'})
46
+ await expect(placeholder).toBeEmptyDOMElement()
47
+ },
48
+ }
49
+
50
+ export const Sizes: Story = {
51
+ render: () => (
52
+ <Inline gap="md" align="center">
53
+ <Avatar alt="Small" initials="S" size="sm" />
54
+ <Avatar alt="Medium" initials="M" size="md" />
55
+ <Avatar alt="Large" initials="L" size="lg" />
56
+ </Inline>
57
+ ),
58
+ }
59
+
60
+ export const Shapes: Story = {
61
+ render: () => (
62
+ <Inline gap="md" align="center">
63
+ <Avatar alt="Circle" initials="C" shape="circle" />
64
+ <Avatar alt="Square" initials="S" shape="square" />
65
+ </Inline>
66
+ ),
67
+ }
68
+
69
+ /** An image that cannot load falls back to the initials. */
70
+ export const BrokenImage: Story = {
71
+ args: {src: 'data:image/png;base64,not-an-image', alt: 'Alan Turing', initials: 'AT'},
72
+ play: async ({canvas}) => {
73
+ await waitFor(async () => {
74
+ const avatar = canvas.getByRole('img', {name: 'Alan Turing'})
75
+ await expect(avatar.tagName).not.toBe('IMG')
76
+ await expect(avatar).toHaveTextContent('AT')
77
+ })
78
+ },
79
+ }
@@ -0,0 +1,42 @@
1
+ import {useState} from 'react'
2
+ import {html} from 'react-strict-dom'
3
+ import {styles} from './styles.css'
4
+
5
+ export type AvatarSize = 'sm' | 'md' | 'lg'
6
+ export type AvatarShape = 'circle' | 'square'
7
+
8
+ export interface AvatarProps {
9
+ /** Image URL. Without it, or when it fails to load, the initials show. */
10
+ src?: string
11
+ /** The accessible name: who or what this avatar stands for. Never empty. */
12
+ alt: string
13
+ /** Shown when there is no image; one or two characters. */
14
+ initials?: string
15
+ /** sm 28, md 40, lg 48 (the controlSm, thumbSm and iconXxl size tokens). Default md. */
16
+ size?: AvatarSize
17
+ /** Default circle. */
18
+ shape?: AvatarShape
19
+ }
20
+
21
+ /**
22
+ * A person or entity as a picture, else their initials, else a neutral blank. The image, the
23
+ * initials and the blank all carry `alt` as the accessible name.
24
+ */
25
+ export function Avatar({src, alt, initials, size = 'md', shape = 'circle'}: AvatarProps) {
26
+ const [failedSrc, setFailedSrc] = useState<string | null>(null)
27
+ const showImage = src !== undefined && src !== failedSrc
28
+ const frame = [styles.base, styles[size], styles[shape]]
29
+
30
+ if (showImage) {
31
+ return (
32
+ <html.div style={frame}>
33
+ <html.img src={src} alt={alt} onError={() => setFailedSrc(src)} style={styles.image} />
34
+ </html.div>
35
+ )
36
+ }
37
+ return (
38
+ <html.div role="img" aria-label={alt} style={frame}>
39
+ {initials ? <html.span aria-hidden={true}>{initials}</html.span> : null}
40
+ </html.div>
41
+ )
42
+ }
@@ -0,0 +1,32 @@
1
+ import {css} from 'react-strict-dom'
2
+ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
+ import {radii} from '@duro-app/tokens/tokens/spacing.css'
4
+ import {sizes} from '@duro-app/tokens/tokens/sizes.css'
5
+ import {typography} from '@duro-app/tokens/tokens/typography.css'
6
+
7
+ export const styles = css.create({
8
+ base: {
9
+ display: 'flex',
10
+ alignItems: 'center',
11
+ justifyContent: 'center',
12
+ flexShrink: 0,
13
+ overflow: 'hidden',
14
+ backgroundColor: colors.neutralBg,
15
+ color: colors.neutralText,
16
+ fontWeight: typography.fontWeightMedium,
17
+ lineHeight: 1,
18
+ },
19
+
20
+ sm: {width: sizes.controlSm, height: sizes.controlSm, fontSize: typography.fontSizeXs},
21
+ md: {width: sizes.thumbSm, height: sizes.thumbSm, fontSize: typography.fontSizeSm},
22
+ lg: {width: sizes.iconXxl, height: sizes.iconXxl, fontSize: typography.fontSizeMd},
23
+
24
+ circle: {borderRadius: radii.full},
25
+ square: {borderRadius: radii.sm},
26
+
27
+ image: {
28
+ width: '100%',
29
+ height: '100%',
30
+ objectFit: 'cover',
31
+ },
32
+ })
@@ -2,7 +2,7 @@ import type {ComponentMeta} from '../component-meta'
2
2
 
3
3
  export const meta: ComponentMeta = {
4
4
  description:
5
- 'SVG icon component. Renders stroke or filled icons from the design system icon set. Size scales with the size prop. Use inside buttons, badges, or standalone.',
5
+ 'SVG icon component. Renders stroke or filled icons from the design system icon set, plus Phosphor glyphs (regular weight, fill style, generated by scripts/generate-phosphor-icons.mjs from phosphor-names.json) under their Phosphor names. Size scales with the size prop. Use inside buttons, badges, or standalone.',
6
6
  whenToUse: [
7
7
  'Button icons (inside Button children)',
8
8
  'Status indicators alongside text',
@@ -2,7 +2,7 @@ import {createElement} from 'react'
2
2
  import Svg, {Circle, Ellipse, Line, Path, Polygon, Polyline, Rect} from 'react-native-svg'
3
3
  import {ICON_SIZES, type ColorToken} from '@duro-app/tokens/keys'
4
4
  import {useResolvedColors} from '../ThemeProvider/ThemeProvider'
5
- import {GLYPHS, type Paint, type Shape} from './geometry'
5
+ import {DEFAULT_VIEW_BOX, GLYPHS, type Paint, type Shape} from './geometry'
6
6
  import type {IconProps} from './types'
7
7
 
8
8
  export type {IconName, IconProps, IconSize} from './types'
@@ -37,7 +37,7 @@ export function Icon({name, size: sizeToken = 'lg', color = 'text'}: IconProps)
37
37
  glyph.style === 'fill' ? {fill: 'currentColor'} : {fill: 'none', stroke: 'currentColor'}
38
38
  return (
39
39
  <Svg
40
- viewBox="0 0 24 24"
40
+ viewBox={glyph.viewBox ?? DEFAULT_VIEW_BOX}
41
41
  width={size}
42
42
  height={size}
43
43
  color={resolved}
@@ -2,7 +2,7 @@ import {createElement} from 'react'
2
2
  import {ICON_SIZES} from '@duro-app/tokens/keys'
3
3
  import {colors} from '@duro-app/tokens/tokens/colors.css'
4
4
 
5
- import {GLYPHS, type Shape} from './geometry'
5
+ import {DEFAULT_VIEW_BOX, GLYPHS, type Shape} from './geometry'
6
6
  import type {IconProps} from './types'
7
7
 
8
8
  export type {IconName, IconProps, IconSize} from './types'
@@ -16,11 +16,12 @@ function svgShape(shape: Shape, key: number) {
16
16
  export function Icon({name, size: sizeToken = 'lg', color}: IconProps) {
17
17
  const size = ICON_SIZES[sizeToken]
18
18
  const glyph = GLYPHS[name]
19
+ const viewBox = glyph.viewBox ?? DEFAULT_VIEW_BOX
19
20
  const shapes = glyph.shapes.map(svgShape)
20
21
  const style = color ? {color: colors[color]} : undefined
21
22
  if (glyph.style === 'fill') {
22
23
  return (
23
- <svg viewBox="0 0 24 24" fill="currentColor" width={size} height={size} style={style}>
24
+ <svg viewBox={viewBox} fill="currentColor" width={size} height={size} style={style}>
24
25
  {shapes}
25
26
  </svg>
26
27
  )
@@ -28,7 +29,7 @@ export function Icon({name, size: sizeToken = 'lg', color}: IconProps) {
28
29
 
29
30
  return (
30
31
  <svg
31
- viewBox="0 0 24 24"
32
+ viewBox={viewBox}
32
33
  fill="none"
33
34
  stroke="currentColor"
34
35
  strokeWidth={2}
@@ -1,3 +1,4 @@
1
+ import {PHOSPHOR_GLYPHS} from './phosphor-glyphs'
1
2
  import type {IconName} from './types'
2
3
 
3
4
  /**
@@ -33,10 +34,16 @@ export type Shape =
33
34
 
34
35
  export interface Glyph {
35
36
  readonly style: 'stroke' | 'fill'
37
+ /** The glyph's own coordinate space; the 24-unit grid when absent. */
38
+ readonly viewBox?: string
36
39
  readonly shapes: readonly Shape[]
37
40
  }
38
41
 
42
+ /** The grid Duro's own glyphs are drawn on. */
43
+ export const DEFAULT_VIEW_BOX = '0 0 24 24'
44
+
39
45
  export const GLYPHS: Record<IconName, Glyph> = {
46
+ ...PHOSPHOR_GLYPHS,
40
47
  'x-circle': {
41
48
  style: 'stroke',
42
49
  shapes: [