-
Notifications
You must be signed in to change notification settings - Fork 460
feat(ui): add Mosaic Item component #9234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a41afe4
1ac76ed
45d7685
497a792
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| --- | ||
| --- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import * as ItemStories from './item.stories'; | ||
|
|
||
| # Item | ||
|
|
||
| Item is a flexible row for lists of accounts, organizations, and settings in Mosaic. It's composed from parts via dot syntax (`Item.Media`, `Item.Content`, `Item.Title`, …) and renders as a `<div>` by default. Pass a `render` prop to make a row an interactive link or button, which adds hover and cursor affordances. | ||
|
|
||
| ## Example | ||
|
|
||
| <Story | ||
| name='Default' | ||
| storyModule={ItemStories} | ||
| /> | ||
|
|
||
| ### Interactive | ||
|
|
||
| <Story | ||
| name='Interactive' | ||
| storyModule={ItemStories} | ||
| /> | ||
|
|
||
| ### Group | ||
|
|
||
| <Story | ||
| name='Group' | ||
| storyModule={ItemStories} | ||
| /> | ||
|
|
||
| ## Usage | ||
|
|
||
| ```tsx | ||
| import { Item } from '@clerk/ui/mosaic/components/item'; | ||
|
|
||
| <Item.Group> | ||
| <Item render={({ children, ...props }) => <a {...props} href='/org'>{children}</a>}> | ||
| <Item.Media> | ||
| <img src={org.imageUrl} alt='' width={36} height={36} /> | ||
| </Item.Media> | ||
| <Item.Content> | ||
| <Item.Title>Test Organization</Item.Title> | ||
| <Item.Description>Member</Item.Description> | ||
| </Item.Content> | ||
| <Item.Actions> | ||
| <Button variant='outline'>Manage</Button> | ||
| </Item.Actions> | ||
| </Item> | ||
| </Item.Group>; | ||
| ``` | ||
|
|
||
| ## Parts | ||
|
|
||
| | Part | Class | Description | | ||
| | ------------------ | --------------------- | -------------------------------------------------------------------- | | ||
| | `Item` | `cl-item` | Root row. Renders a `<div>`, or a custom element via `render`. | | ||
| | `Item.Media` | `cl-item-media` | Leading (or trailing) media: icon, image, or avatar. | | ||
| | `Item.Content` | `cl-item-content` | Vertical stack that grows to fill the row between media and actions. | | ||
| | `Item.Title` | `cl-item-title` | Primary label. | | ||
| | `Item.Description` | `cl-item-description` | Secondary text, clamped to two lines. Renders a `<p>`. | | ||
| | `Item.Actions` | `cl-item-actions` | Trailing controls (buttons, badges). | | ||
| | `Item.Group` | `cl-item-group` | Vertical list wrapper (`role="list"`). | | ||
| | `Item.Separator` | `cl-item-separator` | Thin divider (`<hr>`) between rows. | | ||
|
|
||
| Every part accepts a `render` prop for element polymorphism and forwards a ref. | ||
|
|
||
| ## Styling | ||
|
|
||
| The root reflects its state as `data-*` attributes on `.cl-item`, so consumers can scope overrides without touching StyleX's hashed atoms: | ||
|
|
||
| | Prop | Attribute | Values | Default | | ||
| | -------- | ------------------ | ----------------------------------- | ------- | | ||
| | `render` | `data-interactive` | present when a `render` is provided | — | | ||
|
|
||
| ```css | ||
| /* Re-theme interactive rows */ | ||
| .cl-item[data-interactive] { | ||
| background-color: var(--cl-color-card); | ||
| } | ||
| ``` | ||
|
|
||
| `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 (the examples use a 36px avatar and a 28px trailing icon), then size the slot by sizing that child. Colors, radii, and spacing all resolve from the Mosaic tokens (`--cl-color-*`, `--cl-radius-*`, `--cl-spacing`). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| /** @jsxImportSource @emotion/react */ | ||
| import { Button } from '@clerk/ui/mosaic/components/button'; | ||
| import { Item } from '@clerk/ui/mosaic/components/item'; | ||
|
|
||
| import type { StoryMeta } from '@/lib/types'; | ||
|
|
||
| // Exposes this file's own source (via the `?raw` webpack rule) so each `<Story>` example | ||
| // renders a code footer with its function's source. See `StoryModule.__source`. | ||
| export { default as __source } from './item.stories?raw'; | ||
|
|
||
| export const meta: StoryMeta = { | ||
| group: 'Components', | ||
| title: 'Item', | ||
| source: 'packages/ui/src/mosaic/components/item/item.tsx', | ||
| }; | ||
|
|
||
| // A square org/account avatar sized for the media slot (36px). Since `Item.Media` | ||
| // fits its child, the avatar's own dimensions drive the slot width. | ||
| function Avatar({ initial }: { initial: string }) { | ||
| return ( | ||
| <span | ||
| style={{ | ||
| display: 'inline-flex', | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| width: '2.25rem', | ||
| height: '2.25rem', | ||
| borderRadius: 'var(--cl-radius-inner)', | ||
| backgroundColor: 'var(--cl-color-primary)', | ||
| color: 'var(--cl-color-primary-foreground)', | ||
| fontSize: '0.875rem', | ||
| fontWeight: 600, | ||
| }} | ||
| > | ||
| {initial} | ||
| </span> | ||
| ); | ||
| } | ||
|
|
||
| function ArrowIcon() { | ||
| return ( | ||
| <svg | ||
| width='16' | ||
| height='16' | ||
| viewBox='0 0 24 24' | ||
| fill='none' | ||
| stroke='currentColor' | ||
| strokeWidth='2' | ||
| strokeLinecap='round' | ||
| strokeLinejoin='round' | ||
| > | ||
| <path d='M5 12h14M13 5l7 7-7 7' /> | ||
| </svg> | ||
| ); | ||
| } | ||
|
|
||
| export function Default() { | ||
| return ( | ||
| <Item> | ||
| <Item.Media> | ||
| <Avatar initial='T' /> | ||
| </Item.Media> | ||
| <Item.Content> | ||
| <Item.Title>Test Organization</Item.Title> | ||
| <Item.Description>Member</Item.Description> | ||
| </Item.Content> | ||
| <Item.Actions> | ||
| <Button variant='outline'>Manage</Button> | ||
| </Item.Actions> | ||
| </Item> | ||
| ); | ||
| } | ||
|
|
||
| export function Interactive() { | ||
| return ( | ||
| <Item | ||
| render={({ children, ...props }) => ( | ||
| <a | ||
| {...props} | ||
| href='#settings' | ||
| > | ||
| {children} | ||
| </a> | ||
| )} | ||
| > | ||
| <Item.Media> | ||
| <Avatar initial='T' /> | ||
| </Item.Media> | ||
| <Item.Content> | ||
| <Item.Title>Test Organization</Item.Title> | ||
| <Item.Description>Member</Item.Description> | ||
| </Item.Content> | ||
| <Item.Media style={{ width: '1.75rem' }}> | ||
| <ArrowIcon /> | ||
| </Item.Media> | ||
| </Item> | ||
| ); | ||
| } | ||
|
|
||
| export function Group() { | ||
| return ( | ||
| <Item.Group> | ||
| <Item | ||
| render={({ children, ...props }) => ( | ||
| <a | ||
| {...props} | ||
| href='#one' | ||
| > | ||
| {children} | ||
| </a> | ||
| )} | ||
| > | ||
| <Item.Media> | ||
| <Avatar initial='C' /> | ||
| </Item.Media> | ||
| <Item.Content> | ||
| <Item.Title>Clerk</Item.Title> | ||
| </Item.Content> | ||
| </Item> | ||
| <Item.Separator /> | ||
| <Item | ||
| render={({ children, ...props }) => ( | ||
| <a | ||
| {...props} | ||
| href='#two' | ||
| > | ||
| {children} | ||
| </a> | ||
| )} | ||
| > | ||
| <Item.Media> | ||
| <Avatar initial='C' /> | ||
| </Item.Media> | ||
| <Item.Content> | ||
| <Item.Title>Clerk Cloud</Item.Title> | ||
| </Item.Content> | ||
| </Item> | ||
| </Item.Group> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { Item } from './item'; | ||
| export type { ItemProps } from './item'; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,116 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| import * as stylex from '@stylexjs/stylex'; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| import { colorVars, radiusVars, space, typeScaleVars } from '../../tokens.stylex'; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const item = stylex.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| base: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| padding: space['2'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| borderRadius: radiusVars['--cl-radius-element'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| gap: space['3'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| outline: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| default: 'none', | ||||||||||||||||||||||||||||||||||||||||||||||||
| ':focus-visible': `2px solid color-mix(in oklab, ${colorVars['--cl-color-primary']} 50%, transparent)`, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| alignItems: 'center', | ||||||||||||||||||||||||||||||||||||||||||||||||
| boxSizing: 'border-box', | ||||||||||||||||||||||||||||||||||||||||||||||||
| color: colorVars['--cl-color-card-foreground'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| display: 'flex', | ||||||||||||||||||||||||||||||||||||||||||||||||
| flexWrap: 'wrap', | ||||||||||||||||||||||||||||||||||||||||||||||||
| fontFamily: 'inherit', | ||||||||||||||||||||||||||||||||||||||||||||||||
| fontSize: typeScaleVars['--cl-text-label-size'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| lineHeight: typeScaleVars['--cl-text-label-leading'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| outlineOffset: '2px', | ||||||||||||||||||||||||||||||||||||||||||||||||
| textAlign: 'start', | ||||||||||||||||||||||||||||||||||||||||||||||||
| transitionDuration: '150ms', | ||||||||||||||||||||||||||||||||||||||||||||||||
| transitionProperty: 'background-color, color', | ||||||||||||||||||||||||||||||||||||||||||||||||
| width: '100%', | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // interactive rows (rendered as a link/button via `render`) gain hover + cursor | ||||||||||||||||||||||||||||||||||||||||||||||||
| interactive: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| backgroundColor: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| default: null, | ||||||||||||||||||||||||||||||||||||||||||||||||
| ':active': `color-mix(in oklab, ${colorVars['--cl-color-muted']} 70%, transparent)`, | ||||||||||||||||||||||||||||||||||||||||||||||||
| '@media (hover: hover)': { | ||||||||||||||||||||||||||||||||||||||||||||||||
| ':hover': `color-mix(in oklab, ${colorVars['--cl-color-muted']} 50%, transparent)`, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| cursor: 'pointer', | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const media = stylex.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| // sizes to its child; height follows the row (taller on a 2-line item, shorter on a button). | ||||||||||||||||||||||||||||||||||||||||||||||||
| base: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| gap: space['2'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| alignItems: 'center', | ||||||||||||||||||||||||||||||||||||||||||||||||
| display: 'flex', | ||||||||||||||||||||||||||||||||||||||||||||||||
| flexShrink: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||
| justifyContent: 'center', | ||||||||||||||||||||||||||||||||||||||||||||||||
| width: 'fit-content', | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const content = stylex.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| base: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| display: 'flex', | ||||||||||||||||||||||||||||||||||||||||||||||||
| flexDirection: 'column', | ||||||||||||||||||||||||||||||||||||||||||||||||
| flexGrow: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||
| justifyContent: 'center', | ||||||||||||||||||||||||||||||||||||||||||||||||
| minWidth: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const title = stylex.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| base: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| gap: space['2'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| alignItems: 'center', | ||||||||||||||||||||||||||||||||||||||||||||||||
| color: colorVars['--cl-color-card-foreground'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| display: 'flex', | ||||||||||||||||||||||||||||||||||||||||||||||||
| fontSize: typeScaleVars['--cl-text-label-size'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| fontWeight: typeScaleVars['--cl-text-label-weight'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| lineHeight: typeScaleVars['--cl-text-label-leading'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| width: 'fit-content', | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const description = stylex.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| base: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| overflow: 'hidden', | ||||||||||||||||||||||||||||||||||||||||||||||||
| WebkitBoxOrient: 'vertical', | ||||||||||||||||||||||||||||||||||||||||||||||||
| WebkitLineClamp: 2, | ||||||||||||||||||||||||||||||||||||||||||||||||
| color: colorVars['--cl-color-muted-foreground'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| fontSize: typeScaleVars['--cl-text-label-sm-size'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| fontWeight: 400, | ||||||||||||||||||||||||||||||||||||||||||||||||
| lineHeight: typeScaleVars['--cl-text-label-sm-leading'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+77
to
+87
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
rg -n -C3 'WebkitLineClamp|WebkitBoxOrient|display:\s*.*-webkit-box' packages/ui/srcRepository: clerk/javascript Length of output: 874 🌐 Web query:
💡 Result: The MDN Web Docs documentation on the display CSS property explains that it defines an element's inner and outer display types, which determine how the element participates in layout (outer type) and how its children are laid out (inner type) [1][2][3]. There is no "requirement" for display in the sense of a mandatory setting; rather, the property provides the mechanism to control layout [1][4]. Key technical details include: 1. Two-Value Syntax: The display property can accept one or two keywords [1][5]. The multi-keyword syntax allows developers to explicitly define both the outer and inner display types (e.g., block flow, block flex) [1][5]. 2. Backward Compatibility: The traditional single-keyword values (such as block, inline, flex, grid) are supported for backward compatibility and are considered "legacy" values [1][5]. These single values automatically map to a combined outer/inner value [1][2]. For example, using only an inner keyword like flex or grid causes the outer display type to default to block [1][5]. 3. Layout Modes: The property determines how an element generates boxes and interacts with flow layout, grid, or flexbox, among other formatting contexts [1][3][6]. The CSS Display Module Level 3 specification defines these behaviors and is the technical standard that the MDN documentation reflects [6][4][5]. Citations:
🌐 Web query:
💡 Result: The display: -webkit-box property is technically required if you are using the legacy -webkit-line-clamp property to truncate text [1]. While modern CSS is introducing a standardized line-clamp property, the legacy -webkit-line-clamp approach relies on specific proprietary behaviors associated with the older -webkit-box layout model [1]. For this legacy clamping to function, the following properties must be present [1]: 1. display: -webkit-box or display: -webkit-inline-box [1] 2. -webkit-box-orient: vertical [1] 3. -webkit-line-clamp: [1] If these conditions are not met—specifically if the display value is set to a modern value like flex instead of the legacy -webkit-box—the legacy line-clamp mechanism will not engage, as the element will be treated as a standard flex container rather than the legacy block container required for that specific implementation [1]. Citations: 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== item.styles.ts relevant section =="
sed -n '60,95p' packages/ui/src/mosaic/components/item/item.styles.ts
echo
echo "== item component usages of description =="
rg -n -C4 'item\.description|description\(' packages/ui/src/mosaic/components packages/ui/src | head -n 120
echo
echo "== stylex package/config =="
fd -a 'stylex' package.json tsconfig*.json . | head -n 40Repository: clerk/javascript Length of output: 3203 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== candidate mosaic item files =="
fd -a 'item' packages/ui/src/mosaic/components | sed 's#^\./##'
echo
echo "== imports/usages referencing stylex.create or item styles =="
rg -n "from ['\"]\\.\\.\\/\\.\\.\\/item\\.styles['\"]|from ['\"]\\.\\/item\\.styles['\"]|stylex\\.create|WebkitLineClamp|description" packages/ui/src/mosaic packages/ui/src/components packages/ui/src | head -n 200
echo
echo "== package refs for stylex ="
rg -n '"`@stylexjs/`|stylex' package.json packages -g 'package.json' -g '*.ts' -g '*.tsx' | head -n 120
echo
echo "== stylex declaration/token syntax examples =="
sed -n '1,160p' packages/ui/src/mosaic/tokens.stylex.tsRepository: clerk/javascript Length of output: 39165 🌐 Web query:
💡 Result: The statement that the CSS line-clamp property does not require display is correct for the standardized version of the property, but incorrect for the commonly used, legacy vendor-prefixed version [1][2]. 1. Standardized line-clamp: The standardized CSS line-clamp property (defined in the CSS Overflow Module Level 4) is designed to function without the specific legacy requirements of its predecessor [3][4]. It is a shorthand property that controls line limiting through internal mechanisms (max-lines, block-ellipsis, and continue) and does not depend on the specific display or box-orient properties required by the legacy version [3][2]. 2. Vendor-prefixed -webkit-line-clamp: The widely used legacy property, -webkit-line-clamp, specifically requires the display property to be set to -webkit-box or -webkit-inline-box, in combination with -webkit-box-orient: vertical [1][5][6]. Because this legacy property is still heavily used for compatibility, this dependency is a well-known and documented requirement [1][7]. In summary, while the modern, standardized line-clamp property does not have these restrictions, the prefixed version that developers often encounter in existing codebases strictly requires them [1][2]. Citations:
Enable the two-line clamp.
Proposed fix base: {
+ display: '-webkit-box',
overflow: 'hidden',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 2,📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const actions = stylex.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| base: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| gap: space['2'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| alignItems: 'center', | ||||||||||||||||||||||||||||||||||||||||||||||||
| display: 'flex', | ||||||||||||||||||||||||||||||||||||||||||||||||
| flexShrink: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const group = stylex.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| base: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| display: 'flex', | ||||||||||||||||||||||||||||||||||||||||||||||||
| flexDirection: 'column', | ||||||||||||||||||||||||||||||||||||||||||||||||
| width: '100%', | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const separator = stylex.create({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| base: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| borderStyle: 'none', | ||||||||||||||||||||||||||||||||||||||||||||||||
| marginBlock: space['2'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| marginInline: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||
| backgroundColor: colorVars['--cl-color-border'], | ||||||||||||||||||||||||||||||||||||||||||||||||
| flexShrink: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||
| height: '1px', | ||||||||||||||||||||||||||||||||||||||||||||||||
| width: '100%', | ||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the usage example self-contained.
ButtonandBuildingIconare used but never imported or defined, so this snippet does not compile when copied. Add the publicButtonimport and an icon import/definition, or remove those usages.🤖 Prompt for AI Agents
Source: Coding guidelines