Skip to content

Commit d32ea2a

Browse files
feat(ui): add Mosaic Item component (#9234)
1 parent a90a93b commit d32ea2a

16 files changed

Lines changed: 978 additions & 35 deletions

File tree

.changeset/mosaic-item.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

packages/swingset/src/components/DocsViewer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const docModules: Record<string, Record<string, React.ComponentType>> = {
3333
button: dynamic(() => import('../stories/button.mdx')),
3434
card: dynamic(() => import('../stories/card.component.mdx')),
3535
input: dynamic(() => import('../stories/input.mdx')),
36+
item: dynamic(() => import('../stories/item.mdx')),
3637
dialog: dynamic(() => import('../stories/dialog.component.mdx')),
3738
heading: dynamic(() => import('../stories/heading.mdx')),
3839
icon: dynamic(() => import('../stories/icon.mdx')),

packages/swingset/src/lib/registry.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ import {
4646
meta as inputMeta,
4747
Sizes as InputSizes,
4848
} from '../stories/input.stories';
49+
import {
50+
Default as ItemDefault,
51+
Group as ItemGroup,
52+
Interactive as ItemInteractive,
53+
meta as itemMeta,
54+
} from '../stories/item.stories';
4955
import { meta as menuMeta } from '../stories/menu.stories';
5056
import {
5157
Default as OrganizationProfileDefault,
@@ -149,6 +155,13 @@ const inputModule: StoryModule = { meta: inputMeta, Default, Sizes: InputSizes,
149155

150156
const dialogComponentModule: StoryModule = { meta: dialogComponentMeta, Default: DialogDefault };
151157

158+
const itemModule: StoryModule = {
159+
meta: itemMeta,
160+
Default: ItemDefault,
161+
Interactive: ItemInteractive,
162+
Group: ItemGroup,
163+
};
164+
152165
const headingModule: StoryModule = {
153166
meta: headingMeta,
154167
Default: HeadingDefault,
@@ -204,6 +217,7 @@ export const registry: StoryModule[] = [
204217
buttonModule,
205218
cardComponentModule,
206219
inputModule,
220+
itemModule,
207221
dialogComponentModule,
208222
headingModule,
209223
iconModule,
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import * as ItemStories from './item.stories';
2+
3+
# Item
4+
5+
Item is a flexible row for lists of accounts, organizations, and settings in Mosaic. It's composed from parts via dot syntax (`Item.Root`, `Item.Media`, `Item.Content`, `Item.Title`, …). `Item.Root` renders as a `<div>` by default; pass it a `render` prop to make a row an interactive link or button, which adds hover and cursor affordances.
6+
7+
## Example
8+
9+
<Story
10+
name='Default'
11+
storyModule={ItemStories}
12+
/>
13+
14+
### Interactive
15+
16+
<Story
17+
name='Interactive'
18+
storyModule={ItemStories}
19+
/>
20+
21+
### Group
22+
23+
<Story
24+
name='Group'
25+
storyModule={ItemStories}
26+
/>
27+
28+
## Usage
29+
30+
```tsx
31+
import { Avatar } from '@clerk/ui/mosaic/components/avatar';
32+
import { Item } from '@clerk/ui/mosaic/components/item';
33+
34+
<Item.Group>
35+
<Item.Root render={({ children, ...props }) => <a {...props} href='/org'>{children}</a>}>
36+
<Item.Media>
37+
<Avatar.Root shape='square' size='md'>
38+
<Avatar.Image src={org.imageUrl} alt='' />
39+
<Avatar.Fallback>{org.name[0]}</Avatar.Fallback>
40+
</Avatar.Root>
41+
</Item.Media>
42+
<Item.Content>
43+
<Item.Title>Test Organization</Item.Title>
44+
<Item.Description>Member</Item.Description>
45+
</Item.Content>
46+
<Item.Actions>
47+
<Button variant='outline'>Manage</Button>
48+
</Item.Actions>
49+
</Item.Root>
50+
</Item.Group>;
51+
```
52+
53+
## Parts
54+
55+
| Part | Class | Description |
56+
| -------------------- | ------------------------ | -------------------------------------------------------------------- |
57+
| `Item.Root` | `cl-item` | Root row. Renders a `<div>`, or a custom element via `render`. |
58+
| `Item.Media` | `cl-item-media` | Leading (or trailing) media: icon, image, or avatar. |
59+
| `Item.Content` | `cl-item-content` | Vertical stack that grows to fill the row between media and actions. |
60+
| `Item.Title` | `cl-item-title` | Primary label. |
61+
| `Item.Description` | `cl-item-description` | Secondary text. Renders a `<p>`. |
62+
| `Item.Actions` | `cl-item-actions` | Trailing controls (buttons, badges). |
63+
| `Item.Header` | `cl-item-header` | Header row above a group: a label with optional actions. |
64+
| `Item.HeaderTitle` | `cl-item-header-title` | Label text within an `Item.Header`. |
65+
| `Item.HeaderActions` | `cl-item-header-actions` | Trailing controls within an `Item.Header`. |
66+
| `Item.Group` | `cl-item-group` | Vertical wrapper around a set of rows (layout only, no role). |
67+
| `Item.Separator` | `cl-item-separator` | Thin divider (`<hr>`) between rows. |
68+
69+
Every part accepts a `render` prop for element polymorphism and forwards a ref.
70+
71+
## Styling
72+
73+
The root reflects its state as `data-*` attributes on `.cl-item`, so consumers can scope overrides without touching StyleX's hashed atoms:
74+
75+
| Prop | Attribute | Values | Default |
76+
| --------- | ------------------ | ----------------------------------- | -------- |
77+
| `variant` | `data-variant` | `entity` \| `action` | `entity` |
78+
| `render` | `data-interactive` | present when a `render` is provided ||
79+
80+
`variant` sets the row's vertical density (`entity` is standard, `action` is denser) and, on interactive rows, promotes the title color.
81+
82+
```css
83+
/* Re-theme interactive rows */
84+
.cl-item[data-interactive] {
85+
background-color: var(--cl-color-card);
86+
}
87+
```
88+
89+
`Item.Media` sizes to its child and centers it; its height follows the row. Bring your own icon, avatar, or image at whatever dimensions you need, then size the slot by sizing that child. Colors, radii, and spacing all resolve from the Mosaic tokens (`--cl-color-*`, `--cl-radius-*`, `--cl-spacing`).

0 commit comments

Comments
 (0)