@dxos/react-ui-card 0.8.2 → 0.11.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 (74) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/index.mjs +288 -0
  3. package/dist/lib/index.mjs.map +1 -0
  4. package/dist/lib/translations.mjs +11 -0
  5. package/dist/lib/translations.mjs.map +1 -0
  6. package/dist/types/src/components/Avatar/Avatar.d.ts +14 -0
  7. package/dist/types/src/components/Avatar/Avatar.d.ts.map +1 -0
  8. package/dist/types/src/components/Avatar/Avatar.stories.d.ts +15 -0
  9. package/dist/types/src/components/Avatar/Avatar.stories.d.ts.map +1 -0
  10. package/dist/types/src/components/Avatar/avatar-name.d.ts +6 -0
  11. package/dist/types/src/components/Avatar/avatar-name.d.ts.map +1 -0
  12. package/dist/types/src/components/Avatar/index.d.ts +3 -0
  13. package/dist/types/src/components/Avatar/index.d.ts.map +1 -0
  14. package/dist/types/src/components/CardTile/CardTile.d.ts +46 -0
  15. package/dist/types/src/components/CardTile/CardTile.d.ts.map +1 -0
  16. package/dist/types/src/components/CardTile/CardTile.stories.d.ts +27 -0
  17. package/dist/types/src/components/CardTile/CardTile.stories.d.ts.map +1 -0
  18. package/dist/types/src/components/CardTile/index.d.ts +2 -0
  19. package/dist/types/src/components/CardTile/index.d.ts.map +1 -0
  20. package/dist/types/src/components/Row/Row.d.ts +100 -0
  21. package/dist/types/src/components/Row/Row.d.ts.map +1 -0
  22. package/dist/types/src/components/Row/Row.stories.d.ts +23 -0
  23. package/dist/types/src/components/Row/Row.stories.d.ts.map +1 -0
  24. package/dist/types/src/components/Row/index.d.ts +2 -0
  25. package/dist/types/src/components/Row/index.d.ts.map +1 -0
  26. package/dist/types/src/components/index.d.ts +3 -1
  27. package/dist/types/src/components/index.d.ts.map +1 -1
  28. package/dist/types/src/hooks/index.d.ts +2 -0
  29. package/dist/types/src/hooks/index.d.ts.map +1 -0
  30. package/dist/types/src/hooks/useActorContact.d.ts +5 -0
  31. package/dist/types/src/hooks/useActorContact.d.ts.map +1 -0
  32. package/dist/types/src/index.d.ts +2 -1
  33. package/dist/types/src/index.d.ts.map +1 -1
  34. package/dist/types/src/translations.d.ts +11 -0
  35. package/dist/types/src/translations.d.ts.map +1 -0
  36. package/dist/types/src/util.d.ts +3 -0
  37. package/dist/types/src/util.d.ts.map +1 -0
  38. package/dist/types/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +39 -30
  40. package/src/components/Avatar/Avatar.stories.tsx +31 -0
  41. package/src/components/Avatar/Avatar.tsx +39 -0
  42. package/src/components/Avatar/avatar-name.ts +19 -0
  43. package/src/components/Avatar/index.ts +7 -0
  44. package/src/components/CardTile/CardTile.stories.tsx +54 -0
  45. package/src/components/CardTile/CardTile.tsx +103 -0
  46. package/src/components/CardTile/index.ts +5 -0
  47. package/src/components/Row/Row.stories.tsx +65 -0
  48. package/src/components/Row/Row.tsx +379 -0
  49. package/src/components/Row/index.ts +5 -0
  50. package/src/components/index.ts +4 -2
  51. package/src/hooks/index.ts +5 -0
  52. package/src/hooks/useActorContact.ts +31 -0
  53. package/src/index.ts +3 -2
  54. package/src/translations.ts +19 -0
  55. package/src/util.ts +7 -0
  56. package/README.md +0 -3
  57. package/dist/lib/browser/index.mjs +0 -200
  58. package/dist/lib/browser/index.mjs.map +0 -7
  59. package/dist/lib/browser/meta.json +0 -1
  60. package/dist/lib/node/index.cjs +0 -243
  61. package/dist/lib/node/index.cjs.map +0 -7
  62. package/dist/lib/node/meta.json +0 -1
  63. package/dist/lib/node-esm/index.mjs +0 -202
  64. package/dist/lib/node-esm/index.mjs.map +0 -7
  65. package/dist/lib/node-esm/meta.json +0 -1
  66. package/dist/types/src/components/Card.d.ts +0 -52
  67. package/dist/types/src/components/Card.d.ts.map +0 -1
  68. package/dist/types/src/components/Card.stories.d.ts +0 -57
  69. package/dist/types/src/components/Card.stories.d.ts.map +0 -1
  70. package/dist/types/src/theme.d.ts +0 -31
  71. package/dist/types/src/theme.d.ts.map +0 -1
  72. package/src/components/Card.stories.tsx +0 -217
  73. package/src/components/Card.tsx +0 -149
  74. package/src/theme.ts +0 -101
