@goldencomm/baseline-registry 0.0.0-stage → 0.1.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/README.md +26 -2
- package/package.json +15 -4
- package/r/accordion-contained.json +44 -0
- package/r/accordion-split.json +44 -0
- package/r/accordion.json +43 -0
- package/r/alert.json +27 -0
- package/r/aspect-ratio.json +24 -0
- package/r/avatar.json +27 -0
- package/r/badge.json +28 -0
- package/r/base.json +1058 -0
- package/r/blog-card-grid.json +30 -0
- package/r/breadcrumb.json +28 -0
- package/r/button-group.json +28 -0
- package/r/button.json +28 -0
- package/r/card.json +24 -0
- package/r/carousel.json +29 -0
- package/r/checkbox.json +25 -0
- package/r/columns.json +24 -0
- package/r/contact-centered.json +32 -0
- package/r/container.json +27 -0
- package/r/cta-left.json +28 -0
- package/r/dialog.json +31 -0
- package/r/drawer.json +27 -0
- package/r/dropdown-menu.json +28 -0
- package/r/faq-centered.json +28 -0
- package/r/field.json +29 -0
- package/r/header-inline.json +33 -0
- package/r/header-stacked.json +34 -0
- package/r/hero-centered.json +28 -0
- package/r/hero-split.json +29 -0
- package/r/hover-card.json +29 -0
- package/r/icon.json +30 -0
- package/r/input-group.json +30 -0
- package/r/input-otp.json +28 -0
- package/r/input.json +27 -0
- package/r/interest.json +15 -0
- package/r/item.json +29 -0
- package/r/kbd.json +24 -0
- package/r/label.json +24 -0
- package/r/media.json +33 -0
- package/r/meter.json +24 -0
- package/r/native-select.json +25 -0
- package/r/navigation-menu.json +30 -0
- package/r/pagination-bordered.json +27 -0
- package/r/pagination-grouped.json +27 -0
- package/r/pagination.json +26 -0
- package/r/panel.json +27 -0
- package/r/popover.json +28 -0
- package/r/progress.json +34 -0
- package/r/radio.json +24 -0
- package/r/registry.json +2770 -0
- package/r/section.json +27 -0
- package/r/select.json +28 -0
- package/r/separator.json +27 -0
- package/r/sheet.json +27 -0
- package/r/sidebar.json +34 -0
- package/r/spinner.json +25 -0
- package/r/switch.json +27 -0
- package/r/table.json +24 -0
- package/r/tabs-line.json +28 -0
- package/r/tabs.json +27 -0
- package/r/text-styles.json +16 -0
- package/r/textarea.json +24 -0
- package/r/toggle-group.json +52 -0
- package/r/toggle.json +27 -0
- package/r/tooltip.json +62 -0
- package/r/typeset.json +16 -0
- package/r/utils.json +18 -0
package/README.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @goldencomm/baseline-registry
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
GC Baseline's components and blocks as shadcn registry items (`r/<name>.json`), built from the monorepo's
|
|
4
|
+
`registry/` with `shadcn build`. There is no hosted registry (ADR-0034): a project installs items by URL from this
|
|
5
|
+
package through a public npm CDN, pinned to a version.
|
|
6
|
+
|
|
7
|
+
`components.json`:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"registries": {
|
|
12
|
+
"@baseline": "https://cdn.jsdelivr.net/npm/@goldencomm/baseline-registry@0.1.0/r/{name}.json"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx shadcn init https://cdn.jsdelivr.net/npm/@goldencomm/baseline-registry@0.1.0/r/base.json
|
|
19
|
+
npx shadcn add @baseline/button @baseline/header-inline
|
|
20
|
+
npx shadcn diff # what changed against the pinned version
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Updating a project: raise the version in the URL, then `npx shadcn add @baseline/<item> --overwrite` for the items that
|
|
24
|
+
changed. The project's tokens come from `@goldencomm/baseline-tokens` (`baseline-tokens build --platform next --client
|
|
25
|
+
client.tokens.json`), which rewrites the default token values `base` installs; run it after any `base` install.
|
|
26
|
+
|
|
27
|
+
New projects are scaffolded with every item installed: `npm create @goldencomm/baseline@latest`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goldencomm/baseline-registry",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "GC Baseline registry: every component and block as shadcn registry items (r/<name>.json), installed by a version-pinned CDN URL. No hosted registry (ADR-0034).",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"files": [
|
|
8
|
+
"r",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"prepack": "node ../scripts/registry-package.mjs"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "accordion-contained",
|
|
4
|
+
"title": "Accordion (contained)",
|
|
5
|
+
"description": "Baseline accordion, the contained look (ADR-0031: install one look per project; it replaces @baseline/accordion). Spec: components/accordion/spec.md",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"@baseline/utils",
|
|
8
|
+
"@baseline/icon"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "src/ui/accordion/accordion.contained.tsx",
|
|
13
|
+
"content": "// baseline:accordion@0.1.0\n// Fork of shadcn/ui Accordion (base-ui flavour, source read 2026-10-02). Spec: components/accordion/spec.md.\n// THE CONTAINED LOOK (ADR-0031): each item a bordered card (Figma's Single Panel), the cards spaced by\n// accordion/group/gap. accordion.tsx (the default) and the\n// other look file are the alternatives; a project installs one, as components/ui/accordion.tsx. Geometry comes from the accordion/* tokens.\n//\n// Ladder step 1: <details name> + <summary>, not Base UI's Accordion (a <button> trigger and a div panel, ADR-0030).\n// Items sharing a `name` close each other (upstream's single-open default) with no JavaScript; the browser's\n// find-in-page opens a closed item that holds the match. A SERVER COMPONENT (the ADR-0033 approach): `Accordion` hands\n// `name` and `open` to its direct AccordionItem children; there is no controlled value (a ref to the <details> and its\n// `toggle` event do that natively). The height animates through ::details-content where interpolate-size exists\n// (accordion.css.json) and opens instantly elsewhere.\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Icon } from \"@/registry/ui/icon/icon\";\n\ntype AccordionProps = React.ComponentProps<\"div\"> & {\n /** false (default): one item open at a time (the items share a native `name`); true: independent items. */\n multiple?: boolean;\n /** The values of the items open at first. */\n defaultValue?: string[];\n /** The shared name, when you need a stable one; generated otherwise. */\n name?: string;\n};\n\nfunction Accordion({ multiple = false, defaultValue = [], name, className, children, ...props }: AccordionProps) {\n const id = React.useId();\n const group = multiple ? undefined : (name ?? `accordion${id}`);\n return (\n <div data-slot=\"accordion\" className={cn(\"flex w-full flex-col gap-(--bl-accordion-group-gap)\", className)} {...props}>\n {React.Children.map(children, (child) => {\n if (!React.isValidElement<AccordionItemProps>(child) || child.type !== AccordionItem) return child;\n const { value, name: own, defaultOpen } = child.props;\n return React.cloneElement(child, {\n name: own ?? group,\n defaultOpen: defaultOpen ?? (value !== undefined && defaultValue.includes(value)),\n });\n })}\n </div>\n );\n}\n\ntype AccordionItemProps = Omit<React.ComponentProps<\"details\">, \"open\"> & {\n value?: string;\n /** Open at first (the native `open` attribute); Accordion sets it from its defaultValue. */\n defaultOpen?: boolean;\n};\n\nfunction AccordionItem({ value, defaultOpen, className, ...props }: AccordionItemProps) {\n return (\n <details\n data-slot=\"accordion-item\"\n data-value={value}\n open={defaultOpen || undefined}\n className={cn(\n \"group/accordion-item rounded-(--bl-accordion-radius) border-(length:--bl-accordion-border-width) border-border bg-card px-(--bl-accordion-padding-x) text-card-foreground\",\n // open and hovered (hover wins): a tint layered over the card fill, a border, the text colour\n \"open:border-(--bl-accordion-border-open) open:bg-[image:linear-gradient(var(--bl-accordion-background-open),var(--bl-accordion-background-open))] open:text-(--bl-accordion-foreground-open)\",\n \"has-[>summary:hover]:border-(--bl-accordion-border-hover) has-[>summary:hover]:bg-[image:linear-gradient(var(--bl-accordion-background-hover),var(--bl-accordion-background-hover))]\",\n className,\n )}\n {...props}\n />\n );\n}\n\ntype AccordionTriggerProps = React.ComponentProps<\"summary\"> & {\n /** The heading inside the summary (upstream's Header is an h3). */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n};\n\nfunction AccordionTrigger({ headingLevel = 3, className, children, ...props }: AccordionTriggerProps) {\n const Heading = `h${headingLevel}` as const;\n return (\n <summary\n data-slot=\"accordion-trigger\"\n className={cn(\n \"group/accordion-trigger relative flex cursor-pointer list-none items-start justify-between gap-(--bl-accordion-trigger-gap)\",\n \"rounded-lg border border-transparent py-(--bl-accordion-padding-y) text-left outline-none transition-all\",\n \"[&::-webkit-details-marker]:hidden hover:[&>[data-slot=accordion-title]]:underline\",\n // focus keeps Baseline's ring role (already translucent), as Button's\n \"focus-visible:ring-(length:--bl-control-ring-width) focus-visible:ring-ring\",\n \"[&>[data-slot=icon]]:size-(--bl-accordion-trigger-icon-size) [&>[data-slot=icon]]:shrink-0 [&>[data-slot=icon]]:text-muted-foreground\",\n \"hover:text-(--bl-accordion-foreground-hover)\",\n // the chevron's open and hover colours (hover wins); muted-foreground by default\n \"group-open/accordion-item:[&>[data-slot=icon]]:text-(--bl-accordion-icon-open) hover:[&>[data-slot=icon]]:text-(--bl-accordion-icon-hover)\",\n className,\n )}\n {...props}\n >\n <Heading\n data-slot=\"accordion-title\"\n className=\"m-0 flex-1 text-(length:--bl-accordion-title-font-size) leading-(--bl-accordion-title-line-height) font-(--bl-accordion-title-font-weight)\"\n >\n {children}\n </Heading>\n <Icon name=\"chevron-down\" aria-hidden className=\"pointer-events-none group-open/accordion-item:hidden\" />\n <Icon name=\"chevron-up\" aria-hidden className=\"pointer-events-none hidden group-open/accordion-item:block\" />\n </summary>\n );\n}\n\nfunction AccordionContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"accordion-content\"\n className={cn(\n \"pb-(--bl-accordion-padding-y) text-(length:--bl-accordion-content-font-size) leading-(--bl-accordion-content-line-height)\",\n \"[&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Accordion, AccordionContent, AccordionItem, AccordionTrigger };\nexport type { AccordionItemProps, AccordionProps, AccordionTriggerProps };\n",
|
|
14
|
+
"type": "registry:ui",
|
|
15
|
+
"target": "components/ui/accordion.tsx"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"css": {
|
|
19
|
+
"[data-slot=accordion-item]": {
|
|
20
|
+
"interpolate-size": "allow-keywords"
|
|
21
|
+
},
|
|
22
|
+
"[data-slot=accordion-item]::details-content": {
|
|
23
|
+
"block-size": "0",
|
|
24
|
+
"overflow-y": "clip",
|
|
25
|
+
"transition": "block-size 200ms ease-out, content-visibility 200ms allow-discrete"
|
|
26
|
+
},
|
|
27
|
+
"[data-slot=accordion-item][open]::details-content": {
|
|
28
|
+
"block-size": "auto"
|
|
29
|
+
},
|
|
30
|
+
"@media (prefers-reduced-motion: reduce)": {
|
|
31
|
+
"[data-slot=accordion-item]::details-content": {
|
|
32
|
+
"transition": "none"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"meta": {
|
|
37
|
+
"spec": "components/accordion/spec.md",
|
|
38
|
+
"figma": "391:58",
|
|
39
|
+
"upstream": "shadcn/ui accordion (base-ui flavour, source read from ui.shadcn.com 2026-10-02)",
|
|
40
|
+
"status": "ready",
|
|
41
|
+
"look": "contained"
|
|
42
|
+
},
|
|
43
|
+
"type": "registry:ui"
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "accordion-split",
|
|
4
|
+
"title": "Accordion (split)",
|
|
5
|
+
"description": "Baseline accordion, the split look (ADR-0031: install one look per project; it replaces @baseline/accordion). Spec: components/accordion/spec.md",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"@baseline/utils",
|
|
8
|
+
"@baseline/icon"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "src/ui/accordion/accordion.split.tsx",
|
|
13
|
+
"content": "// baseline:accordion@0.1.0\n// Fork of shadcn/ui Accordion (base-ui flavour, source read 2026-10-02). Spec: components/accordion/spec.md.\n// THE SPLIT LOOK (ADR-0031): each item a bordered card whose header is a bordered box of its own above\n// the content (Figma's Split Panel), the cards spaced by accordion/group/gap. accordion.tsx (the default) and the\n// other look file are the alternatives; a project installs one, as components/ui/accordion.tsx. Geometry comes from the accordion/* tokens.\n//\n// Ladder step 1: <details name> + <summary>, not Base UI's Accordion (a <button> trigger and a div panel, ADR-0030).\n// Items sharing a `name` close each other (upstream's single-open default) with no JavaScript; the browser's\n// find-in-page opens a closed item that holds the match. A SERVER COMPONENT (the ADR-0033 approach): `Accordion` hands\n// `name` and `open` to its direct AccordionItem children; there is no controlled value (a ref to the <details> and its\n// `toggle` event do that natively). The height animates through ::details-content where interpolate-size exists\n// (accordion.css.json) and opens instantly elsewhere.\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Icon } from \"@/registry/ui/icon/icon\";\n\ntype AccordionProps = React.ComponentProps<\"div\"> & {\n /** false (default): one item open at a time (the items share a native `name`); true: independent items. */\n multiple?: boolean;\n /** The values of the items open at first. */\n defaultValue?: string[];\n /** The shared name, when you need a stable one; generated otherwise. */\n name?: string;\n};\n\nfunction Accordion({ multiple = false, defaultValue = [], name, className, children, ...props }: AccordionProps) {\n const id = React.useId();\n const group = multiple ? undefined : (name ?? `accordion${id}`);\n return (\n <div data-slot=\"accordion\" className={cn(\"flex w-full flex-col gap-(--bl-accordion-group-gap)\", className)} {...props}>\n {React.Children.map(children, (child) => {\n if (!React.isValidElement<AccordionItemProps>(child) || child.type !== AccordionItem) return child;\n const { value, name: own, defaultOpen } = child.props;\n return React.cloneElement(child, {\n name: own ?? group,\n defaultOpen: defaultOpen ?? (value !== undefined && defaultValue.includes(value)),\n });\n })}\n </div>\n );\n}\n\ntype AccordionItemProps = Omit<React.ComponentProps<\"details\">, \"open\"> & {\n value?: string;\n /** Open at first (the native `open` attribute); Accordion sets it from its defaultValue. */\n defaultOpen?: boolean;\n};\n\nfunction AccordionItem({ value, defaultOpen, className, ...props }: AccordionItemProps) {\n return (\n <details\n data-slot=\"accordion-item\"\n data-value={value}\n open={defaultOpen || undefined}\n className={cn(\n \"group/accordion-item rounded-(--bl-accordion-radius) border-(length:--bl-accordion-border-width) border-border bg-card text-card-foreground\",\n className,\n )}\n {...props}\n />\n );\n}\n\ntype AccordionTriggerProps = React.ComponentProps<\"summary\"> & {\n /** The heading inside the summary (upstream's Header is an h3). */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n};\n\nfunction AccordionTrigger({ headingLevel = 3, className, children, ...props }: AccordionTriggerProps) {\n const Heading = `h${headingLevel}` as const;\n return (\n <summary\n data-slot=\"accordion-trigger\"\n className={cn(\n \"group/accordion-trigger relative flex cursor-pointer list-none items-start justify-between gap-(--bl-accordion-trigger-gap)\",\n \"-m-(--bl-accordion-border-width) rounded-(--bl-accordion-radius) border-(length:--bl-accordion-border-width) border-border bg-card px-(--bl-accordion-padding-x) py-(--bl-accordion-padding-y) text-left outline-none transition-all\",\n // open and hovered (hover wins): a tint layered over the card fill, a border, the text colour; the trigger only\n \"group-open/accordion-item:border-(--bl-accordion-border-open) group-open/accordion-item:bg-[image:linear-gradient(var(--bl-accordion-background-open),var(--bl-accordion-background-open))] group-open/accordion-item:text-(--bl-accordion-foreground-open)\",\n \"hover:border-(--bl-accordion-border-hover) hover:bg-[image:linear-gradient(var(--bl-accordion-background-hover),var(--bl-accordion-background-hover))] hover:text-(--bl-accordion-foreground-hover)\",\n \"[&::-webkit-details-marker]:hidden hover:[&>[data-slot=accordion-title]]:underline\",\n // focus keeps Baseline's ring role (already translucent), as Button's\n \"focus-visible:ring-(length:--bl-control-ring-width) focus-visible:ring-ring\",\n \"[&>[data-slot=icon]]:size-(--bl-accordion-trigger-icon-size) [&>[data-slot=icon]]:shrink-0 [&>[data-slot=icon]]:text-muted-foreground\",\n // the chevron's open and hover colours (hover wins); muted-foreground by default\n \"group-open/accordion-item:[&>[data-slot=icon]]:text-(--bl-accordion-icon-open) hover:[&>[data-slot=icon]]:text-(--bl-accordion-icon-hover)\",\n className,\n )}\n {...props}\n >\n <Heading\n data-slot=\"accordion-title\"\n className=\"m-0 flex-1 text-(length:--bl-accordion-title-font-size) leading-(--bl-accordion-title-line-height) font-(--bl-accordion-title-font-weight)\"\n >\n {children}\n </Heading>\n <Icon name=\"chevron-down\" aria-hidden className=\"pointer-events-none group-open/accordion-item:hidden\" />\n <Icon name=\"chevron-up\" aria-hidden className=\"pointer-events-none hidden group-open/accordion-item:block\" />\n </summary>\n );\n}\n\nfunction AccordionContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"accordion-content\"\n className={cn(\n \"px-(--bl-accordion-padding-x) py-(--bl-accordion-padding-y) text-(length:--bl-accordion-content-font-size) leading-(--bl-accordion-content-line-height)\",\n \"[&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Accordion, AccordionContent, AccordionItem, AccordionTrigger };\nexport type { AccordionItemProps, AccordionProps, AccordionTriggerProps };\n",
|
|
14
|
+
"type": "registry:ui",
|
|
15
|
+
"target": "components/ui/accordion.tsx"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"css": {
|
|
19
|
+
"[data-slot=accordion-item]": {
|
|
20
|
+
"interpolate-size": "allow-keywords"
|
|
21
|
+
},
|
|
22
|
+
"[data-slot=accordion-item]::details-content": {
|
|
23
|
+
"block-size": "0",
|
|
24
|
+
"overflow-y": "clip",
|
|
25
|
+
"transition": "block-size 200ms ease-out, content-visibility 200ms allow-discrete"
|
|
26
|
+
},
|
|
27
|
+
"[data-slot=accordion-item][open]::details-content": {
|
|
28
|
+
"block-size": "auto"
|
|
29
|
+
},
|
|
30
|
+
"@media (prefers-reduced-motion: reduce)": {
|
|
31
|
+
"[data-slot=accordion-item]::details-content": {
|
|
32
|
+
"transition": "none"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"meta": {
|
|
37
|
+
"spec": "components/accordion/spec.md",
|
|
38
|
+
"figma": "391:58",
|
|
39
|
+
"upstream": "shadcn/ui accordion (base-ui flavour, source read from ui.shadcn.com 2026-10-02)",
|
|
40
|
+
"status": "ready",
|
|
41
|
+
"look": "split"
|
|
42
|
+
},
|
|
43
|
+
"type": "registry:ui"
|
|
44
|
+
}
|
package/r/accordion.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "accordion",
|
|
4
|
+
"title": "Accordion",
|
|
5
|
+
"description": "Baseline accordion (ready). Spec: components/accordion/spec.md",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"@baseline/utils",
|
|
8
|
+
"@baseline/icon"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "src/ui/accordion/accordion.tsx",
|
|
13
|
+
"content": "// baseline:accordion@0.1.0\n// Fork of shadcn/ui Accordion (base-ui flavour, source read 2026-10-02). Spec: components/accordion/spec.md.\n// THE DEFAULT LOOK (ADR-0031): upstream's, a rule between items. accordion.contained.tsx and accordion.split.tsx are the\n// other looks; a project installs one, as components/ui/accordion.tsx. Geometry comes from the accordion/* tokens.\n//\n// Ladder step 1: <details name> + <summary>, not Base UI's Accordion (a <button> trigger and a div panel, ADR-0030).\n// Items sharing a `name` close each other (upstream's single-open default) with no JavaScript; the browser's\n// find-in-page opens a closed item that holds the match. A SERVER COMPONENT (the ADR-0033 approach): `Accordion` hands\n// `name` and `open` to its direct AccordionItem children; there is no controlled value (a ref to the <details> and its\n// `toggle` event do that natively). The height animates through ::details-content where interpolate-size exists\n// (accordion.css.json) and opens instantly elsewhere.\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Icon } from \"@/registry/ui/icon/icon\";\n\ntype AccordionProps = React.ComponentProps<\"div\"> & {\n /** false (default): one item open at a time (the items share a native `name`); true: independent items. */\n multiple?: boolean;\n /** The values of the items open at first. */\n defaultValue?: string[];\n /** The shared name, when you need a stable one; generated otherwise. */\n name?: string;\n};\n\nfunction Accordion({ multiple = false, defaultValue = [], name, className, children, ...props }: AccordionProps) {\n const id = React.useId();\n const group = multiple ? undefined : (name ?? `accordion${id}`);\n return (\n <div data-slot=\"accordion\" className={cn(\"flex w-full flex-col\", className)} {...props}>\n {React.Children.map(children, (child) => {\n if (!React.isValidElement<AccordionItemProps>(child) || child.type !== AccordionItem) return child;\n const { value, name: own, defaultOpen } = child.props;\n return React.cloneElement(child, {\n name: own ?? group,\n defaultOpen: defaultOpen ?? (value !== undefined && defaultValue.includes(value)),\n });\n })}\n </div>\n );\n}\n\ntype AccordionItemProps = Omit<React.ComponentProps<\"details\">, \"open\"> & {\n value?: string;\n /** Open at first (the native `open` attribute); Accordion sets it from its defaultValue. */\n defaultOpen?: boolean;\n};\n\nfunction AccordionItem({ value, defaultOpen, className, ...props }: AccordionItemProps) {\n return (\n <details\n data-slot=\"accordion-item\"\n data-value={value}\n open={defaultOpen || undefined}\n className={cn(\"group/accordion-item not-last:border-b border-border\", className)}\n {...props}\n />\n );\n}\n\ntype AccordionTriggerProps = React.ComponentProps<\"summary\"> & {\n /** The heading inside the summary (upstream's Header is an h3). */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n};\n\nfunction AccordionTrigger({ headingLevel = 3, className, children, ...props }: AccordionTriggerProps) {\n const Heading = `h${headingLevel}` as const;\n return (\n <summary\n data-slot=\"accordion-trigger\"\n className={cn(\n \"group/accordion-trigger relative flex cursor-pointer list-none items-start justify-between gap-(--bl-accordion-trigger-gap)\",\n \"rounded-lg border border-transparent py-(--bl-accordion-padding-y) text-left outline-none transition-all\",\n \"[&::-webkit-details-marker]:hidden hover:[&>[data-slot=accordion-title]]:underline\",\n // focus keeps Baseline's ring role (already translucent), as Button's\n \"focus-visible:ring-(length:--bl-control-ring-width) focus-visible:ring-ring\",\n \"[&>[data-slot=icon]]:size-(--bl-accordion-trigger-icon-size) [&>[data-slot=icon]]:shrink-0 [&>[data-slot=icon]]:text-muted-foreground\",\n className,\n )}\n {...props}\n >\n <Heading\n data-slot=\"accordion-title\"\n className=\"m-0 flex-1 text-(length:--bl-accordion-title-font-size) leading-(--bl-accordion-title-line-height) font-(--bl-accordion-title-font-weight)\"\n >\n {children}\n </Heading>\n <Icon name=\"chevron-down\" aria-hidden className=\"pointer-events-none group-open/accordion-item:hidden\" />\n <Icon name=\"chevron-up\" aria-hidden className=\"pointer-events-none hidden group-open/accordion-item:block\" />\n </summary>\n );\n}\n\nfunction AccordionContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"accordion-content\"\n className={cn(\n \"pb-(--bl-accordion-padding-y) text-(length:--bl-accordion-content-font-size) leading-(--bl-accordion-content-line-height)\",\n \"[&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Accordion, AccordionContent, AccordionItem, AccordionTrigger };\nexport type { AccordionItemProps, AccordionProps, AccordionTriggerProps };\n",
|
|
14
|
+
"type": "registry:ui",
|
|
15
|
+
"target": "components/ui/accordion.tsx"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"css": {
|
|
19
|
+
"[data-slot=accordion-item]": {
|
|
20
|
+
"interpolate-size": "allow-keywords"
|
|
21
|
+
},
|
|
22
|
+
"[data-slot=accordion-item]::details-content": {
|
|
23
|
+
"block-size": "0",
|
|
24
|
+
"overflow-y": "clip",
|
|
25
|
+
"transition": "block-size 200ms ease-out, content-visibility 200ms allow-discrete"
|
|
26
|
+
},
|
|
27
|
+
"[data-slot=accordion-item][open]::details-content": {
|
|
28
|
+
"block-size": "auto"
|
|
29
|
+
},
|
|
30
|
+
"@media (prefers-reduced-motion: reduce)": {
|
|
31
|
+
"[data-slot=accordion-item]::details-content": {
|
|
32
|
+
"transition": "none"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"meta": {
|
|
37
|
+
"spec": "components/accordion/spec.md",
|
|
38
|
+
"figma": "391:58",
|
|
39
|
+
"upstream": "shadcn/ui accordion (base-ui flavour, source read from ui.shadcn.com 2026-10-02)",
|
|
40
|
+
"status": "ready"
|
|
41
|
+
},
|
|
42
|
+
"type": "registry:ui"
|
|
43
|
+
}
|
package/r/alert.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "alert",
|
|
4
|
+
"title": "Alert",
|
|
5
|
+
"description": "Baseline alert (ready). Spec: components/alert/spec.md",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"class-variance-authority"
|
|
8
|
+
],
|
|
9
|
+
"registryDependencies": [
|
|
10
|
+
"@baseline/utils"
|
|
11
|
+
],
|
|
12
|
+
"files": [
|
|
13
|
+
{
|
|
14
|
+
"path": "src/ui/alert/alert.tsx",
|
|
15
|
+
"content": "// baseline:alert@0.1.0\n// Fork of shadcn/ui Alert (base-ui flavour, source read 2026-09-30). Spec: components/alert/spec.md.\n// Geometry comes from Figma through the alert/* tokens (ADR-0017); the destructive description is the\n// alert/destructive/description-foreground tint (destructive at 90 %, ADR-0017 §2, emitted as color-mix()).\n//\n// Ladder step 1: a div with role=\"alert\", as upstream; no primitive, no JS. Server-safe.\nimport * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/utils\";\n\n// Upstream's icon rules target `> svg`; Baseline's <Icon> is a span, so each rule names both.\nconst alertVariants = cva(\n [\n \"group/alert relative grid w-full gap-(--bl-alert-content-gap) text-left\",\n \"rounded-(--bl-alert-radius) border-(length:--bl-alert-border-width) border-border\",\n \"px-(--bl-alert-padding-x) py-(--bl-alert-padding-y)\",\n \"text-(length:--bl-alert-description-font-size) leading-(--bl-alert-description-line-height)\",\n // Baseline deviation (user, 2026-10-02): the action is the grid's last column, beside the text, not upstream's\n // absolute top-right box over a fixed pr-18. The row makes room for whatever action it holds, so the text never runs\n // under it (as Item's actions). The column gap is alert/gap.\n \"has-data-[slot=alert-action]:grid-cols-[1fr_auto] has-data-[slot=alert-action]:gap-x-(--bl-alert-gap)\",\n \"has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-(--bl-alert-gap)\",\n \"has-[>[data-slot=icon]]:grid-cols-[auto_1fr] has-[>[data-slot=icon]]:gap-x-(--bl-alert-gap)\",\n \"has-[>svg]:has-data-[slot=alert-action]:grid-cols-[auto_1fr_auto] has-[>[data-slot=icon]]:has-data-[slot=alert-action]:grid-cols-[auto_1fr_auto]\",\n \"*:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg]:size-(--bl-alert-icon-size)\",\n \"*:data-[slot=icon]:row-span-2 *:data-[slot=icon]:translate-y-0.5 *:data-[slot=icon]:size-(--bl-alert-icon-size)\",\n ].join(\" \"),\n {\n variants: {\n variant: {\n default: \"bg-card text-card-foreground\",\n destructive:\n \"bg-card text-destructive *:data-[slot=alert-description]:text-(--bl-alert-destructive-description-foreground)\",\n },\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/**\n * An inline, persistent callout. Success and warning are not variants: they are an Alert inside the Success or Warning\n * scheme (`data-scheme`), which recolours the whole callout.\n *\n * role=\"alert\" announces on insertion. An alert that is on the page from the start and never changes should pass\n * role=\"status\" (or none), or it interrupts the screen reader for no reason.\n */\nfunction Alert({ className, variant, ...props }: React.ComponentProps<\"div\"> & VariantProps<typeof alertVariants>) {\n return <div data-slot=\"alert\" role=\"alert\" className={cn(alertVariants({ variant }), className)} {...props} />;\n}\n\nfunction AlertTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-title\"\n className={cn(\n \"font-(--bl-alert-title-font-weight) text-(length:--bl-alert-title-font-size) leading-(--bl-alert-title-line-height)\",\n \"group-has-[>svg]/alert:col-start-2 group-has-[>[data-slot=icon]]/alert:col-start-2\",\n \"[&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction AlertDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-description\"\n className={cn(\n \"text-(length:--bl-alert-description-font-size) leading-(--bl-alert-description-line-height) text-balance text-muted-foreground md:text-pretty\",\n \"group-has-[>svg]/alert:col-start-2 group-has-[>[data-slot=icon]]/alert:col-start-2\",\n \"[&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction AlertAction({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-action\"\n className={cn(\"col-end-[-1] row-span-2 row-start-1 self-start\", className)}\n {...props}\n />\n );\n}\n\nexport { Alert, AlertTitle, AlertDescription, AlertAction, alertVariants };\n",
|
|
16
|
+
"type": "registry:ui",
|
|
17
|
+
"target": "components/ui/alert.tsx"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"meta": {
|
|
21
|
+
"spec": "components/alert/spec.md",
|
|
22
|
+
"figma": "4356:14021",
|
|
23
|
+
"upstream": "shadcn/ui alert (base-ui flavour, source read from ui.shadcn.com 2026-09-30)",
|
|
24
|
+
"status": "ready"
|
|
25
|
+
},
|
|
26
|
+
"type": "registry:ui"
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "aspect-ratio",
|
|
4
|
+
"title": "Aspect-ratio",
|
|
5
|
+
"description": "Baseline aspect-ratio (ready). Spec: components/aspect-ratio/spec.md",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"@baseline/utils"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "src/ui/aspect-ratio/aspect-ratio.tsx",
|
|
12
|
+
"content": "// baseline:aspect-ratio@0.1.0\n// shadcn/ui Aspect Ratio (base-ui flavour, source read 2026-10-01), taken as-is. Spec: components/aspect-ratio/spec.md.\n// It exists for a ratio known only at runtime (Media's \"adapt to the image\"): the number goes into --ratio and one\n// static class reads it, so no class is ever interpolated. A ratio known when the code is written can use\n// Tailwind's aspect-* classes directly. Plain div, no JS: server-safe.\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nfunction AspectRatio({ ratio, className, ...props }: React.ComponentProps<\"div\"> & { ratio: number }) {\n return (\n <div\n data-slot=\"aspect-ratio\"\n style={{ \"--ratio\": ratio } as React.CSSProperties}\n className={cn(\"relative aspect-(--ratio)\", className)}\n {...props}\n />\n );\n}\n\nexport { AspectRatio };\n",
|
|
13
|
+
"type": "registry:ui",
|
|
14
|
+
"target": "components/ui/aspect-ratio.tsx"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"meta": {
|
|
18
|
+
"spec": "components/aspect-ratio/spec.md",
|
|
19
|
+
"figma": "2249:44210",
|
|
20
|
+
"upstream": "shadcn/ui aspect-ratio (base-ui flavour, source read from ui.shadcn.com 2026-09-30)",
|
|
21
|
+
"status": "ready"
|
|
22
|
+
},
|
|
23
|
+
"type": "registry:ui"
|
|
24
|
+
}
|
package/r/avatar.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "avatar",
|
|
4
|
+
"title": "Avatar",
|
|
5
|
+
"description": "Baseline avatar (ready). Spec: components/avatar/spec.md",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"@base-ui/react"
|
|
8
|
+
],
|
|
9
|
+
"registryDependencies": [
|
|
10
|
+
"@baseline/utils"
|
|
11
|
+
],
|
|
12
|
+
"files": [
|
|
13
|
+
{
|
|
14
|
+
"path": "src/ui/avatar/avatar.tsx",
|
|
15
|
+
"content": "// baseline:avatar@0.1.0\n// Fork of shadcn/ui Avatar (base-ui flavour, source read 2026-09-30). Spec: components/avatar/spec.md.\n// Geometry comes from Figma through the avatar/<size>/* tokens (ADR-0017); every ring around something overlapping\n// something else is the shared overlap/ring-width (ADR-0019).\n//\n// Ladder step 3: Base UI Avatar keeps the <img> and adds load state (the fallback shows until the image loads, and again\n// if it fails), so ADR-0030 allows it. Base UI's parts are client components; these wrappers carry no directive.\nimport * as React from \"react\";\nimport { Avatar as AvatarPrimitive } from \"@base-ui/react/avatar\";\nimport { cn } from \"@/lib/utils\";\n\ntype AvatarSize = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\n// One static class per size (ADR-0008): the box, read from avatar/<size>/size\nconst AVATAR_SIZES: Record<AvatarSize, string> = {\n xs: \"size-(--bl-avatar-xs-size)\",\n sm: \"size-(--bl-avatar-sm-size)\",\n md: \"size-(--bl-avatar-md-size)\",\n lg: \"size-(--bl-avatar-lg-size)\",\n xl: \"size-(--bl-avatar-xl-size)\",\n};\n\n/**\n * A person or entity as a round picture, with initials or an icon when there is no picture. Five sizes (upstream has\n * three; the user kept Figma's five). The outline is upstream's overlay: a border ring blended with `darken`, so it shows\n * against light photos and disappears against dark ones.\n */\nfunction Avatar({\n className,\n size = \"md\",\n ...props\n}: AvatarPrimitive.Root.Props & { size?: AvatarSize }) {\n return (\n <AvatarPrimitive.Root\n data-slot=\"avatar\"\n data-size={size}\n className={cn(\n \"group/avatar relative flex shrink-0 rounded-full select-none\",\n \"after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:border-(length:--bl-avatar-border-width) after:border-border after:mix-blend-darken\",\n AVATAR_SIZES[size],\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {\n return (\n <AvatarPrimitive.Image\n data-slot=\"avatar-image\"\n className={cn(\"aspect-square size-full rounded-full object-cover\", className)}\n {...props}\n />\n );\n}\n\n/** Initials or an <Icon>. The text steps with the circle: text/xs on xs and sm, text/md from md up (Figma's ramp). */\nfunction AvatarFallback({ className, ...props }: AvatarPrimitive.Fallback.Props) {\n return (\n <AvatarPrimitive.Fallback\n data-slot=\"avatar-fallback\"\n className={cn(\n \"flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground\",\n \"font-(family-name:--bl-avatar-font-family) font-(--bl-avatar-font-weight)\",\n \"group-data-[size=xs]/avatar:text-(length:--bl-avatar-xs-font-size) group-data-[size=xs]/avatar:leading-(--bl-avatar-xs-line-height) group-data-[size=xs]/avatar:[&_[data-slot=icon]]:size-(--bl-avatar-xs-icon-size)\",\n \"group-data-[size=sm]/avatar:text-(length:--bl-avatar-sm-font-size) group-data-[size=sm]/avatar:leading-(--bl-avatar-sm-line-height) group-data-[size=sm]/avatar:[&_[data-slot=icon]]:size-(--bl-avatar-sm-icon-size)\",\n \"group-data-[size=md]/avatar:text-(length:--bl-avatar-md-font-size) group-data-[size=md]/avatar:leading-(--bl-avatar-md-line-height) group-data-[size=md]/avatar:[&_[data-slot=icon]]:size-(--bl-avatar-md-icon-size)\",\n \"group-data-[size=lg]/avatar:text-(length:--bl-avatar-lg-font-size) group-data-[size=lg]/avatar:leading-(--bl-avatar-lg-line-height) group-data-[size=lg]/avatar:[&_[data-slot=icon]]:size-(--bl-avatar-lg-icon-size)\",\n \"group-data-[size=xl]/avatar:text-(length:--bl-avatar-xl-font-size) group-data-[size=xl]/avatar:leading-(--bl-avatar-xl-line-height) group-data-[size=xl]/avatar:[&_[data-slot=icon]]:size-(--bl-avatar-xl-icon-size)\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * A status dot at the bottom-right. Status is the colour scheme, never a variant (PLAN §5.2): pass\n * `data-scheme=\"success\" | \"warning\" | \"error\"`. A glyph inside it shows only where the dot is big enough (lg, xl).\n * When the status means something, add visually hidden text (\"online\"); the dot alone is not announced.\n */\nfunction AvatarBadge({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"avatar-badge\"\n className={cn(\n \"absolute end-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground select-none\",\n \"ring-(length:--bl-overlap-ring-width) ring-background\",\n \"group-data-[size=xs]/avatar:size-(--bl-avatar-xs-badge-size) group-data-[size=xs]/avatar:[&>*]:hidden\",\n \"group-data-[size=sm]/avatar:size-(--bl-avatar-sm-badge-size) group-data-[size=sm]/avatar:[&>*]:hidden\",\n \"group-data-[size=md]/avatar:size-(--bl-avatar-md-badge-size) group-data-[size=md]/avatar:[&>*]:hidden\",\n \"group-data-[size=lg]/avatar:size-(--bl-avatar-lg-badge-size) group-data-[size=lg]/avatar:[&>*]:size-[calc(var(--bl-avatar-lg-badge-size)_*_2_/_3)]\",\n \"group-data-[size=xl]/avatar:size-(--bl-avatar-xl-badge-size) group-data-[size=xl]/avatar:[&>*]:size-[calc(var(--bl-avatar-xl-badge-size)_*_2_/_3)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/** Overlapping avatars: each one after the first moves in by avatar/group/gap (-8px) and gets the overlap ring. */\nfunction AvatarGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"avatar-group\"\n className={cn(\n \"group/avatar-group flex space-x-(--bl-avatar-group-gap)\",\n \"*:data-[slot=avatar]:ring-(length:--bl-overlap-ring-width) *:data-[slot=avatar]:ring-background\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/** The \"+N\" at the end of a group. It takes the size of the group's avatars, as upstream's does. */\nfunction AvatarGroupCount({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"avatar-group-count\"\n className={cn(\n \"relative flex shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground\",\n \"ring-(length:--bl-overlap-ring-width) ring-background font-(family-name:--bl-avatar-font-family) font-(--bl-avatar-font-weight)\",\n // the avatars' blended outline too, so the count is not the one circle in the row without an edge (user\n // decision 2026-10-01; upstream's AvatarGroupCount has the ring only)\n \"after:pointer-events-none after:absolute after:inset-0 after:rounded-full after:border-(length:--bl-avatar-border-width) after:border-border after:mix-blend-darken\",\n \"size-(--bl-avatar-md-size) text-(length:--bl-avatar-md-font-size) leading-(--bl-avatar-md-line-height)\",\n \"group-has-data-[size=xs]/avatar-group:size-(--bl-avatar-xs-size) group-has-data-[size=xs]/avatar-group:text-(length:--bl-avatar-xs-font-size) group-has-data-[size=xs]/avatar-group:leading-(--bl-avatar-xs-line-height)\",\n \"group-has-data-[size=sm]/avatar-group:size-(--bl-avatar-sm-size) group-has-data-[size=sm]/avatar-group:text-(length:--bl-avatar-sm-font-size) group-has-data-[size=sm]/avatar-group:leading-(--bl-avatar-sm-line-height)\",\n \"group-has-data-[size=lg]/avatar-group:size-(--bl-avatar-lg-size) group-has-data-[size=lg]/avatar-group:text-(length:--bl-avatar-lg-font-size) group-has-data-[size=lg]/avatar-group:leading-(--bl-avatar-lg-line-height)\",\n \"group-has-data-[size=xl]/avatar-group:size-(--bl-avatar-xl-size) group-has-data-[size=xl]/avatar-group:text-(length:--bl-avatar-xl-font-size) group-has-data-[size=xl]/avatar-group:leading-(--bl-avatar-xl-line-height)\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Avatar, AvatarImage, AvatarFallback, AvatarBadge, AvatarGroup, AvatarGroupCount };\nexport type { AvatarSize };\n",
|
|
16
|
+
"type": "registry:ui",
|
|
17
|
+
"target": "components/ui/avatar.tsx"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"meta": {
|
|
21
|
+
"spec": "components/avatar/spec.md",
|
|
22
|
+
"figma": "1870:229",
|
|
23
|
+
"upstream": "shadcn/ui avatar (base-ui flavour, source read from ui.shadcn.com 2026-09-30)",
|
|
24
|
+
"status": "ready"
|
|
25
|
+
},
|
|
26
|
+
"type": "registry:ui"
|
|
27
|
+
}
|
package/r/badge.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
3
|
+
"name": "badge",
|
|
4
|
+
"title": "Badge",
|
|
5
|
+
"description": "Baseline badge (ready). Spec: components/badge/spec.md",
|
|
6
|
+
"dependencies": [
|
|
7
|
+
"@base-ui/react",
|
|
8
|
+
"class-variance-authority"
|
|
9
|
+
],
|
|
10
|
+
"registryDependencies": [
|
|
11
|
+
"@baseline/utils"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "src/ui/badge/badge.tsx",
|
|
16
|
+
"content": "// baseline:badge@0.1.0\n// Fork of shadcn/ui Badge (base-ui flavour, source read 2026-09-30). Spec: components/badge/spec.md.\n// Geometry comes from Figma through the badge/<size>/* tokens (ADR-0017). The destructive tint is upstream's\n// bg-destructive/10: in Figma it is the Surface `destructive` mode, which Button shares (2026-10-01).\n//\n// Ladder step 3: Base UI useRender renders a <span>, or the element passed as `render` (an <a> for a link badge).\n// Without the library the DOM is the same, so ADR-0030 allows it. Server-safe: no hooks that need a client boundary.\nimport * as React from \"react\";\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/utils\";\n\nconst badgeVariants = cva(\n [\n \"group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap\",\n \"border-(length:--bl-badge-border-width) border-transparent font-(--bl-badge-font-weight) transition-all\",\n \"focus-visible:border-ring focus-visible:ring-(length:--bl-control-ring-width) focus-visible:ring-ring/50\",\n \"aria-invalid:border-destructive aria-invalid:ring-destructive/20\",\n \"[&>svg]:pointer-events-none [&>[data-slot=icon]]:pointer-events-none\",\n ].join(\" \"),\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground [a]:hover:bg-primary/80\",\n secondary: \"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80\",\n destructive:\n \"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 [a]:hover:bg-destructive/20\",\n outline: \"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground\",\n ghost: \"text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground\",\n link: \"text-primary underline-offset-4 [a]:hover:underline\",\n },\n // One line per Figma Size; every value is a badge/<size>/* variable, never a Tailwind step. The icon side takes\n // badge/<size>/padding-x-icon (upstream's px-2 -> pl-1.5 at md), its own variable since 2026-10-02 (Figma's Icon\n // axis), so a designer sets how tight it is.\n size: {\n sm: \"h-(--bl-badge-sm-height) gap-(--bl-badge-sm-gap) px-(--bl-badge-sm-padding-x) rounded-(--bl-badge-sm-radius) text-(length:--bl-badge-sm-font-size) leading-(--bl-badge-sm-line-height) [&>svg]:size-(--bl-badge-sm-icon-size) [&>[data-slot=icon]]:size-(--bl-badge-sm-icon-size) has-data-[icon=inline-start]:ps-(--bl-badge-sm-padding-x-icon) has-data-[icon=inline-end]:pe-(--bl-badge-sm-padding-x-icon)\",\n md: \"h-(--bl-badge-md-height) gap-(--bl-badge-md-gap) px-(--bl-badge-md-padding-x) rounded-(--bl-badge-md-radius) text-(length:--bl-badge-md-font-size) leading-(--bl-badge-md-line-height) [&>svg]:size-(--bl-badge-md-icon-size) [&>[data-slot=icon]]:size-(--bl-badge-md-icon-size) has-data-[icon=inline-start]:ps-(--bl-badge-md-padding-x-icon) has-data-[icon=inline-end]:pe-(--bl-badge-md-padding-x-icon)\",\n },\n },\n defaultVariants: { variant: \"default\", size: \"md\" },\n },\n);\n\ntype BadgeProps = useRender.ComponentProps<\"span\"> & VariantProps<typeof badgeVariants>;\n\n/**\n * A compact label: a status, a count, a category.\n *\n * Status colours are not variants: a success or warning badge is a `default` badge inside the Success or Warning\n * scheme (`data-scheme`). Icons go in as children with `data-icon=\"inline-start\"` or `data-icon=\"inline-end\"`. Pass\n * `render={<a href=\"…\" />}` for a link badge; hover and focus apply only then.\n */\nfunction Badge({ className, variant = \"default\", size = \"md\", render, ...props }: BadgeProps) {\n return useRender({\n defaultTagName: \"span\",\n props: mergeProps<\"span\">({ className: cn(badgeVariants({ variant, size }), className) }, props),\n render,\n state: { slot: \"badge\", variant, size },\n });\n}\n\nexport { Badge, badgeVariants };\nexport type { BadgeProps };\n",
|
|
17
|
+
"type": "registry:ui",
|
|
18
|
+
"target": "components/ui/badge.tsx"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"meta": {
|
|
22
|
+
"spec": "components/badge/spec.md",
|
|
23
|
+
"figma": "469:69",
|
|
24
|
+
"upstream": "shadcn/ui badge (base-ui flavour, source read from ui.shadcn.com 2026-09-30)",
|
|
25
|
+
"status": "ready"
|
|
26
|
+
},
|
|
27
|
+
"type": "registry:ui"
|
|
28
|
+
}
|