@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.
Files changed (68) hide show
  1. package/README.md +26 -2
  2. package/package.json +15 -4
  3. package/r/accordion-contained.json +44 -0
  4. package/r/accordion-split.json +44 -0
  5. package/r/accordion.json +43 -0
  6. package/r/alert.json +27 -0
  7. package/r/aspect-ratio.json +24 -0
  8. package/r/avatar.json +27 -0
  9. package/r/badge.json +28 -0
  10. package/r/base.json +1058 -0
  11. package/r/blog-card-grid.json +30 -0
  12. package/r/breadcrumb.json +28 -0
  13. package/r/button-group.json +28 -0
  14. package/r/button.json +28 -0
  15. package/r/card.json +24 -0
  16. package/r/carousel.json +29 -0
  17. package/r/checkbox.json +25 -0
  18. package/r/columns.json +24 -0
  19. package/r/contact-centered.json +32 -0
  20. package/r/container.json +27 -0
  21. package/r/cta-left.json +28 -0
  22. package/r/dialog.json +31 -0
  23. package/r/drawer.json +27 -0
  24. package/r/dropdown-menu.json +28 -0
  25. package/r/faq-centered.json +28 -0
  26. package/r/field.json +29 -0
  27. package/r/header-inline.json +33 -0
  28. package/r/header-stacked.json +34 -0
  29. package/r/hero-centered.json +28 -0
  30. package/r/hero-split.json +29 -0
  31. package/r/hover-card.json +29 -0
  32. package/r/icon.json +30 -0
  33. package/r/input-group.json +30 -0
  34. package/r/input-otp.json +28 -0
  35. package/r/input.json +27 -0
  36. package/r/interest.json +15 -0
  37. package/r/item.json +29 -0
  38. package/r/kbd.json +24 -0
  39. package/r/label.json +24 -0
  40. package/r/media.json +33 -0
  41. package/r/meter.json +24 -0
  42. package/r/native-select.json +25 -0
  43. package/r/navigation-menu.json +30 -0
  44. package/r/pagination-bordered.json +27 -0
  45. package/r/pagination-grouped.json +27 -0
  46. package/r/pagination.json +26 -0
  47. package/r/panel.json +27 -0
  48. package/r/popover.json +28 -0
  49. package/r/progress.json +34 -0
  50. package/r/radio.json +24 -0
  51. package/r/registry.json +2770 -0
  52. package/r/section.json +27 -0
  53. package/r/select.json +28 -0
  54. package/r/separator.json +27 -0
  55. package/r/sheet.json +27 -0
  56. package/r/sidebar.json +34 -0
  57. package/r/spinner.json +25 -0
  58. package/r/switch.json +27 -0
  59. package/r/table.json +24 -0
  60. package/r/tabs-line.json +28 -0
  61. package/r/tabs.json +27 -0
  62. package/r/text-styles.json +16 -0
  63. package/r/textarea.json +24 -0
  64. package/r/toggle-group.json +52 -0
  65. package/r/toggle.json +27 -0
  66. package/r/tooltip.json +62 -0
  67. package/r/typeset.json +16 -0
  68. package/r/utils.json +18 -0