package/package.json CHANGED
@@ -1,54 +1,63 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-card",
3
- "version": "0.8.2",
4
- "description": "An opinionated card component.",
3
+ "version": "0.11.0",
4
+ "description": "Shared card-composition primitives (Row, CardTile, Avatar).",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
- "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
11
+ "license": "FSL-1.1-Apache-2.0",
8
12
  "author": "DXOS.org",
9
- "sideEffects": true,
10
13
  "type": "module",
14
+ "imports": {
15
+ "#translations": "./src/translations.ts"
16
+ },
11
17
  "exports": {
12
18
  ".": {
19
+ "source": "./src/index.ts",
13
20
  "types": "./dist/types/src/index.d.ts",
14
- "browser": "./dist/lib/browser/index.mjs",
15
- "node": "./dist/lib/node-esm/index.mjs"
21
+ "import": "./dist/lib/index.mjs"
22
+ },
23
+ "./translations": {
24
+ "source": "./src/translations.ts",
25
+ "types": "./dist/types/src/translations.d.ts",
26
+ "import": "./dist/lib/translations.mjs"
16
27
  }
17
28
  },
18
29
  "types": "dist/types/src/index.d.ts",
19
- "typesVersions": {
20
- "*": {}
21
- },
22
30
  "files": [
23
31
  "dist",
24
32
  "src"
25
33
  ],
26
34
  "dependencies": {
27
- "@preact-signals/safe-react": "^0.9.0",
28
- "@radix-ui/react-primitive": "2.0.2"
35
+ "date-fns": "^3.6.0",
36
+ "@dxos/echo": "0.11.0",
37
+ "@dxos/echo-react": "0.11.0",
38
+ "@dxos/keys": "0.11.0",
39
+ "@dxos/lit-ui": "0.11.0",
40
+ "@dxos/react-ui": "0.11.0",
41
+ "@dxos/react-ui-mosaic": "0.11.0",
42
+ "@dxos/types": "0.11.0",
43
+ "@dxos/ui-theme": "0.11.0",
44
+ "@dxos/util": "0.11.0"
29
45
  },
30
46
  "devDependencies": {
31
- "@dnd-kit/core": "^6.0.5",
32
- "@dnd-kit/sortable": "^7.0.1",
33
- "@dnd-kit/utilities": "^3.2.0",
34
- "@phosphor-icons/react": "^2.1.5",
35
- "@types/react": "~18.2.0",
36
- "@types/react-dom": "~18.2.0",
37
- "react": "~18.2.0",
38
- "react-dom": "~18.2.0",
39
- "vite": "5.4.7",
40
- "@dxos/random": "0.8.2",
41
- "@dxos/react-ui": "0.8.2",
42
- "@dxos/react-ui-theme": "0.8.2",
43
- "@dxos/storybook-utils": "0.8.2",
44
- "@dxos/util": "0.8.2"
47
+ "@types/react": "~19.2.17",
48
+ "@types/react-dom": "~19.2.3",
49
+ "effect": "3.21.4",
50
+ "react": "~19.2.7",
51
+ "react-dom": "~19.2.7",
52
+ "vite": "^8.1.4",
53
+ "@dxos/random": "0.11.0",
54
+ "@dxos/storybook-utils": "0.11.0"
45
55
  },
