@uisatin/ui 0.1.0 → 0.2.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/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # @uisatin/ui
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c1fdcb5: AvatarGroup: avatars side by side share one name tooltip. The first name still waits 400 ms; after that, moving to the next person slides the tooltip over to them and eases its width to the new name, instead of closing one tooltip and opening another. It closes 150 ms after the pointer leaves the row, so the gaps between faces don't close it. Focus shows it at once; Escape and blur hide it.
8
+
9
+ ### Patch Changes
10
+
11
+ - c1fdcb5: Accordion and expanding groups: the content no longer scales while it opens. Chrome drew the scaled text as a bitmap, so it looked heavier until the animation ended, and the Accordion's open item did it on every page load. The section still grows downwards from under its header.
12
+ - 2fdb885: The package now links its source (`repository`, `bugs`), states `engines.node >=20.19` (it's ESM only), ships its CHANGELOG, and is published with npm provenance from the public repository.
13
+
14
+ ## 0.1.1
15
+
16
+ ### Patch Changes
17
+
18
+ - fd01b6e: Banner and Announcement bar: the mesh glows are now inlined as WebP data URIs (`banner-mesh`), so they show in any app. In 0.1.0 they pointed at `/figma/*.png` on uisatin.dev and were missing everywhere else.
19
+ - cb6f3e1: Type declarations now resolve under TypeScript `moduleResolution` `node16` and `nodenext`: relative imports in the `.d.ts` files carry their `.js` extension (folders point at `index.js`). `bundler` resolution is unchanged.
package/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # @uisatin/ui
2
2
 
3
- Satin's React components: shadcn-based, built on Radix, themed with Satin tokens. Docs: https://uisatin.dev
3
+ [![npm](https://img.shields.io/npm/v/@uisatin/ui)](https://www.npmjs.com/package/@uisatin/ui)
4
+ [![CI](https://github.com/maciej-kownacki/ui-satin/actions/workflows/ci.yml/badge.svg)](https://github.com/maciej-kownacki/ui-satin/actions/workflows/ci.yml)
5
+ [![license: MIT](https://img.shields.io/npm/l/@uisatin/ui)](https://github.com/maciej-kownacki/ui-satin/blob/main/LICENSE)
6
+
7
+ Satin's React components: shadcn-based, built on Radix, themed with Satin tokens.
8
+
9
+ [Docs](https://uisatin.dev) · [Source](https://github.com/maciej-kownacki/ui-satin/tree/main/packages/ui) · [Changelog](https://github.com/maciej-kownacki/ui-satin/blob/main/packages/ui/CHANGELOG.md) · [Issues](https://github.com/maciej-kownacki/ui-satin/issues)
4
10
 
5
11
  **Satin Free.** This package is the free, open-source edition of Satin, under the MIT license. Satin Pro, with more components, patterns and templates, will ship separately as its own licensed package; nothing here changes when it does.
6
12
 
@@ -12,7 +18,9 @@ You don't need Tailwind. Satin ships one precompiled stylesheet with the tokens
12
18
  npm i @uisatin/ui
13
19
  ```
14
20
 
15
- React 19 or newer is a peer dependency.
21
+ Requirements: React 19 or newer (peer dependency). The package is ESM only; Node 20.19 or newer can also `require()` it. Every entry point is marked `'use client'`, so it works in React Server Components apps (Next.js App Router).
22
+
23
+ Versions: Satin is 0.x. Until 1.0 a minor release (0.2 → 0.3) may change an API; patches don't. Every change is in the changelog.
16
24
 
17
25
  ## Use
18
26
 
@@ -1,3 +1,4 @@
1
+ import { bannerMesh } from './chunk-YX5VNRJX.js';
1
2
  import { Button } from './chunk-HNQUX43Z.js';
2
3
  import { cn } from './chunk-H63FYCMY.js';
3
4
  import 'react';
@@ -29,16 +30,6 @@ var bannerVariants = cva(
29
30
  defaultVariants: { layout: "horizontal", shape: "rounded", lines: "one" }
30
31
  }
31
32
  );
32
- var bannerMesh = {
33
- horizontal: {
34
- one: { light: "/figma/banner-mesh-light.png", dark: "/figma/banner-mesh-dark.png" },
35
- two: { light: "/figma/banner-mesh-two-light.png", dark: "/figma/banner-mesh-two-dark.png" }
36
- },
37
- sidebar: {
38
- one: { light: "/figma/banner-mesh-sidebar-light.png", dark: "/figma/banner-mesh-sidebar-dark.png" },
39
- two: { light: "/figma/mesh-light.png", dark: "/figma/mesh-dark.png" }
40
- }
41
- };
42
33
  function Banner({
43
34
  className,
44
35
  title,
@@ -144,6 +135,6 @@ function Banner({
144
135
  );
145
136
  }
146
137
 
147
- export { Banner, bannerMesh, bannerVariants };
148
- //# sourceMappingURL=chunk-TOMSU4S4.js.map
149
- //# sourceMappingURL=chunk-TOMSU4S4.js.map
138
+ export { Banner, bannerVariants };
139
+ //# sourceMappingURL=chunk-6QMSWITQ.js.map
140
+ //# sourceMappingURL=chunk-6QMSWITQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/banner.tsx"],"names":[],"mappings":"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","file":"chunk-6QMSWITQ.js","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { Xmark } from 'iconoir-react'\nimport { cn } from '../lib/utils'\nimport { Button } from './button'\nimport { bannerMesh, type Mesh } from './banner-mesh'\n\n/**\n * Banner, 1:1 with the uiKit `Banner` set (3822:3475): a title, an optional description (one\n * or two lines) and one action, on bg/card with a 1px border/default and the shadcn md shadow.\n *\n * - layout=\"horizontal\": 450 wide, one row, 12 × 8 padding, 10 gap; the action and the close\n * button sit on the right, 4px apart.\n * - layout=\"sidebar\": 222 wide (the navigation column's inner width), 12 padding, 12 gap; copy on\n * top, the action stretched across the bottom, close pinned 3px from the top-right corner.\n * - shape=\"rounded\" is radius-xl (12). shape=\"pill\" is fully round on a one-line horizontal\n * banner and radius-3xl (20) wherever the banner is taller.\n * - background=\"mesh\" lays two pre-rendered glows over the card: a light one in Multiply (shows\n * on white, gone on dark) and a dark one in Screen (glows on dark, gone on white), so it follows\n * the theme without variables. Figma renders one pair per layout and line count; they are\n * inlined in banner-mesh.tsx and can be swapped with `mesh`.\n *\n * tone=\"primary\" turns the card action/primary/bg (border action/primary/border) with\n * action/primary/fg copy (description at 80%); its mesh is the dark glow alone, in Screen, so it\n * lightens the primary. `shimmer` sends a soft band of light across now and then.\n *\n * Title is 14/18 medium, description Body/md in text/muted, 2px apart. The close\n * button only renders when `onDismiss` is passed.\n */\nconst bannerVariants = cva(\n [\n 'relative isolate flex max-w-full overflow-clip border border-border-default bg-bg-card',\n '[box-shadow:0_2px_4px_-2px_var(--effect-shadow-sm),0_4px_6px_-1px_var(--effect-shadow-sm)]',\n ],\n {\n variants: {\n layout: {\n horizontal: 'w-[450px] items-center gap-2.5 px-3 py-2',\n sidebar: 'w-[222px] flex-col items-start gap-3 p-3',\n },\n shape: {\n rounded: 'rounded-xl',\n pill: 'rounded-3xl',\n },\n lines: {\n one: '',\n two: '',\n },\n },\n compoundVariants: [{ layout: 'horizontal', shape: 'pill', lines: 'one', className: 'rounded-pill' }],\n defaultVariants: { layout: 'horizontal', shape: 'rounded', lines: 'one' },\n },\n)\n\n// The glows are inlined (banner-mesh.tsx), so they come with the component wherever it goes.\nexport { bannerMesh, type Mesh } from './banner-mesh'\n\nexport interface BannerProps\n extends Omit<React.ComponentProps<'div'>, 'title'>,\n Omit<VariantProps<typeof bannerVariants>, 'lines'> {\n title: React.ReactNode\n description?: React.ReactNode\n /** One action, usually `<Button variant=\"secondary\">`. Stretches across a sidebar banner. */\n action?: React.ReactNode\n background?: 'mesh' | 'plain'\n /** `primary`: action/primary/bg with action/primary/fg text; its mesh glows lighter on top. */\n tone?: 'default' | 'primary'\n /** Now and then a soft band of light crosses the banner (off under reduced motion). */\n shimmer?: boolean\n /** Shows the close button. */\n onDismiss?: () => void\n /** Accessible name of the close button. */\n dismissLabel?: string\n /** Replace the mesh images. */\n mesh?: Mesh\n}\n\nexport function Banner({\n className,\n title,\n description,\n action,\n layout = 'horizontal',\n background = 'mesh',\n tone = 'default',\n shimmer = false,\n shape = 'rounded',\n onDismiss,\n dismissLabel = 'Dismiss',\n mesh,\n ...props\n}: BannerProps) {\n const lines = description ? 'two' : 'one'\n const layoutKey = layout ?? 'horizontal'\n const glow = mesh ?? bannerMesh[layoutKey][lines]\n const sidebar = layoutKey === 'sidebar'\n const primary = tone === 'primary'\n\n const close = onDismiss && (\n <Button\n variant=\"tertiary\"\n shape=\"rounded\"\n iconOnly\n aria-label={dismissLabel}\n onClick={onDismiss}\n className={cn(\n sidebar && 'absolute top-[3px] right-[3px] z-10',\n primary && 'text-action-primary-fg [&_svg]:text-action-primary-fg satin-hover:bg-action-primary-bg-hover',\n )}\n >\n <Xmark aria-hidden />\n </Button>\n )\n\n return (\n <div\n data-slot=\"banner\"\n data-layout={layoutKey}\n data-background={background}\n data-tone={tone}\n className={cn(\n bannerVariants({ layout, shape, lines }),\n primary && 'border-action-primary-border bg-action-primary-bg',\n shimmer && 'satin-sheen',\n className,\n )}\n {...props}\n >\n {background === 'mesh' && primary && (\n <img\n src={glow.dark}\n alt=\"\"\n aria-hidden\n className=\"pointer-events-none absolute -inset-px -z-10 size-[calc(100%+2px)] max-w-none object-cover mix-blend-screen\"\n />\n )}\n {background === 'mesh' && !primary && (\n <>\n <img\n src={glow.light}\n alt=\"\"\n aria-hidden\n className=\"pointer-events-none absolute -inset-px -z-10 size-[calc(100%+2px)] max-w-none object-cover mix-blend-multiply\"\n />\n <img\n src={glow.dark}\n alt=\"\"\n aria-hidden\n className=\"pointer-events-none absolute -inset-px -z-10 size-[calc(100%+2px)] max-w-none object-cover mix-blend-screen\"\n />\n </>\n )}\n <div\n className={cn(\n 'flex min-w-0 flex-col justify-center text-sm',\n sidebar ? 'w-full' : 'flex-1',\n description && 'gap-0.5',\n )}\n >\n <p className={cn('truncate text-base leading-[18px] font-medium', primary ? 'text-action-primary-fg' : 'text-text-default')}>{title}</p>\n {description && (\n <p className={cn('text-body', primary ? 'text-action-primary-fg/80' : 'text-text-muted', !sidebar && 'truncate')}>{description}</p>\n )}\n </div>\n {sidebar ? (\n <>\n {action && <div className=\"flex w-full items-center *:flex-1\">{action}</div>}\n {close}\n </>\n ) : (\n (action || close) && (\n <div className=\"flex shrink-0 items-center gap-1\">\n {action}\n {close}\n </div>\n )\n )}\n </div>\n )\n}\n\nexport { bannerVariants }\n"]}
@@ -1,4 +1,4 @@
1
- import { bannerMesh } from './chunk-TOMSU4S4.js';
1
+ import { bannerMesh } from './chunk-YX5VNRJX.js';
2
2
  import { Button } from './chunk-HNQUX43Z.js';
3
3
  import { cn } from './chunk-H63FYCMY.js';
4
4
  import 'react';
@@ -91,5 +91,5 @@ function AnnouncementBar({
91
91
  }
92
92
 
93
93
  export { AnnouncementBar };
94
- //# sourceMappingURL=chunk-2YEWSJD7.js.map
95
- //# sourceMappingURL=chunk-2YEWSJD7.js.map
94
+ //# sourceMappingURL=chunk-D7RZNTFC.js.map
95
+ //# sourceMappingURL=chunk-D7RZNTFC.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/announcement-bar.tsx"],"names":[],"mappings":"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","file":"chunk-2YEWSJD7.js","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { ArrowRight, Xmark } from 'iconoir-react'\nimport { cn } from '../lib/utils'\nimport { Button } from './button'\nimport { bannerMesh } from './banner'\n\n/**\n * AnnouncementBar: one line of news across the top (or bottom) edge of the page, with a button\n * to read more and an optional close.\n *\n * `tone` sets the fill: primary (action/primary/bg, the default), inverse (action/inverse/bg) or\n * default (bg/card between two border/subtle rules). The bar is 44 high, the message centred in Body/lg with\n * the action after it, 12px apart: a secondary xs Button with a 14px arrow. The close button,\n * when `onDismiss` is passed, sits 12px from the right edge: a 28px square, the 16px cross at\n * 70% that turns full on hover over action/primary/bg-hover.\n *\n * `background=\"mesh\"` adds the Banner's glow over the primary; `shimmer` sends a soft band of\n * light across now and then.\n *\n * `sticky` keeps it on the top or bottom edge of its scroll container as the page scrolls.\n * Long messages wrap; the close keeps its corner.\n */\nexport interface AnnouncementBarProps extends React.ComponentProps<'div'> {\n /** The button after the message; with `href` it renders as a link. */\n action?: { label: React.ReactNode; href?: string; onClick?: () => void }\n onDismiss?: () => void\n dismissLabel?: string\n /** Sticks to the top or bottom edge of its scroll container. */\n sticky?: 'top' | 'bottom'\n /**\n * `primary`: action/primary/bg (the default). `inverse`: action/inverse/bg, near-black in light\n * and near-white in dark. `default`: bg/card with border/subtle rules above and below, like a Banner.\n */\n tone?: 'primary' | 'inverse' | 'default'\n /** `mesh` lays the Banner's glow over the bar: lighter on primary and inverse, coloured on default. */\n background?: 'plain' | 'mesh'\n /** Now and then a soft band of light crosses the bar (off under reduced motion). */\n shimmer?: boolean\n}\n\nexport function AnnouncementBar({\n action,\n onDismiss,\n dismissLabel = 'Dismiss announcement',\n sticky,\n tone = 'primary',\n background = 'plain',\n shimmer = false,\n children,\n className,\n ...props\n}: AnnouncementBarProps) {\n const toneClass = {\n primary: 'bg-action-primary-bg text-action-primary-fg',\n inverse: 'bg-action-inverse-bg text-text-on-inverse',\n default: 'bg-bg-card text-text-default [box-shadow:inset_0_1px_0_var(--border-subtle),inset_0_-1px_0_var(--border-subtle)]',\n }[tone]\n const closeClass = {\n primary: 'text-action-primary-fg opacity-70 hover:bg-action-primary-bg-hover hover:opacity-100',\n inverse: 'text-text-on-inverse opacity-70 hover:bg-action-inverse-bg-hover hover:opacity-100',\n default: 'text-text-muted hover:bg-bg-hover hover:text-text-default',\n }[tone]\n const label = action && (\n <>\n {action.label}\n <ArrowRight aria-hidden />\n </>\n )\n\n return (\n <div\n role=\"region\"\n aria-label=\"Announcement\"\n data-slot=\"announcement-bar\"\n data-tone={tone}\n className={cn(\n 'relative isolate flex min-h-11 w-full items-center justify-center px-12 py-2 text-base',\n toneClass,\n shimmer && 'satin-sheen',\n sticky === 'top' && 'sticky top-0 z-40',\n sticky === 'bottom' && 'sticky bottom-0 z-40',\n className,\n )}\n {...props}\n >\n {background === 'mesh' && tone === 'default' && (\n <img\n src={bannerMesh.horizontal.one.light}\n alt=\"\"\n aria-hidden\n className=\"pointer-events-none absolute inset-0 -z-10 size-full max-w-none object-cover mix-blend-multiply\"\n />\n )}\n {background === 'mesh' && (\n <img\n src={bannerMesh.horizontal.one.dark}\n alt=\"\"\n aria-hidden\n className=\"pointer-events-none absolute inset-0 -z-10 size-full max-w-none object-cover mix-blend-screen\"\n />\n )}\n <div className=\"flex flex-wrap items-center justify-center gap-x-3 gap-y-1 text-center\">\n <p className=\"m-0\">{children}</p>\n {action &&\n (action.href ? (\n <Button asChild variant=\"secondary\" size=\"xs\" className=\"shrink-0 no-underline\">\n <a href={action.href} onClick={action.onClick}>\n {label}\n </a>\n </Button>\n ) : (\n <Button variant=\"secondary\" size=\"xs\" className=\"shrink-0\" onClick={action.onClick}>\n {label}\n </Button>\n ))}\n </div>\n {onDismiss && (\n <button\n type=\"button\"\n aria-label={dismissLabel}\n onClick={onDismiss}\n className={cn(\n 'absolute top-1/2 right-3 grid size-7 -translate-y-1/2 place-items-center rounded-md outline-none',\n closeClass,\n 'transition-[opacity,background-color] duration-(--duration-fast) ease-(--ease-out)',\n 'focus-visible:opacity-100 focus-visible:[box-shadow:0_0_0_2px_var(--action-focus-ring)]',\n )}\n >\n <Xmark aria-hidden className=\"size-4 [stroke-width:1.875]\" />\n </button>\n )}\n </div>\n )\n}\n"]}
1
+ {"version":3,"sources":["../src/components/announcement-bar.tsx"],"names":[],"mappings":"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","file":"chunk-D7RZNTFC.js","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { ArrowRight, Xmark } from 'iconoir-react'\nimport { cn } from '../lib/utils'\nimport { Button } from './button'\nimport { bannerMesh } from './banner'\n\n/**\n * AnnouncementBar: one line of news across the top (or bottom) edge of the page, with a button\n * to read more and an optional close.\n *\n * `tone` sets the fill: primary (action/primary/bg, the default), inverse (action/inverse/bg) or\n * default (bg/card between two border/subtle rules). The bar is 44 high, the message centred in Body/lg with\n * the action after it, 12px apart: a secondary xs Button with a 14px arrow. The close button,\n * when `onDismiss` is passed, sits 12px from the right edge: a 28px square, the 16px cross at\n * 70% that turns full on hover over action/primary/bg-hover.\n *\n * `background=\"mesh\"` adds the Banner's glow over the primary; `shimmer` sends a soft band of\n * light across now and then.\n *\n * `sticky` keeps it on the top or bottom edge of its scroll container as the page scrolls.\n * Long messages wrap; the close keeps its corner.\n */\nexport interface AnnouncementBarProps extends React.ComponentProps<'div'> {\n /** The button after the message; with `href` it renders as a link. */\n action?: { label: React.ReactNode; href?: string; onClick?: () => void }\n onDismiss?: () => void\n dismissLabel?: string\n /** Sticks to the top or bottom edge of its scroll container. */\n sticky?: 'top' | 'bottom'\n /**\n * `primary`: action/primary/bg (the default). `inverse`: action/inverse/bg, near-black in light\n * and near-white in dark. `default`: bg/card with border/subtle rules above and below, like a Banner.\n */\n tone?: 'primary' | 'inverse' | 'default'\n /** `mesh` lays the Banner's glow over the bar: lighter on primary and inverse, coloured on default. */\n background?: 'plain' | 'mesh'\n /** Now and then a soft band of light crosses the bar (off under reduced motion). */\n shimmer?: boolean\n}\n\nexport function AnnouncementBar({\n action,\n onDismiss,\n dismissLabel = 'Dismiss announcement',\n sticky,\n tone = 'primary',\n background = 'plain',\n shimmer = false,\n children,\n className,\n ...props\n}: AnnouncementBarProps) {\n const toneClass = {\n primary: 'bg-action-primary-bg text-action-primary-fg',\n inverse: 'bg-action-inverse-bg text-text-on-inverse',\n default: 'bg-bg-card text-text-default [box-shadow:inset_0_1px_0_var(--border-subtle),inset_0_-1px_0_var(--border-subtle)]',\n }[tone]\n const closeClass = {\n primary: 'text-action-primary-fg opacity-70 hover:bg-action-primary-bg-hover hover:opacity-100',\n inverse: 'text-text-on-inverse opacity-70 hover:bg-action-inverse-bg-hover hover:opacity-100',\n default: 'text-text-muted hover:bg-bg-hover hover:text-text-default',\n }[tone]\n const label = action && (\n <>\n {action.label}\n <ArrowRight aria-hidden />\n </>\n )\n\n return (\n <div\n role=\"region\"\n aria-label=\"Announcement\"\n data-slot=\"announcement-bar\"\n data-tone={tone}\n className={cn(\n 'relative isolate flex min-h-11 w-full items-center justify-center px-12 py-2 text-base',\n toneClass,\n shimmer && 'satin-sheen',\n sticky === 'top' && 'sticky top-0 z-40',\n sticky === 'bottom' && 'sticky bottom-0 z-40',\n className,\n )}\n {...props}\n >\n {background === 'mesh' && tone === 'default' && (\n <img\n src={bannerMesh.horizontal.one.light}\n alt=\"\"\n aria-hidden\n className=\"pointer-events-none absolute inset-0 -z-10 size-full max-w-none object-cover mix-blend-multiply\"\n />\n )}\n {background === 'mesh' && (\n <img\n src={bannerMesh.horizontal.one.dark}\n alt=\"\"\n aria-hidden\n className=\"pointer-events-none absolute inset-0 -z-10 size-full max-w-none object-cover mix-blend-screen\"\n />\n )}\n <div className=\"flex flex-wrap items-center justify-center gap-x-3 gap-y-1 text-center\">\n <p className=\"m-0\">{children}</p>\n {action &&\n (action.href ? (\n <Button asChild variant=\"secondary\" size=\"xs\" className=\"shrink-0 no-underline\">\n <a href={action.href} onClick={action.onClick}>\n {label}\n </a>\n </Button>\n ) : (\n <Button variant=\"secondary\" size=\"xs\" className=\"shrink-0\" onClick={action.onClick}>\n {label}\n </Button>\n ))}\n </div>\n {onDismiss && (\n <button\n type=\"button\"\n aria-label={dismissLabel}\n onClick={onDismiss}\n className={cn(\n 'absolute top-1/2 right-3 grid size-7 -translate-y-1/2 place-items-center rounded-md outline-none',\n closeClass,\n 'transition-[opacity,background-color] duration-(--duration-fast) ease-(--ease-out)',\n 'focus-visible:opacity-100 focus-visible:[box-shadow:0_0_0_2px_var(--action-focus-ring)]',\n )}\n >\n <Xmark aria-hidden className=\"size-4 [stroke-width:1.875]\" />\n </button>\n )}\n </div>\n )\n}\n"]}
@@ -0,0 +1,166 @@
1
+ import { Tooltip, TooltipBubble } from './chunk-B6V4VSGH.js';
2
+ import { cn } from './chunk-H63FYCMY.js';
3
+ import * as React from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { cva } from 'class-variance-authority';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
+
8
+ var avatarVariants = cva(
9
+ "inline-flex shrink-0 items-center justify-center overflow-clip bg-tone-bg text-center whitespace-nowrap text-tone-fg select-none",
10
+ {
11
+ variants: {
12
+ size: {
13
+ 16: "size-4 text-[7px] leading-[8px] font-semibold tracking-normal",
14
+ 20: "size-5 text-[9px] leading-[10px] font-semibold tracking-normal",
15
+ 24: "size-6 text-2xs font-semibold",
16
+ 32: "size-8 text-sm leading-4 font-medium tracking-normal",
17
+ 40: "size-10 text-base leading-[14px] font-medium tracking-normal",
18
+ 48: "size-12 text-lg font-medium"
19
+ },
20
+ shape: {
21
+ circle: "rounded-full",
22
+ rounded: ""
23
+ }
24
+ },
25
+ compoundVariants: [
26
+ { shape: "rounded", size: 16, className: "rounded-xs" },
27
+ { shape: "rounded", size: 20, className: "rounded-xs" },
28
+ { shape: "rounded", size: 24, className: "rounded-sm" },
29
+ { shape: "rounded", size: 32, className: "rounded-md" },
30
+ { shape: "rounded", size: 40, className: "rounded-lg" },
31
+ { shape: "rounded", size: 48, className: "rounded-xl" }
32
+ ],
33
+ defaultVariants: { size: 32, shape: "circle" }
34
+ }
35
+ );
36
+ var AVATAR_TOOLTIP_DELAY = 400;
37
+ var AVATAR_TOOLTIP_CLOSE = 150;
38
+ var AvatarGroupContext = React.createContext(null);
39
+ function Avatar({ className, initials, tone = "neutral", size = 32, shape, name, ...props }) {
40
+ const group = React.useContext(AvatarGroupContext);
41
+ const grouped = group && name ? group : null;
42
+ const avatar = /* @__PURE__ */ jsx(
43
+ "span",
44
+ {
45
+ "data-slot": "avatar",
46
+ "data-tone": tone,
47
+ ...name ? { role: "img", "aria-label": name, tabIndex: 0 } : {},
48
+ className: cn(
49
+ avatarVariants({ size, shape }),
50
+ name && "outline-none focus-visible:[box-shadow:0_0_0_2px_var(--action-focus-ring)]",
51
+ className
52
+ ),
53
+ ...props,
54
+ ...grouped && {
55
+ onPointerEnter: (e) => (props.onPointerEnter?.(e), grouped.show(e.currentTarget, name)),
56
+ onPointerLeave: (e) => (props.onPointerLeave?.(e), grouped.hide()),
57
+ onFocus: (e) => (props.onFocus?.(e), grouped.show(e.currentTarget, name, true)),
58
+ onBlur: (e) => (props.onBlur?.(e), grouped.hide(true)),
59
+ onKeyDown: (e) => (props.onKeyDown?.(e), e.key === "Escape" && grouped.hide(true))
60
+ },
61
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": name ? true : void 0, children: initials })
62
+ }
63
+ );
64
+ if (!name || grouped) return avatar;
65
+ return /* @__PURE__ */ jsx(Tooltip, { label: name, delay: AVATAR_TOOLTIP_DELAY, closeDelay: AVATAR_TOOLTIP_CLOSE, children: avatar });
66
+ }
67
+ var GAP = 8;
68
+ var BUBBLE_PADDING = 20;
69
+ function AvatarGroup({ className, side = "top", children, ...props }) {
70
+ const [target, setTarget] = React.useState(null);
71
+ const [open, setOpen] = React.useState(false);
72
+ const shown = React.useRef(false);
73
+ const openTimer = React.useRef(void 0);
74
+ const closeTimer = React.useRef(void 0);
75
+ const api = React.useMemo(() => {
76
+ const set = (next) => {
77
+ shown.current = next;
78
+ setOpen(next);
79
+ };
80
+ return {
81
+ show(el, name, now) {
82
+ clearTimeout(closeTimer.current);
83
+ clearTimeout(openTimer.current);
84
+ setTarget({ el, name });
85
+ if (shown.current || now) set(true);
86
+ else openTimer.current = setTimeout(() => set(true), AVATAR_TOOLTIP_DELAY);
87
+ },
88
+ hide(now) {
89
+ clearTimeout(openTimer.current);
90
+ clearTimeout(closeTimer.current);
91
+ if (now) set(false);
92
+ else closeTimer.current = setTimeout(() => set(false), AVATAR_TOOLTIP_CLOSE);
93
+ }
94
+ };
95
+ }, []);
96
+ React.useEffect(
97
+ () => () => {
98
+ clearTimeout(openTimer.current);
99
+ clearTimeout(closeTimer.current);
100
+ },
101
+ []
102
+ );
103
+ return /* @__PURE__ */ jsxs(AvatarGroupContext.Provider, { value: api, children: [
104
+ /* @__PURE__ */ jsx("div", { "data-slot": "avatar-group", role: "group", className: cn("flex items-center gap-2", className), ...props, children }),
105
+ open && target && /* @__PURE__ */ jsx(GroupTooltip, { anchor: target.el, name: target.name, side })
106
+ ] });
107
+ }
108
+ function GroupTooltip({ anchor, name, side }) {
109
+ const label = React.useRef(null);
110
+ const [width, setWidth] = React.useState();
111
+ const [point, setPoint] = React.useState();
112
+ React.useLayoutEffect(() => {
113
+ if (label.current) setWidth(label.current.offsetWidth + BUBBLE_PADDING);
114
+ }, [name]);
115
+ React.useLayoutEffect(() => {
116
+ const place = () => {
117
+ const r = anchor.getBoundingClientRect();
118
+ const half = (width ?? 0) / 2;
119
+ const x = Math.min(Math.max(r.left + r.width / 2, half + GAP), window.innerWidth - half - GAP);
120
+ setPoint({ x, y: side === "top" ? r.top - GAP : r.bottom + GAP });
121
+ };
122
+ place();
123
+ addEventListener("scroll", place, true);
124
+ addEventListener("resize", place);
125
+ return () => {
126
+ removeEventListener("scroll", place, true);
127
+ removeEventListener("resize", place);
128
+ };
129
+ }, [anchor, side, width]);
130
+ if (!point) return null;
131
+ return createPortal(
132
+ // A point at the avatar's edge, moved with transform so a change of person slides; the
133
+ // bubble hangs off it, centred, so a width change grows evenly on both sides.
134
+ /* @__PURE__ */ jsx(
135
+ "div",
136
+ {
137
+ "aria-hidden": true,
138
+ "data-side": side,
139
+ className: "pointer-events-none fixed top-0 left-0 z-50 transition-transform duration-(--duration-base) ease-(--ease-out) motion-reduce:transition-none",
140
+ style: { transform: `translate(${point.x}px, ${point.y}px)` },
141
+ children: /* @__PURE__ */ jsx(
142
+ "div",
143
+ {
144
+ className: cn(
145
+ "-translate-x-1/2 transition-[opacity,translate] duration-(--duration-fast) ease-(--ease-out) starting:opacity-0",
146
+ side === "top" ? "-translate-y-full starting:-translate-y-[calc(100%-4px)]" : "starting:translate-y-[-4px]"
147
+ ),
148
+ children: /* @__PURE__ */ jsx(
149
+ TooltipBubble,
150
+ {
151
+ className: "overflow-hidden transition-[width] duration-(--duration-base) ease-(--ease-out) motion-reduce:transition-none",
152
+ style: { width },
153
+ label: /* @__PURE__ */ jsx("span", { ref: label, className: "inline-block shrink-0 transition-opacity duration-(--duration-fast) ease-(--ease-out) starting:opacity-0", children: name }, name)
154
+ }
155
+ )
156
+ }
157
+ )
158
+ }
159
+ ),
160
+ document.body
161
+ );
162
+ }
163
+
164
+ export { AVATAR_TOOLTIP_DELAY, Avatar, AvatarGroup, avatarVariants };
165
+ //# sourceMappingURL=chunk-MYEZXZZC.js.map
166
+ //# sourceMappingURL=chunk-MYEZXZZC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/avatar.tsx"],"names":[],"mappings":"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","file":"chunk-MYEZXZZC.js","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '../lib/utils'\nimport { Tooltip, TooltipBubble } from './tooltip'\n\nexport type AvatarTone = 'neutral' | 'brand' | 'sky' | 'green' | 'amber' | 'red' | 'fuchsia' | 'plum'\nexport type AvatarSize = 16 | 20 | 24 | 32 | 40 | 48\n\n/**\n * Avatar, 1:1 with the uiKit `Avatar` set (992:20199): initials on a tone fill, the same eight\n * hue pairs as Badge (`tone/bg` fill, `tone/fg` text, no border), six px sizes and a Circle or\n * Rounded shape. Rounded steps the radius with the size: 4 / 4 / 6 / 8 / 10 / 12.\n *\n * Type per size, from Figma: 16 → SemiBold 7/8, 20 → SemiBold 9/10 (no kit token for either),\n * 24 → text/2xs SemiBold, 32 → Label/sm 13/16, 40 → Label/lg 14/14, 48 → text/lg Medium.\n * Groups (docs example 3889:7365) overlap with -8px and stack the first on top.\n *\n * With `name` the avatar says who it is: it becomes an image named after the person, takes\n * keyboard focus, and shows the name in a Tooltip. On hover the tooltip waits 400 ms — the middle\n * of the 0.3–0.5 s NN/g recommends, so a pointer passing over a row of faces doesn't flash names —\n * and stays 150 ms after the pointer leaves. Keyboard focus shows it at once.\n *\n * Avatars side by side go in an `AvatarGroup`: the row shares one tooltip. The first name waits\n * the same 400 ms; after that, moving to the next person slides the tooltip over to them and\n * eases its width to the new name, instead of closing one tooltip and opening another.\n */\nconst avatarVariants = cva(\n 'inline-flex shrink-0 items-center justify-center overflow-clip bg-tone-bg text-center whitespace-nowrap text-tone-fg select-none',\n {\n variants: {\n size: {\n 16: 'size-4 text-[7px] leading-[8px] font-semibold tracking-normal',\n 20: 'size-5 text-[9px] leading-[10px] font-semibold tracking-normal',\n 24: 'size-6 text-2xs font-semibold',\n 32: 'size-8 text-sm leading-4 font-medium tracking-normal',\n 40: 'size-10 text-base leading-[14px] font-medium tracking-normal',\n 48: 'size-12 text-lg font-medium',\n },\n shape: {\n circle: 'rounded-full',\n rounded: '',\n },\n },\n compoundVariants: [\n { shape: 'rounded', size: 16, className: 'rounded-xs' },\n { shape: 'rounded', size: 20, className: 'rounded-xs' },\n { shape: 'rounded', size: 24, className: 'rounded-sm' },\n { shape: 'rounded', size: 32, className: 'rounded-md' },\n { shape: 'rounded', size: 40, className: 'rounded-lg' },\n { shape: 'rounded', size: 48, className: 'rounded-xl' },\n ],\n defaultVariants: { size: 32, shape: 'circle' },\n },\n)\n\nexport interface AvatarProps\n extends Omit<React.ComponentProps<'span'>, 'children'>,\n Omit<VariantProps<typeof avatarVariants>, 'size'> {\n /** One or two letters, or a count like \"+3\" at the end of a group. */\n initials: string\n tone?: AvatarTone\n size?: AvatarSize\n /** The person's name: shown in a tooltip on hover and focus, and read by screen readers. */\n name?: string\n}\n\n/** NN/g: wait 0.3–0.5 s before showing hidden content on hover; we take the middle. */\nexport const AVATAR_TOOLTIP_DELAY = 400\nconst AVATAR_TOOLTIP_CLOSE = 150\n\ntype AvatarGroupApi = { show: (el: HTMLElement, name: string, now?: boolean) => void; hide: (now?: boolean) => void }\nconst AvatarGroupContext = React.createContext<AvatarGroupApi | null>(null)\n\nexport function Avatar({ className, initials, tone = 'neutral', size = 32, shape, name, ...props }: AvatarProps) {\n const group = React.useContext(AvatarGroupContext)\n const grouped = group && name ? group : null\n const avatar = (\n <span\n data-slot=\"avatar\"\n data-tone={tone}\n {...(name ? { role: 'img', 'aria-label': name, tabIndex: 0 } : {})}\n className={cn(\n avatarVariants({ size, shape }),\n name && 'outline-none focus-visible:[box-shadow:0_0_0_2px_var(--action-focus-ring)]',\n className,\n )}\n {...props}\n {...(grouped && {\n onPointerEnter: (e: React.PointerEvent<HTMLSpanElement>) => (props.onPointerEnter?.(e), grouped.show(e.currentTarget, name!)),\n onPointerLeave: (e: React.PointerEvent<HTMLSpanElement>) => (props.onPointerLeave?.(e), grouped.hide()),\n onFocus: (e: React.FocusEvent<HTMLSpanElement>) => (props.onFocus?.(e), grouped.show(e.currentTarget, name!, true)),\n onBlur: (e: React.FocusEvent<HTMLSpanElement>) => (props.onBlur?.(e), grouped.hide(true)),\n onKeyDown: (e: React.KeyboardEvent<HTMLSpanElement>) => (props.onKeyDown?.(e), e.key === 'Escape' && grouped.hide(true)),\n })}\n >\n <span aria-hidden={name ? true : undefined}>{initials}</span>\n </span>\n )\n if (!name || grouped) return avatar\n return (\n <Tooltip label={name} delay={AVATAR_TOOLTIP_DELAY} closeDelay={AVATAR_TOOLTIP_CLOSE}>\n {avatar}\n </Tooltip>\n )\n}\n\nexport interface AvatarGroupProps extends React.ComponentProps<'div'> {\n /** Where the shared name tooltip sits. */\n side?: 'top' | 'bottom'\n}\n\n/** Space the tooltip keeps from the avatar and from the viewport edge, as Tooltip does. */\nconst GAP = 8\n/** The bubble's side padding (10 + 10), added to the measured name for its width. */\nconst BUBBLE_PADDING = 20\n\n/**\n * A row of avatars sharing one name tooltip. It opens after AVATAR_TOOLTIP_DELAY on the first\n * person; while it's open, the next person gets it at once: it slides along the row (transform,\n * 200 ms, ease-out) and its width eases to the new name, which fades in. It closes\n * AVATAR_TOOLTIP_CLOSE after the pointer leaves the row, so the gaps between faces don't\n * close it. Focus shows it at once, Escape and blur hide it.\n */\nexport function AvatarGroup({ className, side = 'top', children, ...props }: AvatarGroupProps) {\n const [target, setTarget] = React.useState<{ el: HTMLElement; name: string } | null>(null)\n const [open, setOpen] = React.useState(false)\n const shown = React.useRef(false)\n const openTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n const closeTimer = React.useRef<ReturnType<typeof setTimeout>>(undefined)\n\n const api = React.useMemo<AvatarGroupApi>(() => {\n const set = (next: boolean) => {\n shown.current = next\n setOpen(next)\n }\n return {\n show(el, name, now) {\n clearTimeout(closeTimer.current)\n clearTimeout(openTimer.current)\n setTarget({ el, name })\n if (shown.current || now) set(true)\n else openTimer.current = setTimeout(() => set(true), AVATAR_TOOLTIP_DELAY)\n },\n hide(now) {\n clearTimeout(openTimer.current)\n clearTimeout(closeTimer.current)\n if (now) set(false)\n else closeTimer.current = setTimeout(() => set(false), AVATAR_TOOLTIP_CLOSE)\n },\n }\n }, [])\n React.useEffect(\n () => () => {\n clearTimeout(openTimer.current)\n clearTimeout(closeTimer.current)\n },\n [],\n )\n\n return (\n <AvatarGroupContext.Provider value={api}>\n <div data-slot=\"avatar-group\" role=\"group\" className={cn('flex items-center gap-2', className)} {...props}>\n {children}\n </div>\n {open && target && <GroupTooltip anchor={target.el} name={target.name} side={side} />}\n </AvatarGroupContext.Provider>\n )\n}\n\nfunction GroupTooltip({ anchor, name, side }: { anchor: HTMLElement; name: string; side: 'top' | 'bottom' }) {\n const label = React.useRef<HTMLSpanElement>(null)\n const [width, setWidth] = React.useState<number>()\n const [point, setPoint] = React.useState<{ x: number; y: number }>()\n\n React.useLayoutEffect(() => {\n if (label.current) setWidth(label.current.offsetWidth + BUBBLE_PADDING)\n }, [name])\n\n React.useLayoutEffect(() => {\n const place = () => {\n const r = anchor.getBoundingClientRect()\n const half = (width ?? 0) / 2\n const x = Math.min(Math.max(r.left + r.width / 2, half + GAP), window.innerWidth - half - GAP)\n setPoint({ x, y: side === 'top' ? r.top - GAP : r.bottom + GAP })\n }\n place()\n addEventListener('scroll', place, true)\n addEventListener('resize', place)\n return () => {\n removeEventListener('scroll', place, true)\n removeEventListener('resize', place)\n }\n }, [anchor, side, width])\n\n if (!point) return null\n return createPortal(\n // A point at the avatar's edge, moved with transform so a change of person slides; the\n // bubble hangs off it, centred, so a width change grows evenly on both sides.\n <div\n aria-hidden\n data-side={side}\n className=\"pointer-events-none fixed top-0 left-0 z-50 transition-transform duration-(--duration-base) ease-(--ease-out) motion-reduce:transition-none\"\n style={{ transform: `translate(${point.x}px, ${point.y}px)` }}\n >\n <div\n className={cn(\n '-translate-x-1/2 transition-[opacity,translate] duration-(--duration-fast) ease-(--ease-out) starting:opacity-0',\n side === 'top' ? '-translate-y-full starting:-translate-y-[calc(100%-4px)]' : 'starting:translate-y-[-4px]',\n )}\n >\n <TooltipBubble\n className=\"overflow-hidden transition-[width] duration-(--duration-base) ease-(--ease-out) motion-reduce:transition-none\"\n style={{ width }}\n label={\n <span key={name} ref={label} className=\"inline-block shrink-0 transition-opacity duration-(--duration-fast) ease-(--ease-out) starting:opacity-0\">\n {name}\n </span>\n }\n />\n </div>\n </div>,\n document.body,\n )\n}\n\nexport { avatarVariants }\n"]}