package/r/toggle.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "toggle",
4
+ "title": "Toggle",
5
+ "description": "Baseline toggle (ready). Spec: components/toggle/spec.md. Not Base UI: a native checkbox drawn as a button (ADR-0030). Pressed is checked: pressed / defaultPressed / onPressedChange map onto it, and aria-label goes on the input",
6
+ "dependencies": [
7
+ "class-variance-authority"
8
+ ],
9
+ "registryDependencies": [
10
+ "@baseline/utils"
11
+ ],
12
+ "files": [
13
+ {
14
+ "path": "src/ui/toggle/toggle.tsx",
15
+ "content": "// baseline:toggle@0.1.0\n// Fork of shadcn/ui Toggle (base-ui flavour, source read 2026-10-02). Spec: components/toggle/spec.md.\n// Geometry comes from Figma through the toggle/* tokens (ADR-0017); colours are the roles upstream uses.\n//\n// Ladder step 1 (ADR-0030, user 2026-10-06): NOT Base UI. A toggle is a checkbox drawn as a button — the case ADR-0030\n// already sends native for Checkbox, Radio and Switch. The <label> is the visible button, the <input type=checkbox>\n// inside it holds the state and stays in the DOM, visually hidden. Pressed, focus, disabled and invalid style from\n// :has(), so nothing runs in the browser and the state submits with a form.\n// <Toggle aria-label=\"Toggle italic\"><Icon className=\"icon-[material-symbols--format-italic]\" /></Toggle>\n// pressed / defaultPressed / onPressedChange keep upstream's names and map onto checked / defaultChecked / onChange.\n// className goes to the label; aria-label, name, value, disabled and the rest go to the input.\nimport * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/utils\";\n\nconst toggleVariants = cva(\n [\n \"group/toggle relative inline-flex shrink-0 cursor-pointer items-center justify-center whitespace-nowrap select-none\",\n \"font-(--bl-toggle-font-weight) transition-all outline-none hover:bg-muted hover:text-foreground\",\n // upstream's aria-pressed:bg-muted, from the checkbox\n \"has-checked:bg-muted\",\n // focus keeps Baseline's ring role (already translucent), as Button's\n \"has-focus-visible:z-10 has-focus-visible:border-ring has-focus-visible:ring-(length:--bl-control-ring-width) has-focus-visible:ring-ring\",\n \"has-disabled:pointer-events-none has-disabled:opacity-50\",\n \"has-[[aria-invalid=true]]:border-destructive has-[[aria-invalid=true]]:ring-(length:--bl-control-ring-width) has-[[aria-invalid=true]]:ring-destructive/20\",\n // Icon always carries size-4, so its size is set outright (as Button); a raw svg keeps upstream's not-size- guard\n \"[&_[data-slot=icon]]:pointer-events-none [&_[data-slot=icon]]:shrink-0 [&_svg]:pointer-events-none [&_svg]:shrink-0\",\n ],\n {\n variants: {\n variant: {\n default: \"bg-transparent\",\n outline: \"border-(length:--bl-toggle-border-width) border-input bg-transparent hover:bg-muted\",\n },\n // --toggle-radius is the size's corner, read by Toggle Group's joined corners (toggle-group.css.json)\n size: {\n sm: [\n \"h-(--bl-toggle-sm-height) min-w-(--bl-toggle-sm-height) gap-(--bl-toggle-sm-gap) px-(--bl-toggle-sm-padding-x)\",\n \"[--toggle-radius:var(--bl-toggle-sm-radius)] rounded-(--toggle-radius)\",\n \"text-(length:--bl-toggle-sm-font-size) leading-(--bl-toggle-sm-line-height)\",\n \"has-data-[icon=inline-start]:ps-(--bl-toggle-sm-padding-x-icon) has-data-[icon=inline-end]:pe-(--bl-toggle-sm-padding-x-icon)\",\n \"[&_[data-slot=icon]]:size-(--bl-toggle-sm-icon-size) [&_svg:not([class*='size-'])]:size-(--bl-toggle-sm-icon-size)\",\n ],\n md: [\n \"h-(--bl-toggle-md-height) min-w-(--bl-toggle-md-height) gap-(--bl-toggle-md-gap) px-(--bl-toggle-md-padding-x)\",\n \"[--toggle-radius:var(--bl-toggle-md-radius)] rounded-(--toggle-radius)\",\n \"text-(length:--bl-toggle-md-font-size) leading-(--bl-toggle-md-line-height)\",\n \"has-data-[icon=inline-start]:ps-(--bl-toggle-md-padding-x-icon) has-data-[icon=inline-end]:pe-(--bl-toggle-md-padding-x-icon)\",\n \"[&_[data-slot=icon]]:size-(--bl-toggle-md-icon-size) [&_svg:not([class*='size-'])]:size-(--bl-toggle-md-icon-size)\",\n ],\n lg: [\n \"h-(--bl-toggle-lg-height) min-w-(--bl-toggle-lg-height) gap-(--bl-toggle-lg-gap) px-(--bl-toggle-lg-padding-x)\",\n \"[--toggle-radius:var(--bl-toggle-lg-radius)] rounded-(--toggle-radius)\",\n \"text-(length:--bl-toggle-lg-font-size) leading-(--bl-toggle-lg-line-height)\",\n \"has-data-[icon=inline-start]:ps-(--bl-toggle-lg-padding-x-icon) has-data-[icon=inline-end]:pe-(--bl-toggle-lg-padding-x-icon)\",\n \"[&_[data-slot=icon]]:size-(--bl-toggle-lg-icon-size) [&_svg:not([class*='size-'])]:size-(--bl-toggle-lg-icon-size)\",\n ],\n },\n },\n defaultVariants: { variant: \"default\", size: \"md\" },\n },\n);\n\ntype ToggleProps = Omit<React.ComponentProps<\"input\">, \"type\" | \"size\" | \"checked\" | \"defaultChecked\" | \"children\"> &\n VariantProps<typeof toggleVariants> & {\n /** controlled pressed state (the checkbox's checked) */\n pressed?: boolean;\n /** uncontrolled initial pressed state */\n defaultPressed?: boolean;\n onPressedChange?: (pressed: boolean) => void;\n /** \"radio\" for a single-choice Toggle Group item; a standalone Toggle is a checkbox */\n type?: \"checkbox\" | \"radio\";\n children?: React.ReactNode;\n /** on the label (the visible button) */\n className?: string;\n style?: React.CSSProperties;\n \"data-slot\"?: string;\n };\n\nfunction Toggle({\n className,\n style,\n variant = \"default\",\n size = \"md\",\n pressed,\n defaultPressed,\n onPressedChange,\n onChange,\n type = \"checkbox\",\n children,\n \"data-slot\": slot = \"toggle\",\n ...inputProps\n}: ToggleProps) {\n return (\n <label\n data-slot={slot}\n data-variant={variant}\n data-size={size}\n className={cn(toggleVariants({ variant, size }), className)}\n style={style}\n >\n <input\n type={type}\n className=\"sr-only\"\n checked={pressed}\n defaultChecked={defaultPressed}\n onChange={\n onPressedChange || onChange\n ? (e) => {\n onChange?.(e);\n onPressedChange?.(e.currentTarget.checked);\n }\n : undefined\n }\n {...inputProps}\n />\n {children}\n </label>\n );\n}\n\nexport { Toggle, toggleVariants };\nexport type { ToggleProps };\n",
16
+ "type": "registry:ui",
17
+ "target": "components/ui/toggle.tsx"
18
+ }
19
+ ],
20
+ "meta": {
21
+ "spec": "components/toggle/spec.md",
22
+ "figma": "4524:265",
23
+ "upstream": "shadcn/ui toggle (base-ui flavour, source read from ui.shadcn.com 2026-10-02)",
24
+ "status": "ready"
25
+ },
26
+ "type": "registry:ui"
27
+ }
package/r/tooltip.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "tooltip",
4
+ "title": "Tooltip",
5
+ "description": "Baseline tooltip (ready). Spec: components/tooltip/spec.md. Not shadcn's API (ADR-0033): no Tooltip root or TooltipProvider; give TooltipContent an id and point a <TooltipTrigger interestfor=\"<id>\" render={<Button />}> at it. Needs @baseline/interest",
6
+ "dependencies": [
7
+ "@base-ui/react",
8
+ "class-variance-authority"
9
+ ],
10
+ "registryDependencies": [
11
+ "@baseline/utils",
12
+ "@baseline/interest"
13
+ ],
14
+ "files": [
15
+ {
16
+ "path": "src/ui/tooltip/tooltip.tsx",
17
+ "content": "// baseline:tooltip@0.2.0\n// Fork of shadcn/ui Tooltip (base-ui flavour, source read 2026-10-02). Spec: components/tooltip/spec.md.\n// Geometry comes from Figma through the tooltip/* tokens (ADR-0017); the look is upstream's inverted one.\n//\n// Ladder step 2 (ADR-0006, ADR-0030): a native <div popover=\"hint\" role=\"tooltip\"> shown by an interest invoker\n// (`interestfor`: hover and keyboard focus) and placed by CSS anchor positioning, not Base UI's Tooltip. interestfor is\n// Chromium only (2026-10); InterestFallback (lib/interest, shared with Hover Card) gives other browsers the same\n// behaviour and does nothing where interestfor exists (user, 2026-10-05: native plus a small script).\n//\n// SERVER COMPONENTS with explicit ids (ADR-0033, as Dialog, Sheet and Popover): no Tooltip root, no TooltipProvider.\n// The trigger is unstyled, as upstream's: an element of your own through `render` (useRender, which runs on the server).\n// <TooltipTrigger interestfor=\"add-to-library\" render={<Button variant=\"outline\" />}>Hover</TooltipTrigger>\n// <TooltipContent id=\"add-to-library\">Add to library</TooltipContent>\n// The trigger names the anchor (anchor-name --<id>) and is aria-describedby the content.\nimport * as React from \"react\";\nimport { mergeProps } from \"@base-ui/react/merge-props\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport { cva } from \"class-variance-authority\";\nimport { cn } from \"@/lib/utils\";\nimport { InterestFallback } from \"@/lib/interest\";\n\n// Interest invokers (Chromium 142) are not in @types/react 19 yet; React passes the lowercase attribute through.\ndeclare module \"react\" {\n interface ButtonHTMLAttributes<T> {\n interestfor?: string;\n }\n}\n\ntype Side = \"top\" | \"bottom\" | \"left\" | \"right\" | \"inline-start\" | \"inline-end\";\ntype Align = \"start\" | \"center\" | \"end\";\n\nconst anchorName = (id: string) => `--${id.replace(/[^\\w-]/g, \"_\")}`;\n\n// When position-try-fallbacks flips the tooltip, an anchored container query moves the arrow to the other edge. These\n// rules are rendered as a <style> React hoists once per page rather than kept in tooltip.css.json: Lightning CSS (the\n// CSS pipeline of Next and Tailwind) cannot parse `@container anchored(...)`, so a consumer's globals.css would fail\n// to build. Where the query is unsupported the arrow stays on the requested side.\nconst TUCK = \"calc(2px - var(--bl-tooltip-arrow-size) / 2)\";\nconst FLIP_CSS = [\n \"[data-slot=tooltip-content]{container-type:anchored}\",\n \"@container anchored(fallback: flip-block){\",\n `[data-slot=tooltip-arrow][data-side=top]{bottom:auto;top:${TUCK}}`,\n `[data-slot=tooltip-arrow][data-side=bottom]{top:auto;bottom:${TUCK}}`,\n \"}\",\n \"@container anchored(fallback: flip-inline){\",\n `[data-slot=tooltip-arrow][data-side=right]{left:auto;right:${TUCK}}`,\n `[data-slot=tooltip-arrow][data-side=left]{right:auto;left:${TUCK}}`,\n `[data-slot=tooltip-arrow][data-side=inline-end]{inset-inline-start:auto;inset-inline-end:${TUCK}}`,\n `[data-slot=tooltip-arrow][data-side=inline-start]{inset-inline-end:auto;inset-inline-start:${TUCK}}`,\n \"}\",\n].join(\"\");\n\n// as Popover: position-area for each side and alignment, the margin facing the trigger, the flip, the align margin\nconst AREA: Record<Side, Record<Align, string>> = {\n bottom: { start: \"block-end span-inline-end\", center: \"block-end\", end: \"block-end span-inline-start\" },\n top: { start: \"block-start span-inline-end\", center: \"block-start\", end: \"block-start span-inline-start\" },\n \"inline-end\": { start: \"inline-end span-block-end\", center: \"inline-end\", end: \"inline-end span-block-start\" },\n \"inline-start\": { start: \"inline-start span-block-end\", center: \"inline-start\", end: \"inline-start span-block-start\" },\n right: { start: \"right span-bottom\", center: \"right\", end: \"right span-top\" },\n left: { start: \"left span-bottom\", center: \"left\", end: \"left span-top\" },\n};\nconst SIDE_MARGIN: Record<Side, keyof React.CSSProperties> = {\n bottom: \"marginBlockStart\",\n top: \"marginBlockEnd\",\n \"inline-end\": \"marginInlineStart\",\n \"inline-start\": \"marginInlineEnd\",\n right: \"marginLeft\",\n left: \"marginRight\",\n};\nconst FLIP: Record<Side, string> = {\n bottom: \"flip-block\",\n top: \"flip-block\",\n \"inline-end\": \"flip-inline\",\n \"inline-start\": \"flip-inline\",\n right: \"flip-inline\",\n left: \"flip-inline\",\n};\nconst ALIGN_MARGIN: Record<Side, Record<\"start\" | \"end\", keyof React.CSSProperties>> = {\n bottom: { start: \"marginInlineStart\", end: \"marginInlineEnd\" },\n top: { start: \"marginInlineStart\", end: \"marginInlineEnd\" },\n \"inline-end\": { start: \"marginBlockStart\", end: \"marginBlockEnd\" },\n \"inline-start\": { start: \"marginBlockStart\", end: \"marginBlockEnd\" },\n right: { start: \"marginTop\", end: \"marginBottom\" },\n left: { start: \"marginTop\", end: \"marginBottom\" },\n};\n\n// Closed it is hidden through a variant (not-open:hidden), which a display class passed in className cannot undo\n// (tailwind-merge drops a plain `hidden` against `flex`), so `display` can transition with it (transition-discrete). Upstream's 100ms fade, zoom from 95%\n// and slide in from the trigger's side, as transitions from @starting-style; none under reduced motion. The arrow's\n// position lives in tooltip.css.json, keyed by data-side / data-align, with the anchored query that moves it on a flip.\nconst contentVariants = cva(\n [\n \"group/tooltip-content inset-auto m-0 inline-flex not-open:hidden w-fit max-w-(--bl-tooltip-max-width) items-center gap-(--bl-tooltip-gap)\",\n \"overflow-visible rounded-(--bl-tooltip-radius) border-0 bg-foreground px-(--bl-tooltip-padding-x) py-(--bl-tooltip-padding-y) outline-none\",\n \"text-(length:--bl-tooltip-font-size) leading-(--bl-tooltip-line-height) text-background has-data-[slot=kbd]:pe-(--bl-tooltip-padding-end-kbd)\",\n \"**:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm\",\n \"scale-95 opacity-0 open:translate-0 open:scale-100 open:opacity-100 starting:open:scale-95 starting:open:opacity-0\",\n \"transition-[opacity,scale,translate,display,overlay] transition-discrete duration-100 motion-reduce:transition-none\",\n ],\n {\n variants: {\n side: {\n top: \"origin-bottom translate-y-2 starting:open:translate-y-2\",\n bottom: \"origin-top -translate-y-2 starting:open:-translate-y-2\",\n right: \"origin-left -translate-x-2 starting:open:-translate-x-2\",\n left: \"origin-right translate-x-2 starting:open:translate-x-2\",\n \"inline-end\": \"origin-left -translate-x-2 starting:open:-translate-x-2 rtl:origin-right rtl:translate-x-2 rtl:starting:open:translate-x-2\",\n \"inline-start\": \"origin-right translate-x-2 starting:open:translate-x-2 rtl:origin-left rtl:-translate-x-2 rtl:starting:open:-translate-x-2\",\n },\n },\n defaultVariants: { side: \"top\" },\n },\n);\n\ntype TooltipTriggerProps = Omit<useRender.ComponentProps<\"button\">, \"interestfor\"> & {\n /** Required: the TooltipContent's id. */\n interestfor: string;\n};\n\n/**\n * Shows the tooltip on hover and keyboard focus (interestfor), is its anchor, and is described by it. Unstyled, as\n * upstream's: a <button type=\"button\">, or your own element through `render` (`render={<Button variant=\"outline\" />}`).\n * It has to end up a <button> or an <a>, the elements interestfor works on. An icon-only trigger keeps its aria-label\n * (a tooltip is a hint, never the only name).\n */\nfunction TooltipTrigger({ interestfor, render, ...props }: TooltipTriggerProps) {\n return useRender({\n defaultTagName: \"button\",\n render,\n props: mergeProps<\"button\">(\n {\n type: \"button\",\n interestfor,\n \"aria-describedby\": interestfor,\n // upstream opens with no delay (TooltipProvider delay={0}): interest-delay-start for the native path (not in\n // csstype yet), data-interest-delay for InterestFallback\n style: { anchorName: anchorName(interestfor), ...({ interestDelayStart: \"0s\" } as React.CSSProperties) },\n ...({ \"data-interest-delay\": \"0\" } as Record<string, string>),\n },\n props,\n ),\n state: { slot: \"tooltip-trigger\" },\n });\n}\n\ntype TooltipContentProps = Omit<React.ComponentProps<\"div\">, \"id\"> & {\n /** Required: the id the trigger points at. */\n id: string;\n side?: Side;\n align?: Align;\n /** px between the trigger and the tooltip (default 4) */\n sideOffset?: number;\n /** px along the side, away from the aligned edge (start / end only) */\n alignOffset?: number;\n};\n\n/** The tooltip: <div popover=\"hint\" role=\"tooltip\"> with upstream's arrow, top by default. */\nfunction TooltipContent({\n id,\n side = \"top\",\n align = \"center\",\n sideOffset = 4,\n alignOffset = 0,\n className,\n style,\n children,\n ...props\n}: TooltipContentProps) {\n const placement: React.CSSProperties = {\n positionAnchor: anchorName(id),\n positionArea: AREA[side][align],\n positionTryFallbacks: FLIP[side],\n [SIDE_MARGIN[side]]: sideOffset,\n ...(align !== \"center\" && alignOffset ? { [ALIGN_MARGIN[side][align]]: alignOffset } : {}),\n };\n return (\n <div\n id={id}\n popover=\"hint\"\n role=\"tooltip\"\n data-slot=\"tooltip-content\"\n data-side={side}\n data-align={align}\n className={cn(contentVariants({ side }), className)}\n style={{ ...placement, ...style }}\n {...props}\n >\n <style href=\"baseline-tooltip-flip\" precedence=\"default\">\n {FLIP_CSS}\n </style>\n {children}\n <span\n aria-hidden\n data-slot=\"tooltip-arrow\"\n data-side={side}\n data-align={align}\n className=\"pointer-events-none absolute size-(--bl-tooltip-arrow-size) rotate-45 rounded-(--bl-tooltip-arrow-radius) bg-foreground\"\n />\n <InterestFallback />\n </div>\n );\n}\n\nexport { TooltipContent, TooltipTrigger };\nexport type { TooltipContentProps, TooltipTriggerProps };\n",
18
+ "type": "registry:ui",
19
+ "target": "components/ui/tooltip.tsx"
20
+ }
21
+ ],
22
+ "css": {
23
+ "[data-slot=tooltip-arrow][data-side=top]": {
24
+ "bottom": "calc(2px - var(--bl-tooltip-arrow-size) / 2)"
25
+ },
26
+ "[data-slot=tooltip-arrow][data-side=bottom]": {
27
+ "top": "calc(2px - var(--bl-tooltip-arrow-size) / 2)"
28
+ },
29
+ "[data-slot=tooltip-arrow][data-side=right]": {
30
+ "left": "calc(2px - var(--bl-tooltip-arrow-size) / 2)",
31
+ "top": "calc(50% - var(--bl-tooltip-arrow-size) / 2)"
32
+ },
33
+ "[data-slot=tooltip-arrow][data-side=left]": {
34
+ "right": "calc(2px - var(--bl-tooltip-arrow-size) / 2)",
35
+ "top": "calc(50% - var(--bl-tooltip-arrow-size) / 2)"
36
+ },
37
+ "[data-slot=tooltip-arrow][data-side=inline-end]": {
38
+ "inset-inline-start": "calc(2px - var(--bl-tooltip-arrow-size) / 2)",
39
+ "top": "calc(50% - var(--bl-tooltip-arrow-size) / 2)"
40
+ },
41
+ "[data-slot=tooltip-arrow][data-side=inline-start]": {
42
+ "inset-inline-end": "calc(2px - var(--bl-tooltip-arrow-size) / 2)",
43
+ "top": "calc(50% - var(--bl-tooltip-arrow-size) / 2)"
44
+ },
45
+ "[data-slot=tooltip-arrow][data-side=top][data-align=center], [data-slot=tooltip-arrow][data-side=bottom][data-align=center]": {
46
+ "left": "calc(50% - var(--bl-tooltip-arrow-size) / 2)"
47
+ },
48
+ "[data-slot=tooltip-arrow][data-side=top][data-align=start], [data-slot=tooltip-arrow][data-side=bottom][data-align=start]": {
49
+ "inset-inline-start": "var(--bl-tooltip-padding-x)"
50
+ },
51
+ "[data-slot=tooltip-arrow][data-side=top][data-align=end], [data-slot=tooltip-arrow][data-side=bottom][data-align=end]": {
52
+ "inset-inline-end": "var(--bl-tooltip-padding-x)"
53
+ }
54
+ },
55
+ "meta": {
56
+ "spec": "components/tooltip/spec.md",
57
+ "figma": "4504:193",
58
+ "upstream": "shadcn/ui tooltip (base-ui flavour, source read from ui.shadcn.com 2026-10-02)",
59
+ "status": "ready"
60
+ },
61
+ "type": "registry:ui"
62
+ }
package/r/typeset.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "typeset",
4
+ "title": "Typeset",
5
+ "description": "Prose rhythm for CMS/MDX content: Baseline fork of shadcn typeset (one .typeset class, em-based hooks, not-typeset opt-out). CSS only. ADR-0028.",
6
+ "files": [
7
+ {
8
+ "path": "src/styles/typeset.css",
9
+ "content": "/*\n * Baseline typeset: a fork of shadcn/typeset (https://ui.shadcn.com/typeset.css, fetched 2026-09-24).\n * ADR-0028 §2: only the hooks at the top and the inline shadcn variables are rewired to Baseline's emitted names\n * (--bl-font-heading, --bl-font-mono, --foreground, --muted-foreground, --border, --primary, --ring, --muted,\n * --bl-radius-lg). Nothing else changes, so an upstream update diffs cleanly, except one marked Baseline addition (the\n * flow gap above an embedded component, 2026-10-01) and one marked Baseline change (tables take the Table\n * component's look, 2026-10-02).\n * Canonical copy (ADR-0028 §2, amended 2026-09-24): edit only this file. tokens/build.mjs writes the platform copies:\n * registry/src/styles/typeset.css (verbatim), platforms/wordpress/theme/src/typeset.css (unlayered, editor root) and\n * platforms/shopify/theme/snippets/typeset.liquid (unlayered, inside {% stylesheet %}); platforms/html/src/baseline.css\n * imports this file directly.\n */\n\n@layer components {\n .typeset {\n --typeset-font-body: inherit;\n --typeset-font-heading: var(--bl-font-heading);\n --typeset-font-mono: var(--bl-font-mono);\n --typeset-size: 1em;\n --typeset-leading: 1.75;\n --typeset-flow: 1.25em;\n }\n}\n\n@layer components {\n .typeset {\n font-family: var(--typeset-font-body);\n font-size: calc(var(--typeset-size) * 1.125);\n line-height: var(--typeset-leading);\n color: var(--foreground);\n overflow-wrap: break-word;\n margin-trim: block-start;\n\n @media (min-width: 48rem), print {\n font-size: var(--typeset-size);\n }\n\n --typeset-muted: var(--muted-foreground);\n --typeset-rule: var(--border);\n }\n\n .typeset\n *:not(\n :where(\n .not-typeset,\n [data-not-typeset],\n .not-typeset *,\n [data-not-typeset] *\n )\n ) {\n /* Paragraphs. */\n &:where(p) {\n margin-block-start: var(--typeset-flow);\n margin-block-end: 0;\n }\n\n /* Headings. */\n &:where(h1, h2, h3, h4, h5, h6) {\n color: var(--foreground);\n font-family: var(--typeset-font-heading);\n font-weight: 600;\n margin-block-end: 0;\n }\n &:where(h1) {\n font-size: 1.75em;\n line-height: 1.3;\n margin-block-start: var(--typeset-flow);\n }\n &:where(h2) {\n font-size: 1.25em;\n line-height: 1.4;\n margin-block-start: calc(var(--typeset-flow) * 1.4);\n }\n &:where(h3) {\n font-size: 1.125em;\n line-height: 1.45;\n margin-block-start: var(--typeset-flow);\n }\n &:where(h4) {\n font-size: 1em;\n line-height: 1.5;\n margin-block-start: var(--typeset-flow);\n }\n &:where(h5) {\n font-size: 0.875em;\n line-height: 1.5;\n font-weight: 500;\n color: var(--typeset-muted);\n margin-block-start: calc(var(--typeset-flow) / 0.875);\n }\n &:where(h6) {\n font-size: 0.8125em;\n line-height: 1.5;\n font-weight: 500;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--typeset-muted);\n margin-block-start: calc(var(--typeset-flow) / 0.8125);\n }\n /* Anchors. */\n &:where([id]) {\n scroll-margin-block-start: var(--typeset-flow);\n }\n\n /* Headings own the space below them. */\n &:where(h1 + *, h2 + *, h3 + *, h4 + *, h5 + *, h6 + *) {\n margin-block-start: 1em;\n }\n &:where(:is(h1, h2, h3, h4) :is(code)) {\n font-size: 0.9em;\n }\n\n /* Links. */\n &:where(a) {\n color: inherit;\n font-weight: 500;\n text-decoration-line: underline;\n text-decoration-color: color-mix(in oklab, currentColor 30%, transparent);\n }\n &:where(a:hover) {\n text-decoration-color: currentColor;\n }\n &:where(a:focus-visible) {\n outline: 2px solid var(--ring);\n outline-offset: 2px;\n border-radius: 0.125em;\n }\n &:where(:is(h1, h2, h3, h4, h5, h6) :is(a)) {\n font-weight: inherit;\n }\n\n /* Inline semantics. */\n &:where(strong, b) {\n font-weight: 600;\n }\n &:where(:is(h1, h2, h3, h4) :is(strong, b)) {\n font-weight: 600;\n }\n &:where(mark) {\n background-color: color-mix(\n in oklab,\n oklch(0.86 0.17 95) 40%,\n transparent\n );\n color: inherit;\n padding: 0.05em 0.15em;\n border-radius: 0.2em;\n }\n &:where(abbr[title]) {\n text-decoration-line: underline;\n text-decoration-style: dotted;\n text-underline-offset: 0.2em;\n cursor: help;\n }\n &:where(del, s) {\n color: var(--typeset-muted);\n text-decoration-line: line-through;\n }\n &:where(sup, sub) {\n font-size: 0.75em;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n &:where(sup) {\n top: -0.5em;\n }\n &:where(sub) {\n bottom: -0.25em;\n }\n &:where(sup a) {\n text-decoration-line: none;\n font-weight: 500;\n }\n\n /* Lists. */\n &:where(ul) {\n list-style-type: disc;\n }\n &:where(ol) {\n list-style-type: decimal;\n }\n &:where(ul ul) {\n list-style-type: circle;\n }\n &:where(ul ul ul) {\n list-style-type: square;\n }\n &:where(ol[type=\"a\" i]) {\n list-style-type: lower-alpha;\n }\n &:where(ol[type=\"i\" i]) {\n list-style-type: lower-roman;\n }\n &:where(ol[type=\"A\" s]) {\n list-style-type: upper-alpha;\n }\n &:where(ol[type=\"I\" s]) {\n list-style-type: upper-roman;\n }\n &:where(ul, ol) {\n margin-block-start: var(--typeset-flow);\n margin-block-end: 0;\n padding-inline-start: 1.5em;\n }\n &:where(li) {\n margin-block-start: 0.5em;\n padding-inline-start: 0.4em;\n }\n &:where(li > p, li > ul, li > ol) {\n margin-block-start: 0.5em;\n }\n &:where(ul > li)::marker {\n color: var(--typeset-muted);\n }\n &:where(ol > li)::marker {\n color: var(--typeset-muted);\n }\n\n /* GFM task lists. */\n &:where(ul.contains-task-list) {\n list-style-type: none;\n padding-inline-start: 0.25em;\n }\n &:where(li.task-list-item > input[type=\"checkbox\"]) {\n margin-inline-end: 0.5em;\n vertical-align: -0.1em;\n accent-color: var(--primary);\n }\n\n /* Disclosures. */\n &:where(details) {\n margin-block-start: var(--typeset-flow);\n margin-block-end: 0;\n }\n &:where(summary) {\n cursor: pointer;\n font-weight: 500;\n }\n &:where(summary)::marker {\n color: var(--typeset-muted);\n }\n\n /* Keyboard keys. */\n &:where(kbd) {\n font-family: inherit;\n font-size: 0.85em;\n font-weight: 500;\n border: 1px solid var(--typeset-rule);\n border-block-end-width: 2px;\n border-radius: min(calc(var(--bl-radius-lg) * 0.6), 0.35em);\n padding: 0.0625em 0.35em;\n }\n\n /* Definition lists. */\n &:where(dl) {\n margin-block-start: var(--typeset-flow);\n margin-block-end: 0;\n }\n &:where(dt) {\n font-weight: 500;\n margin-block-start: 1em;\n }\n &:where(dt + dt) {\n margin-block-start: 0.25em;\n }\n &:where(dd) {\n margin-block-start: 0.25em;\n margin-inline-start: 0;\n padding-inline-start: 1em;\n color: var(--typeset-muted);\n }\n\n /* Inline code. */\n &:where(:not(pre) > code) {\n background-color: var(--muted);\n font-family: var(--typeset-font-mono);\n font-size: 0.85em;\n border-radius: min(calc(var(--bl-radius-lg) * 0.6), 0.35em);\n padding: 0.125em 0.3em;\n }\n\n /* Code blocks. */\n &:where(pre) {\n background-color: var(--muted);\n font-family: var(--typeset-font-mono);\n font-size: 0.875em;\n line-height: 1.5;\n tab-size: 2;\n direction: ltr;\n border-radius: var(--bl-radius-lg);\n padding: 0.75em 1em;\n overflow-x: auto;\n margin-block-start: calc(var(--typeset-flow) / 0.875);\n margin-block-end: 0;\n }\n &:where(pre code) {\n background-color: transparent;\n font-family: inherit;\n font-size: inherit;\n padding: 0;\n border-radius: 0;\n }\n\n /* Blockquote. */\n &:where(blockquote) {\n border-inline-start: 2px solid var(--typeset-rule);\n padding-inline-start: 1em;\n margin-block-start: var(--typeset-flow);\n margin-block-end: 0;\n margin-inline: 0;\n }\n\n /* Dividers. */\n &:where(hr) {\n border: 0;\n border-block-start: 1px solid var(--typeset-rule);\n margin-block-start: calc(var(--typeset-flow) * 2.4);\n margin-block-end: 0;\n }\n &:where(hr + h1, hr + h2, hr + h3, hr + h4) {\n margin-block-start: var(--typeset-flow);\n }\n\n /* GFM footnotes. */\n &:where(.footnotes, [data-footnotes]) {\n margin-block-start: calc(var(--typeset-flow) * 2);\n border-block-start: 1px solid var(--typeset-rule);\n padding-block-start: var(--typeset-flow);\n font-size: 0.875em;\n color: var(--typeset-muted);\n }\n\n /* Math. */\n &:where(math[display=\"block\"]) {\n margin-block-start: var(--typeset-flow);\n margin-block-end: 0;\n overflow-x: auto;\n overflow-y: hidden;\n padding-block: 0.25em;\n }\n\n /* Media. */\n &:where(img, video) {\n border-radius: var(--bl-radius-lg);\n max-width: 100%;\n height: auto;\n margin-block-start: var(--typeset-flow);\n margin-block-end: 0;\n }\n &:where(p img) {\n margin-block-start: 0;\n }\n &:where(figure) {\n margin-block-start: var(--typeset-flow);\n margin-block-end: 0;\n margin-inline: 0;\n }\n &:where(figcaption) {\n color: var(--typeset-muted);\n font-size: 0.875em;\n text-align: center;\n margin-block-start: calc(0.75em / 0.875);\n }\n\n /* Tables: Baseline change (ADR-0028 §2, amended 2026-10-02, user decision; not upstream typeset). A table in prose\n takes the Table component's look (registry/src/ui/table/table.tsx, upstream shadcn Table) on the same table/*\n tokens, so a table reads the same wherever it appears: rules under the header and between body rows, the\n footer filled, a row filled on hover, 40px header cells, 8px cells, no wrapping. Wide tables scroll inside a\n .typeset-scroll wrapper (WordPress's core/table figure already scrolls). */\n &:where(table) {\n width: 100%;\n caption-side: bottom;\n font-size: var(--bl-table-font-size);\n line-height: var(--bl-table-line-height);\n border-collapse: collapse;\n margin-block-start: var(--typeset-flow);\n margin-block-end: 0;\n }\n\n /* Horizontal scroll for any wide block. Wrap it and the wrapper owns the\n flow margin. */\n &:where(.typeset-scroll) {\n overflow-x: auto;\n margin-block-start: var(--typeset-flow);\n }\n &:where(.typeset-scroll > *) {\n margin-block-start: 0;\n }\n &:where(thead tr) {\n border-block-end: var(--bl-table-border-width) solid var(--typeset-rule);\n }\n &:where(tbody tr) {\n border-block-end: var(--bl-table-border-width) solid var(--typeset-rule);\n transition: background-color 150ms;\n }\n &:where(tbody tr:last-child) {\n border-block-end-width: 0;\n }\n &:where(tbody tr:hover) {\n background-color: var(--bl-table-row-hover-background);\n }\n &:where(tfoot) {\n border-block-start: var(--bl-table-border-width) solid var(--typeset-rule);\n background-color: var(--bl-table-footer-background);\n font-weight: var(--bl-table-head-font-weight);\n }\n &:where(thead th) {\n height: var(--bl-table-head-height);\n padding: 0 var(--bl-table-cell-padding-x);\n color: var(--foreground);\n font-weight: var(--bl-table-head-font-weight);\n text-align: start;\n vertical-align: middle;\n white-space: nowrap;\n }\n &:where(:is(tbody, tfoot) :is(td, th)) {\n padding: var(--bl-table-cell-padding-y) var(--bl-table-cell-padding-x);\n text-align: start;\n vertical-align: middle;\n white-space: nowrap;\n }\n &:where(tbody th) {\n font-weight: var(--bl-table-head-font-weight);\n }\n &:where(caption) {\n margin-block-start: var(--bl-table-caption-gap);\n color: var(--typeset-muted);\n font-size: var(--bl-table-font-size);\n line-height: var(--bl-table-line-height);\n }\n &:where(th[align=\"center\"], td[align=\"center\"]) {\n text-align: center;\n }\n &:where(th[align=\"right\"], td[align=\"right\"]) {\n text-align: end;\n }\n\n /* Blocks inside list items keep the list's tighter rhythm. */\n &:where(li > blockquote, li > table, li > figure) {\n margin-block-start: 0.5em;\n }\n &:where(li > pre) {\n margin-block-start: calc(0.5em / 0.875);\n }\n\n @media print {\n &:where(pre, table, blockquote, figure) {\n break-inside: avoid;\n }\n &:where(h1, h2, h3, h4) {\n break-after: avoid;\n }\n }\n\n /* Forced colors strips the code background, so give it a border. */\n @media (forced-colors: active) {\n &:where(:not(pre) > code) {\n border: 1px solid;\n }\n }\n }\n\n /* Baseline addition (ADR-0028 §2, amended 2026-10-01; not in upstream). A component embedded in flowing content opts\n out of typeset (.not-typeset, or the data-slot every Baseline component root carries), and that also took away the\n space above it, so an Alert or Card sat flush against the paragraph before it. A direct child of the typeset root\n that is a component gets the flow gap above it, except as the first child; whatever follows it already has its own\n flow margin. Same specificity as the rules above. */\n .typeset > :where(.not-typeset, [data-not-typeset], [data-slot]):where(:not(:first-child)) {\n margin-block-start: var(--typeset-flow);\n }\n\n /* First child adds no space above. Last so it wins ties. */\n .typeset\n > :where(:first-child):not(\n :where(\n .not-typeset,\n [data-not-typeset],\n .not-typeset *,\n [data-not-typeset] *\n )\n ),\n .typeset\n > :where(:first-child)\n > :where(:first-child):not(\n :where(\n .not-typeset,\n [data-not-typeset],\n .not-typeset *,\n [data-not-typeset] *\n )\n ),\n .typeset\n :where(\n li > :first-child,\n blockquote > :first-child,\n td > :first-child,\n th > :first-child,\n dd > :first-child,\n figure > :first-child,\n figure > picture > img\n ):not(\n :where(\n .not-typeset,\n [data-not-typeset],\n .not-typeset *,\n [data-not-typeset] *\n )\n ) {\n margin-block-start: 0;\n }\n}\n",
10
+ "type": "registry:file",
11
+ "target": "app/typeset.css"
12
+ }
13
+ ],
14
+ "docs": "Add @import \"./typeset.css\"; to app/globals.css after @import \"tailwindcss\"; then wrap flowing content in <div className=\"typeset\">. Components inside prose get className=\"not-typeset\".",
15
+ "type": "registry:item"
16
+ }
package/r/utils.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema/registry-item.json",
3
+ "name": "utils",
4
+ "title": "cn",
5
+ "description": "shadcn's cn engine re-exported as @/lib/utils.",
6
+ "dependencies": [
7
+ "cn"
8
+ ],
9
+ "files": [
10
+ {
11
+ "path": "src/lib/utils.ts",
12
+ "content": "// baseline:utils@0.1.0\n// shadcn's `cn` engine replaces clsx + tailwind-merge (ADR-0005). Consumers import `cn` from \"@/lib/utils\".\nexport { cn } from \"cn\";\n",
13
+ "type": "registry:lib",
14
+ "target": "lib/utils.ts"
15
+ }
16
+ ],
17
+ "type": "registry:lib"
18
+ }