46
56
  "peerDependencies": {
47
- "@phosphor-icons/react": "^2.1.5",
48
- "react": "~18.2.0",
49
- "react-dom": "~18.2.0",
50
- "@dxos/react-ui-theme": "0.8.2",
51
- "@dxos/react-ui": "0.8.2"
57
+ "react": "~19.2.7",
58
+ "react-dom": "~19.2.7",
59
+ "@dxos/react-ui": "^0.11.0",
60
+ "@dxos/ui-theme": "^0.11.0"
52
61
  },
53
62
  "publishConfig": {
54
63
  "access": "public"
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+
7
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
8
+
9
+ import { Avatar } from './Avatar';
10
+
11
+ const meta = {
12
+ title: 'ui/react-ui-card/Avatar',
13
+ component: Avatar,
14
+ decorators: [withTheme(), withLayout({ layout: 'centered' })],
15
+ } satisfies Meta<typeof Avatar>;
16
+
17
+ export default meta;
18
+
19
+ type Story = StoryObj<typeof meta>;
20
+
21
+ export const Default: Story = {
22
+ args: { actor: { name: 'Ada Lovelace', email: 'ada@example.com' } },
23
+ };
24
+
25
+ export const Square: Story = {
26
+ args: { actor: { name: 'Grace Hopper', email: 'grace@example.com' }, variant: 'square', size: 8 },
27
+ };
28
+
29
+ export const EmailOnly: Story = {
30
+ args: { actor: { email: 'nameless@example.com' } },
31
+ };
@@ -0,0 +1,39 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { DxAvatar, type DxAvatarProps } from '@dxos/lit-ui/react';
8
+ import { type Actor } from '@dxos/types';
9
+
10
+ import { avatarName } from './avatar-name';
11
+ import { nameToHue } from './avatar-name';
12
+
13
+ export type AvatarProps = Pick<DxAvatarProps, 'variant' | 'size' | 'onClick'> & {
14
+ /** Sender/participant; supplies the display name and the hue. */
15
+ actor?: Actor.Actor;
16
+ /** Label override; defaults to the actor's {@link avatarName}. */
17
+ name?: string;
18
+ };
19
+
20
+ /**
21
+ * Sender/participant avatar with one shared name→hue derivation ({@link nameToHue}) so a given
22
+ * person renders the same color across the mailbox list, the opened thread, and preview cards —
23
+ * previously each site hand-rolled `DxAvatar` off a different input.
24
+ */
25
+ export const Avatar = ({ actor, name, variant = 'circle', size = 6, onClick }: AvatarProps) => {
26
+ const label = name ?? avatarName(actor);
27
+ return (
28
+ <DxAvatar
29
+ hue={nameToHue(label)}
30
+ hueVariant='surface'
31
+ variant={variant}
32
+ size={size}
33
+ fallback={label}
34
+ onClick={onClick}
35
+ />
36
+ );
37
+ };
38
+
39
+ Avatar.displayName = 'Avatar';
@@ -0,0 +1,19 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Actor } from '@dxos/types';
6
+ import { toHue } from '@dxos/util';
7
+
8
+ import { hashString } from '../../util';
9
+
10
+ // Kept out of `Avatar.tsx`: react-refresh only fast-refreshes a module whose
11
+ // exports are all components, so values exported beside them force a full page reload on
12
+ // every edit.
13
+
14
+ /** Canonical display name for an actor — the avatar glyph fallback and the hue key are both derived from it. */
15
+ export const avatarName = (actor?: Actor.Actor): string =>
16
+ actor?.contact?.target?.fullName ?? actor?.name ?? actor?.email ?? '';
17
+
18
+ /** Single source for a name→hue mapping, so the same sender is one color across every surface. */
19
+ export const nameToHue = (name?: string): string => toHue(hashString(name));
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './avatar-name';
6
+
7
+ export * from './Avatar';
@@ -0,0 +1,54 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React from 'react';
7
+
8
+ import { Card } from '@dxos/react-ui';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
10
+
11
+ import { translations } from '#translations';
12
+
13
+ import { CardTile } from './CardTile';
14
+
15
+ // CardTile.Header standalone inside Card chrome; CardTile.Root's mosaic shell is exercised by the
16
+ // EventStack / InboxStack stories (it requires a Mosaic.Container ancestor).
17
+ const DefaultStory = ({ menu, starred }: { menu?: boolean; starred?: boolean }) => (
18
+ <Card.Root fullWidth border={false} classNames='p-1'>
19
+ <CardTile.Header
20
+ menu={menu}
21
+ starred={starred}
22
+ onToggleStar={() => {}}
23
+ title={
24
+ <>
25
+ <span className='grow truncate font-medium'>Project kickoff — agenda and notes</span>
26
+ <span className='text-xs text-description whitespace-nowrap shrink-0'>2:30 PM</span>
27
+ </>
28
+ }
29
+ />
30
+ <Card.Body>
31
+ <Card.Row>
32
+ <Card.Text variant='description'>Body content rendered beneath the tile header.</Card.Text>
33
+ </Card.Row>
34
+ </Card.Body>
35
+ </Card.Root>
36
+ );
37
+
38
+ const meta = {
39
+ title: 'ui/react-ui-card/CardTile',
40
+ render: DefaultStory,
41
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
42
+ parameters: {
43
+ layout: 'fullscreen',
44
+ translations,
45
+ },
46
+ } satisfies Meta<typeof DefaultStory>;
47
+
48
+ export default meta;
49
+
50
+ type Story = StoryObj<typeof meta>;
51
+
52
+ export const Default: Story = { args: { starred: true, menu: true } };
53
+
54
+ export const NoStar: Story = {};
@@ -0,0 +1,103 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React, { type MouseEvent, PropsWithChildren, type ReactNode, forwardRef } from 'react';
6
+
7
+ import { Card, type ThemedClassName } from '@dxos/react-ui';
8
+ import { Focus, Mosaic, type MosaicTileProps } from '@dxos/react-ui-mosaic';
9
+
10
+ import { Row } from '../Row';
11
+
12
+ //
13
+ // Root
14
+ //
15
+
16
+ type CardTileRootProps = ThemedClassName<
17
+ PropsWithChildren<
18
+ Pick<MosaicTileProps<unknown>, 'data' | 'location' | 'current'> & {
19
+ 'id': string;
20
+ 'onCurrentChange': () => void;
21
+ 'onClick'?: (event: MouseEvent) => void;
22
+ 'data-testid'?: string;
23
+ }
24
+ >
25
+ >;
26
+
27
+ /**
28
+ * Shared mosaic tile shell: `Mosaic.Tile` → `Focus.Item` → `Card.Root`.
29
+ * Callers supply the inner `Card.Header`/`Card.Body` (typically via {@link CardTileHeader} + rows).
30
+ * Activation is committed by the caller's `onCurrentChange` (Mosaic `current`/selection), so click/Enter light the tile up.
31
+ */
32
+ const CardTileRoot = forwardRef<HTMLDivElement, CardTileRootProps>(
33
+ (
34
+ { id, data, location, current, onCurrentChange, onClick, classNames, children, 'data-testid': testId },
35
+ forwardedRef,
36
+ ) => (
37
+ <Mosaic.Tile
38
+ asChild
39
+ id={id}
40
+ data={data}
41
+ location={location}
42
+ classNames={classNames ?? 'dx-hover dx-current dx-selected p-1 rounded-md border border-subdued-separator'}
43
+ >
44
+ <Focus.Item asChild current={current} onCurrentChange={onCurrentChange}>
45
+ <Card.Root fullWidth border={false} onClick={onClick} ref={forwardedRef} data-testid={testId}>
46
+ {children}
47
+ </Card.Root>
48
+ </Focus.Item>
49
+ </Mosaic.Tile>
50
+ ),
51
+ );
52
+
53
+ CardTileRoot.displayName = 'CardTile.Root';
54
+
55
+ //
56
+ // Header
57
+ //
58
+
59
+ /** A single `Card.Menu` dropdown item. */
60
+ export type CardTileMenuItem = {
61
+ label: string;
62
+ icon?: string;
63
+ onClick: () => void;
64
+ };
65
+
66
+ type CardTileHeaderProps = {
67
+ /** Header title content (rendered in a flex row). */
68
+ title: ReactNode;
69
+ /** Whether the tile is starred. `Row.Star` renders the button only when `onToggleStar` is set. */
70
+ starred?: boolean;
71
+ /** Render the trailing `Card.Menu` action slot. */
72
+ menu?: boolean;
73
+ /** Items for the `Card.Menu` dropdown (the trigger is disabled when empty). */
74
+ menuItems?: CardTileMenuItem[];
75
+ onToggleStar?: () => void;
76
+ };
77
+
78
+ /**
79
+ * Tile header row: leading `Row.Star` · title · optional `Card.Menu`. Shared by message/conversation
80
+ * tiles (with menu) and event tiles (star + title only).
81
+ */
82
+ const CardTileHeader = ({ title, starred, menu = false, menuItems, onToggleStar }: CardTileHeaderProps) => (
83
+ <Card.Header>
84
+ <Card.Block>
85
+ <Row.Star starred={starred} onToggle={onToggleStar} />
86
+ </Card.Block>
87
+ <Card.Title classNames='flex items-center gap-3'>{title}</Card.Title>
88
+ {menu && <Card.Menu items={menuItems} />}
89
+ </Card.Header>
90
+ );
91
+
92
+ CardTileHeader.displayName = 'CardTile.Header';
93
+
94
+ //
95
+ // Tile
96
+ //
97
+
98
+ export const CardTile = {
99
+ Root: CardTileRoot,
100
+ Header: CardTileHeader,
101
+ };
102
+
103
+ export type { CardTileHeaderProps, CardTileRootProps };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './CardTile';
@@ -0,0 +1,65 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useMemo } from 'react';
7
+
8
+ import { Obj, Ref } from '@dxos/echo';
9
+ import { Card } from '@dxos/react-ui';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
+ import { type Actor, Person } from '@dxos/types';
12
+
13
+ import { translations } from '#translations';
14
+
15
+ import { Row } from './Row';
16
+
17
+ const SENDER: Actor.Actor = { name: 'Alice Avery', email: 'alice@example.com' };
18
+
19
+ // Exercises each shared Card-row primitive inside the borderless Card chrome the tiles/headers use.
20
+ const DefaultStory = () => {
21
+ const object = useMemo(() => Obj.make(Person.Person, { fullName: 'Casey Contact' }), []);
22
+ // A second stand-in object so the two attachment refs below have distinct URIs (and React keys).
23
+ const secondObject = useMemo(() => Obj.make(Person.Person, { fullName: 'Dana Reference' }), []);
24
+
25
+ return (
26
+ <Card.Root border={false} fullWidth classNames='p-1'>
27
+ <Card.Body>
28
+ <Row.Star starred onToggle={() => {}} />
29
+ <Row.Person actor={SENDER} role='from' avatar />
30
+ <Row.Person actor={SENDER} role='from' onContactCreate={() => {}} />
31
+ <Row.Date start={new Date('2025-11-19T12:00:00')} end={new Date('2025-11-19T13:30:00')} />
32
+ <Row.Ref object={object} />
33
+ <Row.Attachments
34
+ attachments={[
35
+ { name: 'invoice.pdf', ref: Ref.make(object) },
36
+ { name: 'photo.png', ref: Ref.make(secondObject) },
37
+ ]}
38
+ />
39
+ <Row.Tags
40
+ tags={[
41
+ { id: 'a', label: 'travel', hue: 'cyan' },
42
+ { id: 'b', label: 'urgent', hue: 'rose' },
43
+ ]}
44
+ onTagClick={() => {}}
45
+ />
46
+ </Card.Body>
47
+ </Card.Root>
48
+ );
49
+ };
50
+
51
+ const meta = {
52
+ title: 'ui/react-ui-card/Row',
53
+ render: DefaultStory,
54
+ decorators: [withTheme(), withLayout({ layout: 'column' })],
55
+ parameters: {
56
+ layout: 'fullscreen',
57
+ translations,
58
+ },
59
+ } satisfies Meta<typeof DefaultStory>;
60
+
61
+ export default meta;
62
+
63
+ type Story = StoryObj<typeof meta>;
64
+
65
+ export const Default: Story = {};