feat(ui-refactor 1/4): token 基座对齐 beautifului.dev foundation 原版——蓝色 accent、ring+分层阴影、圆角体系 6/8/10/14、强缓动曲线、暗色专属 ring;Button 恢复 pill 体系
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "button",
|
||||
"type": "registry:ui",
|
||||
"title": "Button",
|
||||
"description": "Shared building block used by other Beautiful UI components.",
|
||||
"dependencies": [
|
||||
"class-variance-authority"
|
||||
],
|
||||
"registryDependencies": [
|
||||
"https://www.beautifului.dev/r/foundation.json"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "components/atoms/Button.tsx",
|
||||
"content": "\"use client\";\n\nimport { ButtonHTMLAttributes } from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/utils\";\n\nconst filledShadow = \"shadow-[inset_0_1px_0_rgba(255,255,255,0.14)]\";\n\n/* Pill-shaped by default — the app's core button style. Explicit symmetric\n * padding (not a fixed height) so the top/bottom spacing is always equal. */\nexport const buttonVariants = cva(\n `inline-flex items-center justify-center font-medium select-none\n transition-[transform,background-color,opacity] duration-150 ease-out\n active:scale-[0.96] disabled:opacity-50 disabled:pointer-events-none`,\n {\n variants: {\n variant: {\n primary: `bg-ink text-canvas hover:opacity-90 dark:bg-ink dark:text-canvas ${filledShadow}`,\n secondary: \"bg-surface text-ink shadow-btn hover:bg-inset aria-expanded:bg-hover\",\n ghost: \"bg-hover-2 text-ink hover:bg-line-strong\",\n accent: `bg-accent text-white hover:bg-accent-ink ${filledShadow}`,\n success: `bg-green text-white hover:brightness-95 ${filledShadow}`,\n /* transparent until hovered — for dense toolbars/action rows */\n quiet: \"text-ink hover:bg-hover\",\n },\n size: {\n /* compact toolbar pill — fixed height, lighter weight */\n xs: \"h-7 rounded-full px-2.5 text-[12px] font-normal leading-none gap-1\",\n /* canonical action pill — 27px tall, roomy sides */\n sm: \"h-[27px] px-3 text-[13px] leading-none rounded-full gap-1.5\",\n md: \"px-4 py-[9px] text-sm leading-none rounded-full gap-2\",\n },\n },\n defaultVariants: { variant: \"secondary\", size: \"md\" },\n },\n);\n\nexport type ButtonVariant = NonNullable<VariantProps<typeof buttonVariants>[\"variant\"]>;\n\nexport function Button({\n variant,\n size,\n className,\n ...props\n}: ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants>) {\n return <button className={cn(buttonVariants({ variant, size }), className)} {...props} />;\n}\n",
|
||||
"type": "registry:ui",
|
||||
"target": "components/atoms/Button.tsx"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "context-cards",
|
||||
"type": "registry:component",
|
||||
"title": "Context Cards",
|
||||
"description": "Retrieved knowledge chunks with their sources.",
|
||||
"registryDependencies": [
|
||||
"https://www.beautifului.dev/r/foundation.json"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "components/primitives/ContextCards.tsx",
|
||||
"content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\n\n/* ─────────────────────────────────────────────────────────\n * CONTEXT CARDS\n * Retrieved chunks enter once, then remain available.\n * ───────────────────────────────────────────────────────── */\n\nexport type ContextChunk = {\n title: string;\n chars: string;\n body: string;\n source: string;\n badge: string;\n tone: string;\n};\n\nexport type ContextCardsLabels = {\n header: string;\n count: string;\n};\n\nconst DEFAULT_LABELS: ContextCardsLabels = {\n header: \"All chunks\",\n count: \"32\",\n};\n\nconst CHUNKS: ContextChunk[] = [\n {\n title: \"Vendor onboarding rule\",\n chars: \"290 characters\",\n body: \"Cold-chain certification must be verified before a new dairy can be added to the reorder workflow.\",\n source: \"Dairy Onboarding SOP.pdf\",\n badge: \"PDF\",\n tone: \"bg-red\",\n },\n {\n title: \"Seasonal demand row\",\n chars: \"1,250 characters\",\n body: \"Q4 velocity table: pistachio +18%, vanilla +6%, rocky road -11%; retire flavors below 40 scoops weekly.\",\n source: \"Sales Velocity Export.csv\",\n badge: \"CSV\",\n tone: \"bg-green\",\n },\n];\n\nexport default function ContextCards({\n chunks = CHUNKS,\n labels,\n className,\n}: {\n /** Accepted for gallery/registry parity; ContextCards has no visual variants. */\n variant?: string;\n chunks?: ContextChunk[];\n labels?: Partial<ContextCardsLabels>;\n className?: string;\n} = {}) {\n const [chipsShown, setChipsShown] = useState(false);\n const copy = { ...DEFAULT_LABELS, ...labels };\n\n useEffect(() => {\n const chips = setTimeout(() => setChipsShown(true), 700);\n return () => clearTimeout(chips);\n }, []);\n\n return (\n <div className={`flex w-full max-w-95 flex-col gap-2${className ? ` ${className}` : \"\"}`}>\n <div\n className=\"flex items-center gap-2 px-0.5\"\n style={{ animation: \"fade-in 400ms ease-out both\" }}\n >\n <span className=\"text-[13px] font-semibold text-ink\">{copy.header}</span>\n <span className=\"inline-flex h-5 items-center rounded-md bg-inset px-1.5 text-[11.5px] font-medium text-ink-2 shadow-hairline tabular-nums\">\n {copy.count}\n </span>\n </div>\n\n {chunks.map((chunk, i) => (\n <div\n key={chunk.title}\n className=\"overflow-hidden rounded-card bg-surface shadow-card\"\n style={{\n animation: `fade-up 400ms cubic-bezier(0.23,1,0.32,1) ${i * 100}ms both`,\n }}\n >\n <div className=\"primitive-card-bar flex items-center gap-2.5 border-b border-line\">\n <span className=\"flex min-w-0 items-center gap-1.5 text-[13px] font-medium text-ink\">\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\"><path d=\"M4 6h16M4 12h16M4 18h10\" /></svg>\n <span className=\"truncate\">{chunk.title}</span>\n </span>\n <span className=\"ml-auto shrink-0 text-[12px] text-ink-3 tabular-nums\">{chunk.chars}</span>\n </div>\n <p className=\"px-3 pt-2 pb-1 text-[12.5px] leading-relaxed text-ink-2\">\n {chunk.body}\n </p>\n <div className=\"px-3 pb-3\">\n <span\n className=\"inline-flex h-6 items-center gap-1.5 rounded-full bg-inset px-2\n text-[12px] font-medium text-ink-2 shadow-btn\n transition-[opacity,transform,background-color] duration-300 hover:bg-hover\"\n style={{\n opacity: chipsShown ? 1 : 0,\n transform: chipsShown ? \"scale(1)\" : \"scale(0.95)\",\n transitionTimingFunction: \"cubic-bezier(0.23, 1, 0.32, 1)\",\n transitionDelay: `${i * 80}ms`,\n }}\n >\n <span className={`flex size-3.5 items-center justify-center rounded-[4px] ${chunk.tone} text-[7px] font-bold text-white`}>\n {chunk.badge}\n </span>\n {chunk.source}\n <svg width=\"9\" height=\"9\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M7 17L17 7M7 7h10v10\" /></svg>\n </span>\n </div>\n </div>\n ))}\n </div>\n );\n}\n",
|
||||
"type": "registry:component",
|
||||
"target": "components/primitives/ContextCards.tsx"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "entity-chip",
|
||||
"type": "registry:ui",
|
||||
"title": "EntityChip",
|
||||
"description": "Shared building block used by other Beautiful UI components.",
|
||||
"registryDependencies": [
|
||||
"https://www.beautifului.dev/r/foundation.json"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "components/atoms/EntityChip.tsx",
|
||||
"content": "/** Monogram mark — a colored disc with an initial or short glyph.\n * The shared building block for entity chips and monogram headings. */\nexport function Monogram({\n children,\n color = \"#e08a3c\",\n className = \"\",\n}: {\n children: React.ReactNode;\n color?: string;\n className?: string;\n}) {\n return (\n <span\n className={`flex size-4 shrink-0 items-center justify-center rounded-full\n text-[9px] font-semibold leading-none text-white ${className}`}\n style={{ background: color }}\n >\n {children}\n </span>\n );\n}\n\n/** Inline entity reference — a monogram + name in a soft field pill.\n * Names a supplier, person, or record inside running text. Softer than a\n * StatusPill (no dot, no state) and not a mono token (see Chip). */\nexport function EntityChip({\n name,\n color,\n monogram,\n className = \"\",\n}: {\n name: string;\n color?: string;\n monogram?: React.ReactNode;\n className?: string;\n}) {\n return (\n <span\n className={`mx-0.5 inline-flex items-center gap-1 rounded-full bg-field\n py-px pl-[3px] pr-1.5 align-middle shadow-hairline ${className}`}\n >\n <Monogram color={color}>{monogram ?? name.charAt(0)}</Monogram>\n <span className=\"text-[12px] font-medium text-ink\">{name}</span>\n </span>\n );\n}\n",
|
||||
"type": "registry:ui",
|
||||
"target": "components/atoms/EntityChip.tsx"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "glide-menu",
|
||||
"type": "registry:ui",
|
||||
"title": "GlideMenu",
|
||||
"description": "Shared building block used by other Beautiful UI components.",
|
||||
"registryDependencies": [
|
||||
"https://www.beautifului.dev/r/foundation.json"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "components/primitives/GlideMenu.tsx",
|
||||
"content": "\"use client\";\n\nimport { useRef, useState, type ReactNode } from \"react\";\n\ntype GlideMenuProps = {\n children: ReactNode;\n className?: string;\n highlightClassName?: string;\n rowSelector?: string;\n};\n\n/** A single hover layer that glides between interactive menu rows. */\nexport default function GlideMenu({\n children,\n className = \"\",\n highlightClassName = \"inset-x-0 rounded-[8px] bg-hover\",\n rowSelector = \"[data-menu-row]\",\n}: GlideMenuProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [box, setBox] = useState<{ top: number; height: number } | null>(null);\n const [visible, setVisible] = useState(false);\n\n const moveTo = (target: EventTarget | null) => {\n const container = ref.current;\n if (!(target instanceof Element) || !container) return;\n const row = target.closest(rowSelector);\n if (!(row instanceof HTMLElement) || !container.contains(row)) return;\n const containerRect = container.getBoundingClientRect();\n const rowRect = row.getBoundingClientRect();\n setBox({ top: rowRect.top - containerRect.top, height: rowRect.height });\n setVisible(true);\n };\n\n return (\n <div\n ref={ref}\n onMouseOver={(event) => moveTo(event.target)}\n onMouseLeave={() => setVisible(false)}\n onFocusCapture={(event) => moveTo(event.target)}\n onBlurCapture={(event) => {\n if (!ref.current?.contains(event.relatedTarget as Node | null)) setVisible(false);\n }}\n className={`group/glide-menu relative ${className}`}\n >\n <span\n aria-hidden\n className={`pointer-events-none absolute ${highlightClassName}`}\n style={{\n top: box?.top ?? 0,\n height: box?.height ?? 0,\n opacity: box && visible ? 1 : 0,\n transition:\n \"top 220ms cubic-bezier(0.23,1,0.32,1), height 220ms cubic-bezier(0.23,1,0.32,1), opacity 150ms ease\",\n }}\n />\n {children}\n </div>\n );\n}\n",
|
||||
"type": "registry:ui",
|
||||
"target": "components/primitives/GlideMenu.tsx"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "search",
|
||||
"type": "registry:component",
|
||||
"title": "Search",
|
||||
"description": "Command search with live filtering and an empty state.",
|
||||
"registryDependencies": [
|
||||
"https://www.beautifului.dev/r/foundation.json",
|
||||
"https://www.beautifului.dev/r/glide-menu.json"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "components/primitives/SearchList.tsx",
|
||||
"content": "\"use client\";\n\nimport { useState } from \"react\";\nimport GlideMenu from \"@/components/primitives/GlideMenu\";\n\n/* ─────────────────────────────────────────────────────────\n * SEARCH — command search with live filtering.\n * The field, clear action, and results are directly usable.\n * ───────────────────────────────────────────────────────── */\n\nexport type SearchItem = string;\n\nexport type SearchListLabels = {\n placeholder: string;\n ariaLabel: string;\n emptyTitle: string;\n emptyHint: string;\n};\n\nconst ITEMS: SearchItem[] = [\n \"Forecast summer demand\",\n \"Find waffle cone suppliers\",\n \"Compare seasonal flavors\",\n \"Draft flavor launch plan\",\n \"Check cold-chain status\",\n \"Audit sugar costs\",\n \"Retire low sellers\",\n];\n\nconst LABELS: SearchListLabels = {\n placeholder: \"Search flavors…\",\n ariaLabel: \"Search flavors\",\n emptyTitle: \"No results found\",\n emptyHint: \"Adjust your search to try again\",\n};\n\nexport default function SearchList({\n items = ITEMS,\n labels = LABELS,\n}: {\n items?: SearchItem[];\n labels?: SearchListLabels;\n variant?: string;\n} = {}) {\n const [query, setQuery] = useState(\"\");\n const results = query\n ? items.filter((i) => i.toLowerCase().includes(query.toLowerCase()))\n : items.slice(0, 5);\n const empty = query.length > 2 && results.length === 0;\n\n return (\n <div className=\"flex min-h-[248px] w-full max-w-72 flex-col items-stretch\">\n <div className=\"w-full self-start overflow-hidden rounded-card bg-surface shadow-raised\">\n {/* input row */}\n <div className=\"flex h-10 items-center gap-2 border-b border-line px-3 transition-colors duration-100 hover:bg-hover\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"var(--ink-3)\" strokeWidth=\"2\" strokeLinecap=\"round\" className=\"shrink-0\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"M21 21l-4.3-4.3\" />\n </svg>\n <input\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder={labels.placeholder}\n aria-label={labels.ariaLabel}\n className=\"min-w-0 flex-1 bg-transparent text-[13px] text-ink outline-none placeholder:text-ink-3\"\n />\n {query && (\n <button\n aria-label=\"Clear search\"\n type=\"button\"\n onClick={() => setQuery(\"\")}\n className=\"flex size-6 items-center justify-center rounded-full text-ink-3\n transition-colors duration-100 hover:bg-line/70 hover:text-ink\"\n style={{ animation: \"fade-in 150ms ease-out both\" }}\n >\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.2\" strokeLinecap=\"round\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n )}\n </div>\n\n {/* results / empty state */}\n {empty ? (\n <div className=\"flex flex-col items-center justify-center gap-1 px-4 py-8\" style={{ animation: \"fade-in 250ms ease-out both\" }}>\n <span className=\"mb-1.5 flex size-8 items-center justify-center rounded-control bg-inset text-ink-3 shadow-hairline\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"M21 21l-4.3-4.3\" />\n </svg>\n </span>\n <span className=\"text-[13px] font-medium text-ink\">{labels.emptyTitle}</span>\n <span className=\"text-[12px] text-ink-3\">{labels.emptyHint}</span>\n </div>\n ) : (\n <div className=\"p-1\">\n <GlideMenu className=\"flex flex-col gap-px\" highlightClassName=\"inset-x-0 rounded-[6px] bg-hover\">\n {results.map((item) => (\n <button\n key={item}\n data-menu-row\n type=\"button\"\n onClick={() => setQuery(item)}\n className=\"relative z-10 flex h-8 w-full items-center rounded-[6px] px-2 text-left text-[13px] text-ink\"\n style={{ animation: \"fade-in 200ms ease-out both\" }}\n >\n {item}\n </button>\n ))}\n </GlideMenu>\n </div>\n )}\n </div>\n </div>\n );\n}\n",
|
||||
"type": "registry:component",
|
||||
"target": "components/primitives/SearchList.tsx"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "shimmer",
|
||||
"type": "registry:ui",
|
||||
"title": "Shimmer",
|
||||
"description": "Shared building block used by other Beautiful UI components.",
|
||||
"registryDependencies": [
|
||||
"https://www.beautifului.dev/r/foundation.json"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "components/atoms/Shimmer.tsx",
|
||||
"content": "/** Shimmering label — signals the agent is processing. */\nexport function Shimmer({\n children,\n className = \"\",\n}: {\n children: React.ReactNode;\n className?: string;\n}) {\n return (\n <span\n className={`inline-block bg-clip-text text-transparent ${className}`}\n style={{\n backgroundImage:\n \"linear-gradient(90deg, var(--ink-3) 35%, var(--ink) 50%, var(--ink-3) 65%)\",\n backgroundSize: \"200% 100%\",\n animation: \"shimmer-text 1.8s linear infinite\",\n }}\n >\n {children}\n </span>\n );\n}\n",
|
||||
"type": "registry:ui",
|
||||
"target": "components/atoms/Shimmer.tsx"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "stream-text",
|
||||
"type": "registry:ui",
|
||||
"title": "StreamText",
|
||||
"description": "Shared building block used by other Beautiful UI components.",
|
||||
"registryDependencies": [
|
||||
"https://www.beautifului.dev/r/foundation.json"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "components/atoms/StreamText.tsx",
|
||||
"content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\n/* ─────────────────────────────────────────────────────────\n * STREAM TEXT — reusable streaming primitive\n * Reveals characters quickly (fast, like a real token stream);\n * the leading edge resolves out of a soft blur, and the caret\n * stays solid while streaming, then blinks once the text\n * settles. Inherits typography from its context, so it drops\n * into any text surface (Selection Actions, chat, etc.).\n * ───────────────────────────────────────────────────────── */\n\nexport function StreamText({\n text,\n charsPerTick = 2,\n tickMs = 9,\n blurTail = 6,\n caret = true,\n className,\n onProgress,\n onDone,\n}: {\n text: string;\n /** characters revealed per tick — higher is faster */\n charsPerTick?: number;\n /** interval between reveals, ms */\n tickMs?: number;\n /** how many trailing characters carry the soft blur edge */\n blurTail?: number;\n /** render the caret (solid while streaming, blinks once idle) */\n caret?: boolean;\n className?: string;\n /** fires each tick — useful for re-anchoring UI to reflowing text */\n onProgress?: () => void;\n /** fires once the full string is shown */\n onDone?: () => void;\n}) {\n const [count, setCount] = useState(0);\n const onProgressRef = useRef(onProgress);\n const onDoneRef = useRef(onDone);\n onProgressRef.current = onProgress;\n onDoneRef.current = onDone;\n\n useEffect(() => {\n setCount(0);\n let i = 0;\n const id = setInterval(() => {\n i = Math.min(i + charsPerTick, text.length);\n setCount(i);\n onProgressRef.current?.();\n if (i >= text.length) {\n clearInterval(id);\n onDoneRef.current?.();\n }\n }, tickMs);\n return () => clearInterval(id);\n }, [text, charsPerTick, tickMs]);\n\n const streaming = count < text.length;\n const shown = text.slice(0, count);\n const split = streaming ? Math.max(0, shown.length - blurTail) : shown.length;\n\n return (\n <span className={className}>\n {shown.slice(0, split)}\n {split < shown.length && (\n <span className=\"stream-tail\">{shown.slice(split)}</span>\n )}\n {caret && (\n <span\n aria-hidden\n className={`stream-caret${streaming ? \" is-streaming\" : \"\"}`}\n />\n )}\n </span>\n );\n}\n",
|
||||
"type": "registry:ui",
|
||||
"target": "components/atoms/StreamText.tsx"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "value-pill",
|
||||
"type": "registry:ui",
|
||||
"title": "ValuePill",
|
||||
"description": "Shared building block used by other Beautiful UI components.",
|
||||
"registryDependencies": [
|
||||
"https://www.beautifului.dev/r/foundation.json"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "components/atoms/ValuePill.tsx",
|
||||
"content": "type Tone = \"neutral\" | \"green\" | \"orange\" | \"red\" | \"accent\";\n\nconst TONES: Record<Tone, { cls: string; ring: string }> = {\n neutral: { cls: \"bg-field text-ink-2\", ring: \"var(--shadow-hairline)\" },\n green: { cls: \"bg-green-tint text-green\", ring: \"0 0 0 1px color-mix(in oklch, var(--green) 28%, transparent)\" },\n orange: { cls: \"bg-orange-tint text-orange\", ring: \"0 0 0 1px color-mix(in oklch, var(--orange) 28%, transparent)\" },\n red: { cls: \"bg-red-tint text-red\", ring: \"0 0 0 1px color-mix(in oklch, var(--red) 28%, transparent)\" },\n accent: { cls: \"bg-accent-tint text-accent-ink\", ring: \"0 0 0 1px color-mix(in oklch, var(--accent) 28%, transparent)\" },\n};\n\n/** Inline value badge — a plain value (a date, a name, a count) set off in\n * prose. Softer than a StatusPill (no dot) and not a mono token (see Chip). */\nexport function ValuePill({\n children,\n tone = \"neutral\",\n className = \"\",\n}: {\n children: React.ReactNode;\n tone?: Tone;\n className?: string;\n}) {\n const t = TONES[tone];\n return (\n <span\n className={`mx-0.5 inline-flex items-center rounded-full px-1.5 py-0\n align-middle text-[12px] font-medium ${t.cls} ${className}`}\n style={{ boxShadow: t.ring }}\n >\n {children}\n </span>\n );\n}\n",
|
||||
"type": "registry:ui",
|
||||
"target": "components/atoms/ValuePill.tsx"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user