@epilot/spark-ui 0.26.1 → 0.27.1
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/dist/components/alert-dialog.cjs +1 -1
- package/dist/components/alert-dialog.js +1 -1
- package/dist/components/avatar.d.cts +1 -1
- package/dist/components/avatar.d.ts +1 -1
- package/dist/components/badge.cjs +10 -2
- package/dist/components/badge.cjs.map +1 -1
- package/dist/components/badge.d.cts +2 -1
- package/dist/components/badge.d.ts +2 -1
- package/dist/components/badge.js +10 -2
- package/dist/components/badge.js.map +1 -1
- package/dist/components/banner.d.cts +1 -1
- package/dist/components/banner.d.ts +1 -1
- package/dist/components/box.d.cts +1 -1
- package/dist/components/box.d.ts +1 -1
- package/dist/components/bubble.cjs +1 -1
- package/dist/components/bubble.cjs.map +1 -1
- package/dist/components/bubble.d.cts +1 -1
- package/dist/components/bubble.d.ts +1 -1
- package/dist/components/bubble.js +1 -1
- package/dist/components/bubble.js.map +1 -1
- package/dist/components/button-group.d.cts +1 -1
- package/dist/components/button-group.d.ts +1 -1
- package/dist/components/button.cjs +28 -6
- package/dist/components/button.cjs.map +1 -1
- package/dist/components/button.d.cts +7 -6
- package/dist/components/button.d.ts +7 -6
- package/dist/components/button.js +28 -6
- package/dist/components/button.js.map +1 -1
- package/dist/components/callout.d.cts +1 -1
- package/dist/components/callout.d.ts +1 -1
- package/dist/components/card.cjs +8 -4
- package/dist/components/card.cjs.map +1 -1
- package/dist/components/card.d.cts +6 -2
- package/dist/components/card.d.ts +6 -2
- package/dist/components/card.js +8 -4
- package/dist/components/card.js.map +1 -1
- package/dist/components/checkbox-group.cjs +31 -0
- package/dist/components/checkbox-group.cjs.map +1 -0
- package/dist/components/checkbox-group.d.cts +27 -0
- package/dist/components/checkbox-group.d.ts +27 -0
- package/dist/components/checkbox-group.js +28 -0
- package/dist/components/checkbox-group.js.map +1 -0
- package/dist/components/checkbox.d.cts +1 -1
- package/dist/components/checkbox.d.ts +1 -1
- package/dist/components/dialog.cjs +1 -1
- package/dist/components/dialog.js +1 -1
- package/dist/components/empty-state.d.cts +2 -2
- package/dist/components/empty-state.d.ts +2 -2
- package/dist/components/field.cjs +110 -15
- package/dist/components/field.cjs.map +1 -1
- package/dist/components/field.d.cts +16 -3
- package/dist/components/field.d.ts +16 -3
- package/dist/components/field.js +111 -17
- package/dist/components/field.js.map +1 -1
- package/dist/components/input-group.cjs +9 -9
- package/dist/components/input-group.cjs.map +1 -1
- package/dist/components/input-group.js +9 -9
- package/dist/components/input-group.js.map +1 -1
- package/dist/components/input.d.cts +1 -1
- package/dist/components/input.d.ts +1 -1
- package/dist/components/list.cjs +21 -5
- package/dist/components/list.cjs.map +1 -1
- package/dist/components/list.d.cts +13 -5
- package/dist/components/list.d.ts +13 -5
- package/dist/components/list.js +21 -5
- package/dist/components/list.js.map +1 -1
- package/dist/components/marker.cjs +1 -1
- package/dist/components/marker.cjs.map +1 -1
- package/dist/components/marker.d.cts +1 -1
- package/dist/components/marker.d.ts +1 -1
- package/dist/components/marker.js +1 -1
- package/dist/components/marker.js.map +1 -1
- package/dist/components/pill.d.cts +1 -1
- package/dist/components/pill.d.ts +1 -1
- package/dist/components/radio.cjs +6 -2
- package/dist/components/radio.cjs.map +1 -1
- package/dist/components/radio.d.cts +4 -2
- package/dist/components/radio.d.ts +4 -2
- package/dist/components/radio.js +6 -2
- package/dist/components/radio.js.map +1 -1
- package/dist/components/rating.cjs +167 -0
- package/dist/components/rating.cjs.map +1 -0
- package/dist/components/rating.d.cts +50 -0
- package/dist/components/rating.d.ts +50 -0
- package/dist/components/rating.js +162 -0
- package/dist/components/rating.js.map +1 -0
- package/dist/components/scroll-area.cjs +9 -2
- package/dist/components/scroll-area.cjs.map +1 -1
- package/dist/components/scroll-area.d.cts +1 -1
- package/dist/components/scroll-area.d.ts +1 -1
- package/dist/components/scroll-area.js +10 -3
- package/dist/components/scroll-area.js.map +1 -1
- package/dist/components/segmented-control.d.cts +1 -1
- package/dist/components/segmented-control.d.ts +1 -1
- package/dist/components/select.d.cts +1 -1
- package/dist/components/select.d.ts +1 -1
- package/dist/components/selection-item.cjs +42 -0
- package/dist/components/selection-item.cjs.map +1 -0
- package/dist/components/selection-item.d.cts +25 -0
- package/dist/components/selection-item.d.ts +25 -0
- package/dist/components/selection-item.js +39 -0
- package/dist/components/selection-item.js.map +1 -0
- package/dist/components/sheet.cjs +1 -1
- package/dist/components/sheet.js +1 -1
- package/dist/components/skeleton.cjs +1 -1
- package/dist/components/skeleton.cjs.map +1 -1
- package/dist/components/skeleton.js +1 -1
- package/dist/components/skeleton.js.map +1 -1
- package/dist/components/slider.cjs +1 -1
- package/dist/components/slider.cjs.map +1 -1
- package/dist/components/slider.js +1 -1
- package/dist/components/slider.js.map +1 -1
- package/dist/components/spinner.cjs +81 -8
- package/dist/components/spinner.cjs.map +1 -1
- package/dist/components/spinner.js +81 -8
- package/dist/components/spinner.js.map +1 -1
- package/dist/components/stepper.cjs +3 -2
- package/dist/components/stepper.cjs.map +1 -1
- package/dist/components/stepper.js +3 -2
- package/dist/components/stepper.js.map +1 -1
- package/dist/components/switch-group.cjs +32 -0
- package/dist/components/switch-group.cjs.map +1 -0
- package/dist/components/switch-group.d.cts +26 -0
- package/dist/components/switch-group.d.ts +26 -0
- package/dist/components/switch-group.js +29 -0
- package/dist/components/switch-group.js.map +1 -0
- package/dist/components/switch.d.cts +1 -1
- package/dist/components/switch.d.ts +1 -1
- package/dist/components/tabs.cjs +32 -23
- package/dist/components/tabs.cjs.map +1 -1
- package/dist/components/tabs.d.cts +5 -5
- package/dist/components/tabs.d.ts +5 -5
- package/dist/components/tabs.js +32 -23
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/textarea.d.cts +1 -1
- package/dist/components/textarea.d.ts +1 -1
- package/dist/components/theme.cjs +44 -2
- package/dist/components/theme.cjs.map +1 -1
- package/dist/components/theme.d.cts +4 -2
- package/dist/components/theme.d.ts +4 -2
- package/dist/components/theme.js +44 -2
- package/dist/components/theme.js.map +1 -1
- package/dist/components/toast.cjs +1 -1
- package/dist/components/toast.js +1 -1
- package/dist/components/toggle-group.d.cts +1 -1
- package/dist/components/toggle-group.d.ts +1 -1
- package/dist/components/toggle.d.cts +2 -2
- package/dist/components/toggle.d.ts +2 -2
- package/dist/lib/accent-theme.d.cts +1 -1
- package/dist/lib/accent-theme.d.ts +1 -1
- package/dist/lib/container-chrome.cjs +4 -4
- package/dist/lib/container-chrome.cjs.map +1 -1
- package/dist/lib/container-chrome.d.cts +1 -1
- package/dist/lib/container-chrome.d.ts +1 -1
- package/dist/lib/container-chrome.js +4 -4
- package/dist/lib/container-chrome.js.map +1 -1
- package/dist/lib/input-box-variants.cjs +8 -8
- package/dist/lib/input-box-variants.cjs.map +1 -1
- package/dist/lib/input-box-variants.d.cts +1 -1
- package/dist/lib/input-box-variants.d.ts +1 -1
- package/dist/lib/input-box-variants.js +8 -8
- package/dist/lib/input-box-variants.js.map +1 -1
- package/dist/lib/selection-list.cjs +17 -0
- package/dist/lib/selection-list.cjs.map +1 -0
- package/dist/lib/selection-list.d.cts +11 -0
- package/dist/lib/selection-list.d.ts +11 -0
- package/dist/lib/selection-list.js +15 -0
- package/dist/lib/selection-list.js.map +1 -0
- package/dist/lib/theme-types.cjs.map +1 -1
- package/dist/lib/theme-types.d.cts +2 -2
- package/dist/lib/theme-types.d.ts +2 -2
- package/dist/lib/theme-types.js.map +1 -1
- package/dist/lib/toggle-group-context.d.cts +2 -1
- package/dist/lib/toggle-group-context.d.ts +2 -1
- package/dist/styles/spark-default.css +12 -1
- package/dist/styles/theme.css +98 -0
- package/dist/{theme-types-Bn5E15-P.d.ts → theme-types-CKZzOmSa.d.cts} +11 -2
- package/dist/{theme-types-Bn5E15-P.d.cts → theme-types-CKZzOmSa.d.ts} +11 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.js","names":["React"],"sources":["../../src/components/list.tsx"],"sourcesContent":["import { useRender } from '@base-ui/react/use-render'\nimport * as React from 'react'\nimport type { ContainerRadius } from '../lib/container-chrome'\nimport type { SparkCardColor, SparkCardVariant } from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { Skeleton } from './skeleton'\n\ntype ListVariant = SparkCardVariant\ntype ListRadius = ContainerRadius\n\n// Rounds only the first/last row of a run, regardless of variant — harmless on ghost\n// (nothing painted to clip). Keyed on `data-list-radius` alone: unlike fill (below),\n// rounding doesn't depend on variant/color, so no compound group-data selector is needed.\n// Named `list-radius`, not `radius`: the global Roundness knob (Theme) also writes a\n// bare `data-radius` via an unscoped `[data-radius=...]` selector in spark-default.css —\n// `none`/`full` are spelled the same in both scales, so a plain `data-radius` here would\n// collide and let List silently override the app-wide knob for its whole subtree.\nconst radiusRowClasses: Record<ListRadius, string> = {\n none: 'group-data-[list-radius=none]/list:first:rounded-t-none group-data-[list-radius=none]/list:last:rounded-b-none',\n sm: 'group-data-[list-radius=sm]/list:first:rounded-t-sm group-data-[list-radius=sm]/list:last:rounded-b-sm',\n md: 'group-data-[list-radius=md]/list:first:rounded-t-md group-data-[list-radius=md]/list:last:rounded-b-md',\n lg: 'group-data-[list-radius=lg]/list:first:rounded-t-lg group-data-[list-radius=lg]/list:last:rounded-b-lg',\n xl: 'group-data-[list-radius=xl]/list:first:rounded-t-xl group-data-[list-radius=xl]/list:last:rounded-b-xl',\n '2xl':\n 'group-data-[list-radius=2xl]/list:first:rounded-t-2xl group-data-[list-radius=2xl]/list:last:rounded-b-2xl',\n '3xl':\n 'group-data-[list-radius=3xl]/list:first:rounded-t-3xl group-data-[list-radius=3xl]/list:last:rounded-b-3xl',\n full: 'group-data-[list-radius=full]/list:first:rounded-t-full group-data-[list-radius=full]/list:last:rounded-b-full',\n}\n\n// Fill and border colour depend on variant *and* colour together, so they live on\n// data-look (see the comment on `look` in List, below).\nconst lookClasses = [\n 'group-data-[look=soft]/list:bg-gray-3 dark:group-data-[look=soft]/list:bg-gray-a3',\n // arbitrary-var form, not the bg-spark-a3 utility: identical colour, but avoids a\n // literal substring collision with the unconditional selection/hover tint below,\n // which a couple of tests distinguish with a plain className.includes check.\n 'group-data-[look=soft-accent]/list:bg-(--spark-a3)',\n 'group-data-[look=surface]/list:border-gray-a6 group-data-[look=surface]/list:bg-gray-3 dark:group-data-[look=surface]/list:bg-gray-a3',\n 'group-data-[look=surface-accent]/list:border-spark-a6 group-data-[look=surface-accent]/list:bg-(--spark-surface)',\n 'group-data-[look=outline]/list:border-gray-a6',\n 'group-data-[look=outline-accent]/list:border-spark-a6',\n 'group-data-[look=solid]/list:bg-gray-1',\n 'group-data-[look=solid-accent]/list:bg-spark-9',\n]\n// The look fills out-specify a plain bg utility, so a selected row with a detail keeps\n// only the borders.\nconst borderLookClasses = [\n 'group-data-[look=surface]/list:border-gray-a6',\n 'group-data-[look=surface-accent]/list:border-spark-a6',\n 'group-data-[look=outline]/list:border-gray-a6',\n 'group-data-[look=outline-accent]/list:border-spark-a6',\n]\n\n// ListItem publishes `recent` so ListItemTitle can render the inline recency dot; the\n// column context lets the title show it only on the primary (start) column, never the\n// value column of a two-column row.\nconst ListItemContext = React.createContext<{ recent: boolean }>({\n recent: false,\n})\nconst ListItemColumnContext = React.createContext<{ isEnd: boolean }>({\n isEnd: false,\n})\n\ninterface ListProps extends React.ComponentProps<'ul'> {\n /** A prop, not the ambient `data-style` knob — that would cascade into row content. */\n variant?: ListVariant\n /** Chrome palette. `neutral` (default) tracks the app neutral; `accent` tints with the brand accent. */\n color?: SparkCardColor\n /** Corner radius, as a named step. Unset → `lg`, today's fixed behaviour. */\n radius?: ListRadius\n /** How many `--spark-layout-gap` units apart each row sits (`soft`/`solid`/`ghost` only). */\n gap?: number\n}\n\nfunction List({\n className,\n variant = 'soft',\n color = 'neutral',\n radius = 'lg',\n gap = 0.5,\n style,\n ...props\n}: ListProps) {\n // Fill/border-colour needs BOTH axes at once (e.g. \"soft, tinted with accent\"), which a\n // single group-data selector can't express and chaining two on the same named group\n // can't either (that generates a descendant match between two *different* ancestors,\n // not one ancestor bearing both attributes). Resolving the pair to one value up front\n // sidesteps it entirely — see the `data-look` selectors on ListItem below.\n const look = color === 'accent' ? `${variant}-accent` : variant\n return (\n <ul\n // role=list keeps list semantics even under list-style:none (Safari drops them).\n role=\"list\"\n data-slot=\"list\"\n // group/list + data-variant drive every item's per-variant chrome (below).\n data-variant={variant}\n data-look={look}\n data-list-radius={radius}\n className={cn(\n 'group/list m-0 list-none p-0',\n // soft/solid = separate rows → stack with a small gap (the gap IS the divider).\n // ghost shares that same gap for spacing but no fill/border (see ListItem's\n // chrome block, which has no ghost selector). surface/outline glue rows into one\n // panel with no gap at all.\n 'data-[variant=soft]:flex data-[variant=soft]:flex-col data-[variant=soft]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n 'data-[variant=solid]:flex data-[variant=solid]:flex-col data-[variant=solid]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n 'data-[variant=ghost]:flex data-[variant=ghost]:flex-col data-[variant=ghost]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n className,\n )}\n style={\n {\n '--list-gap-steps': gap,\n // solid's fill inverts far enough that the row's own ink must invert with it\n // (unlike soft/surface/outline's translucent tints, where the default ink\n // stays legible regardless of colour) — matches Card's own solid recipe,\n // where fg and fg-muted both collapse to the one contrast-guaranteed step.\n ...(variant === 'solid' && {\n '--list-item-fg':\n color === 'accent'\n ? 'var(--spark-contrast)'\n : 'var(--neutral-12)',\n '--list-item-fg-muted':\n color === 'accent'\n ? 'var(--spark-contrast)'\n : 'var(--neutral-12)',\n }),\n ...style,\n } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\ninterface ListItemProps extends React.ComponentProps<'li'> {\n /** The \"recently changed\" accent dot. */\n recent?: boolean\n /** Persistent accent-tint (e.g. the active document). */\n selected?: boolean\n /** Loading placeholder (Figma `skeleton=true`). */\n skeleton?: boolean\n /**\n * Base UI polymorphism for the inner row. Pass `<a href/>` or `<button/>` to make\n * the row interactive (accent-on-hover/focus, cursor, focus ring). Omit → static row.\n */\n render?: useRender.RenderProp\n}\n\nfunction splitListItemChildren(\n children: React.ReactNode,\n render: ListItemProps['render'],\n skeleton: boolean | undefined,\n) {\n if (skeleton) return {}\n const childArray = React.Children.toArray(children)\n // Only a ListItemLeading actually marked `interactive` (a Checkbox/Radio/Button) needs\n // to move outside `render`'s element, and only when `render` is literally an <a>/\n // <button> — nesting a focusable control inside another interactive element is invalid\n // HTML and double-fires clicks. A `render` of anything else (a `<Card/>`, say) has no\n // such conflict: it isn't natively interactive itself, so a control inside it is valid,\n // unduplicated HTML, and hoisting would only pull the control outside whatever that\n // element paints (its fill, border, rounded corner) for no reason. A plain icon\n // (interactive unset) is the same story from the other side — either way, Leading stays\n // inside the row by default, so the whole visual row remains one click/tap target.\n const rendersAnchorOrButton =\n React.isValidElement(render) &&\n (render.type === 'a' || render.type === 'button')\n const leadingChild = childArray.find(\n (child): child is React.ReactElement<ListItemLeadingProps> =>\n React.isValidElement(child) && child.type === ListItemLeading,\n )\n const leading =\n rendersAnchorOrButton && leadingChild?.props.interactive\n ? leadingChild\n : undefined\n // Detail always sits outside the row, so it may hold its own controls and never joins\n // the row's link/button.\n const detail = childArray.find(\n (child): child is React.ReactElement<ListItemDetailProps> =>\n React.isValidElement(child) && child.type === ListItemDetail,\n )\n return {\n leading,\n detail,\n rowChildren: childArray.filter(\n (child) => child !== leading && child !== detail,\n ),\n }\n}\n\nfunction itemClassName({\n className,\n hoistLeading,\n hasDetail,\n flushDetail,\n isSelected,\n interactive,\n}: {\n className: string | undefined\n hoistLeading: boolean\n hasDetail: boolean\n flushDetail: boolean\n isSelected: boolean | undefined\n interactive: boolean\n}) {\n const detailSelected = hasDetail && isSelected\n return cn(\n 'relative has-focus-visible:z-10',\n !hoistLeading && detailSelected && 'bg-spark-a3',\n hoistLeading && [\n // a flush detail must reach the item's left edge, so the inset moves to the leading.\n flushDetail\n ? 'flex min-w-0 items-center gap-4 *:data-[slot=list-item-leading]:pl-4'\n : 'flex min-w-0 items-center gap-4 pl-4',\n // a detail wraps onto its own full-width line below the leading + row.\n hasDetail && 'flex-wrap gap-y-0 *:data-[slot=list-item-detail]:pl-0',\n isSelected && 'bg-spark-a3',\n interactive && [\n // neutral at rest → subtle accent tint on hover/focus (the affordance goes\n // accent too, in ListItemTrailing; title + leading stay neutral). The click\n // target itself is `row`, so focus-visible (unlike hover) has to be read off\n // a descendant via has() — it never lands on this <li> directly.\n 'cursor-pointer',\n // hovering the detail must not suggest it navigates.\n hasDetail\n ? 'has-focus-visible:bg-spark-a3 has-[[data-slot=list-item-row]:hover]:bg-spark-a3'\n : 'hover:bg-spark-a3 has-focus-visible:bg-spark-a3',\n 'has-focus-visible:ring-2 has-focus-visible:ring-offset-2',\n ],\n ],\n // soft/solid rows sit in a gapped stack, not a glued panel, but still read as one\n // rounded group: only the first/last row round the outer corners. surface/\n // outline glue rows into one bordered panel instead — border SIDES are variant-\n // only (no colour axis), so they stay on data-variant; fill and border COLOUR\n // live on data-look instead (lookClasses, above).\n 'group-data-[variant=surface]/list:border-x group-data-[variant=surface]/list:border-t group-data-[variant=surface]/list:last:border-b',\n 'group-data-[variant=outline]/list:border-x group-data-[variant=outline]/list:border-t group-data-[variant=outline]/list:last:border-b',\n detailSelected ? borderLookClasses : lookClasses,\n Object.values(radiusRowClasses),\n className,\n )\n}\n\nfunction ListItem({\n className,\n children,\n recent,\n selected,\n skeleton,\n render,\n ...props\n}: ListItemProps) {\n // Skeleton rows are decorative placeholders — never render as a real <a>/<button>. An\n // aria-hidden anchor would still be focusable (WCAG 4.1.2), so skeleton forces a plain,\n // non-interactive <div> regardless of `render`.\n const interactive = !skeleton && render != null\n\n const recentCtx = React.useMemo(() => ({ recent: recent ?? false }), [recent])\n\n const { leading, detail, rowChildren } = splitListItemChildren(\n children,\n render,\n skeleton,\n )\n const hoistLeading = leading != null\n // With a detail the <li> is the visual item, so selection must tint it, not just the row.\n const selectOnItem = hoistLeading || detail != null\n const isSelected = !skeleton && selected\n\n const row = useRender({\n defaultTagName: 'div',\n render: skeleton ? undefined : render,\n props: {\n 'data-slot': 'list-item-row',\n // skeleton is a decorative placeholder → keep it out of the a11y tree.\n 'aria-hidden': skeleton ? true : undefined,\n className: cn(\n // row layout from Figma 2217-1667; min-h is a HARD 44px floor (WCAG 2.5.5),\n // py/gap/text still scale past it via the spacing/scaling knobs. no-underline\n // resets the default <a> decoration when the row renders as a link.\n 'flex min-h-[2.75rem] items-center gap-4 py-3 text-left no-underline',\n hoistLeading\n ? 'min-w-0 flex-1 pr-4'\n : detail\n ? 'w-full rounded-t-[inherit] px-4'\n : 'w-full rounded-[inherit] px-4',\n // not hoisted → this row IS the whole visual item (no outer <li> chrome), so\n // selection tint lives here, same as a row with no leading slot at all.\n !selectOnItem && isSelected && 'bg-spark-a3',\n interactive && [\n 'group/row cursor-pointer transition-colors duration-150 motion-reduce:transition-none',\n 'disabled:pointer-events-none disabled:opacity-50',\n hoistLeading\n ? // the custom focus ring is drawn on the outer <li> (has-focus-visible,\n // below) so it wraps the leading control too — but the browser's own\n // default outline still targets whichever element actually has focus,\n // which is this one, so it has to be suppressed here specifically.\n 'focus-visible:outline-hidden'\n : [\n 'hover:bg-spark-a3 focus-visible:bg-spark-a3',\n 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n ],\n ],\n ),\n children: skeleton ? <ListItemSkeleton /> : rowChildren,\n },\n })\n\n return (\n <li\n data-slot=\"list-item\"\n className={itemClassName({\n className,\n hoistLeading,\n hasDetail: detail != null,\n flushDetail: Boolean(detail?.props.flush),\n isSelected,\n interactive,\n })}\n {...props}\n >\n <ListItemContext.Provider value={recentCtx}>\n {leading}\n {row}\n {detail}\n </ListItemContext.Provider>\n </li>\n )\n}\n\ninterface ListItemDetailProps extends React.ComponentProps<'div'> {\n /** Drops the padding so the detail runs edge to edge and to the item's bottom, clipped to its rounded corners. */\n flush?: boolean\n}\n\nfunction ListItemDetail({ className, flush, ...props }: ListItemDetailProps) {\n return (\n <div\n data-slot=\"list-item-detail\"\n className={cn(\n 'w-full min-w-0 cursor-auto',\n // clips itself, not the <li>, so the row's focus ring is never cut off.\n flush ? 'overflow-hidden rounded-b-[inherit]' : 'px-4 pb-3',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ListItemSkeleton() {\n // Compose the shared Skeleton primitive so the placeholder matches every other skeleton\n // (token + radius + animation + motion-reduce).\n return (\n <>\n <Skeleton className=\"size-10 shrink-0 rounded-full\" />\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <Skeleton className=\"h-3.5 w-1/3\" />\n <Skeleton className=\"h-3.5 w-2/3\" />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col items-end gap-2\">\n <Skeleton className=\"h-3.5 w-1/4\" />\n <Skeleton className=\"h-3.5 w-1/2\" />\n </div>\n </>\n )\n}\n\ninterface ListItemLeadingProps extends React.ComponentProps<'div'> {\n /**\n * Set when this slot holds a focusable control (Checkbox, Radio, Button). ListItem\n * then hoists it outside `render`'s element — nesting a real control inside the row's\n * own <a>/<button> is invalid HTML and double-fires clicks. Leave unset for decorative\n * content (an icon): it stays inside the row, so the whole visual row is one\n * click/tap target, same as a row with no leading slot at all.\n */\n interactive?: boolean\n}\n\nfunction ListItemLeading({\n className,\n interactive: _interactive,\n ...props\n}: ListItemLeadingProps) {\n return (\n <div\n data-slot=\"list-item-leading\"\n className={cn('flex shrink-0 items-center', className)}\n {...props}\n />\n )\n}\n\ninterface ListItemContentProps extends React.ComponentProps<'div'> {\n /** `end` right-aligns the column (the value column). */\n align?: 'start' | 'end'\n}\n\nfunction ListItemContent({\n className,\n align = 'start',\n ...props\n}: ListItemContentProps) {\n const columnCtx = React.useMemo(() => ({ isEnd: align === 'end' }), [align])\n return (\n <ListItemColumnContext.Provider value={columnCtx}>\n <div\n data-slot=\"list-item-content\"\n className={cn(\n 'flex min-w-0 flex-1 flex-col gap-1',\n align === 'end' ? 'items-end text-right' : 'items-start text-left',\n className,\n )}\n {...props}\n />\n </ListItemColumnContext.Provider>\n )\n}\n\nfunction ListItemPreline({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"list-item-preline\"\n className={cn(\n 'block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))',\n className,\n )}\n {...props}\n />\n )\n}\n\ninterface ListItemTitleProps extends React.ComponentProps<'span'> {\n /** `strong` keeps the title bold, e.g. for menu rows; `default` is medium, bold on a `recent` row. */\n emphasis?: 'default' | 'strong'\n}\n\nfunction ListItemTitle({\n className,\n children,\n emphasis = 'default',\n ...props\n}: ListItemTitleProps) {\n const { recent } = React.useContext(ListItemContext)\n const { isEnd } = React.useContext(ListItemColumnContext)\n // recency dot sits inline, immediately left of the title (Figma 1310-977) — only on the\n // primary (start) column, so a two-column value row's value never carries it.\n const showDot = recent && !isEnd\n\n // className/{...props} always land on THIS span (the one that truncates), so a caller's\n // override (e.g. `title`, `line-clamp-2 whitespace-normal`) behaves identically whether\n // or not the dot renders — only the wrapping structure differs below.\n const title = (\n <span\n data-slot=\"list-item-title\"\n className={cn(\n showDot ? 'min-w-0 truncate' : 'block w-full truncate',\n 'text-base text-(--list-item-fg,var(--neutral-12))',\n recent || emphasis === 'strong' ? 'font-bold' : 'font-medium',\n className,\n )}\n {...props}\n >\n {children}\n </span>\n )\n\n if (!showDot) {\n // No dot: stay a plain block so the column's text alignment (e.g. an end column's\n // text-right) governs the title directly.\n return title\n }\n\n return (\n <span className=\"flex w-full items-center gap-2\">\n <span\n aria-hidden=\"true\"\n data-slot=\"list-item-indicator\"\n className=\"size-2 shrink-0 rounded-full bg-spark-9\"\n />\n {title}\n </span>\n )\n}\n\nfunction ListItemSubline({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"list-item-subline\"\n className={cn(\n 'block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ListItemTrailing({\n className,\n ...props\n}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"list-item-trailing\"\n className={cn(\n 'flex shrink-0 items-center text-gray-11',\n // interactive rows tint the affordance accent on hover/focus (group/row is set on\n // the interactive inner row; absent on static rows, so this simply never fires).\n // Relies on the child reading currentColor (Spark icons do) — the colour-\n // independent Icon component hardcodes its own ink and won't pick this up; use\n // a Spark icon here, not Icon.\n 'transition-colors duration-150 motion-reduce:transition-none',\n 'group-hover/row:text-spark-a11 group-focus-visible/row:text-spark-a11',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport {\n List,\n ListItem,\n ListItemContent,\n ListItemDetail,\n ListItemLeading,\n ListItemPreline,\n ListItemSubline,\n ListItemTitle,\n ListItemTrailing,\n}\nexport type {\n ListProps,\n ListItemProps,\n ListItemContentProps,\n ListItemDetailProps,\n ListItemLeadingProps,\n ListItemTitleProps,\n ListVariant,\n ListRadius,\n}\n"],"mappings":";;;;;;;AAiBA,MAAM,mBAA+C;CACnD,MAAM;CACN,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,OACE;CACF,OACE;CACF,MAAM;AACR;AAIA,MAAM,cAAc;CAClB;CAIA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAGA,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;AACF;AAKA,MAAM,kBAAkBA,QAAM,cAAmC,EAC/D,QAAQ,MACV,CAAC;AACD,MAAM,wBAAwBA,QAAM,cAAkC,EACpE,OAAO,MACT,CAAC;AAaD,SAAS,KAAK,EACZ,WACA,UAAU,QACV,QAAQ,WACR,SAAS,MACT,MAAM,IACN,OACA,GAAG,SACS;CAOZ,OACE,oBAAC,MAAD;EAEE,MAAK;EACL,aAAU;EAEV,gBAAc;EACd,aARS,UAAU,WAAW,GAAG,QAAQ,WAAW;EASpD,oBAAkB;EAClB,WAAW,GACT,gCAKA,2IACA,8IACA,8IACA,SACF;EACA,OACE;GACE,oBAAoB;GAKpB,GAAI,YAAY,WAAW;IACzB,kBACE,UAAU,WACN,0BACA;IACN,wBACE,UAAU,WACN,0BACA;GACR;GACA,GAAG;EACL;EAEF,GAAI;CACL,CAAA;AAEL;AAgBA,SAAS,sBACP,UACA,QACA,UACA;CACA,IAAI,UAAU,OAAO,CAAC;CACtB,MAAM,aAAaA,QAAM,SAAS,QAAQ,QAAQ;CAUlD,MAAM,wBACJA,QAAM,eAAe,MAAM,MAC1B,OAAO,SAAS,OAAO,OAAO,SAAS;CAC1C,MAAM,eAAe,WAAW,MAC7B,UACCA,QAAM,eAAe,KAAK,KAAK,MAAM,SAAS,eAClD;CACA,MAAM,UACJ,yBAAyB,cAAc,MAAM,cACzC,eACA,KAAA;CAGN,MAAM,SAAS,WAAW,MACvB,UACCA,QAAM,eAAe,KAAK,KAAK,MAAM,SAAS,cAClD;CACA,OAAO;EACL;EACA;EACA,aAAa,WAAW,QACrB,UAAU,UAAU,WAAW,UAAU,MAC5C;CACF;AACF;AAEA,SAAS,cAAc,EACrB,WACA,cACA,WACA,aACA,YACA,eAQC;CACD,MAAM,iBAAiB,aAAa;CACpC,OAAO,GACL,mCACA,CAAC,gBAAgB,kBAAkB,eACnC,gBAAgB;EAEd,cACI,yEACA;EAEJ,aAAa;EACb,cAAc;EACd,eAAe;GAKb;GAEA,YACI,oFACA;GACJ;EACF;CACF,GAMA,yIACA,yIACA,iBAAiB,oBAAoB,aACrC,OAAO,OAAO,gBAAgB,GAC9B,SACF;AACF;AAEA,SAAS,SAAS,EAChB,WACA,UACA,QACA,UACA,UACA,QACA,GAAG,SACa;CAIhB,MAAM,cAAc,CAAC,YAAY,UAAU;CAE3C,MAAM,YAAYA,QAAM,eAAe,EAAE,QAAQ,UAAU,MAAM,IAAI,CAAC,MAAM,CAAC;CAE7E,MAAM,EAAE,SAAS,QAAQ,gBAAgB,sBACvC,UACA,QACA,QACF;CACA,MAAM,eAAe,WAAW;CAEhC,MAAM,eAAe,gBAAgB,UAAU;CAC/C,MAAM,aAAa,CAAC,YAAY;CAEhC,MAAM,MAAM,UAAU;EACpB,gBAAgB;EAChB,QAAQ,WAAW,KAAA,IAAY;EAC/B,OAAO;GACL,aAAa;GAEb,eAAe,WAAW,OAAO,KAAA;GACjC,WAAW,GAIT,uEACA,eACI,wBACA,SACE,oCACA,iCAGN,CAAC,gBAAgB,cAAc,eAC/B,eAAe;IACb;IACA;IACA,eAKI,iCACA,CACE,+CACA,+EACF;GACN,CACF;GACA,UAAU,WAAW,oBAAC,kBAAD,CAAmB,CAAA,IAAI;EAC9C;CACF,CAAC;CAED,OACE,oBAAC,MAAD;EACE,aAAU;EACV,WAAW,cAAc;GACvB;GACA;GACA,WAAW,UAAU;GACrB,aAAa,QAAQ,QAAQ,MAAM,KAAK;GACxC;GACA;EACF,CAAC;EACD,GAAI;YAEJ,qBAAC,gBAAgB,UAAjB;GAA0B,OAAO;aAAjC;IACG;IACA;IACA;GACuB;;CACxB,CAAA;AAER;AAOA,SAAS,eAAe,EAAE,WAAW,OAAO,GAAG,SAA8B;CAC3E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,8BAEA,QAAQ,wCAAwC,aAChD,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,mBAAmB;CAG1B,OACE,qBAAA,UAAA,EAAA,UAAA;EACE,oBAAC,UAAD,EAAU,WAAU,gCAAiC,CAAA;EACrD,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,UAAD,EAAU,WAAU,cAAe,CAAA,GACnC,oBAAC,UAAD,EAAU,WAAU,cAAe,CAAA,CAChC;;EACL,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,UAAD,EAAU,WAAU,cAAe,CAAA,GACnC,oBAAC,UAAD,EAAU,WAAU,cAAe,CAAA,CAChC;;CACL,EAAA,CAAA;AAEN;AAaA,SAAS,gBAAgB,EACvB,WACA,aAAa,cACb,GAAG,SACoB;CACvB,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,8BAA8B,SAAS;EACrD,GAAI;CACL,CAAA;AAEL;AAOA,SAAS,gBAAgB,EACvB,WACA,QAAQ,SACR,GAAG,SACoB;CACvB,MAAM,YAAYA,QAAM,eAAe,EAAE,OAAO,UAAU,MAAM,IAAI,CAAC,KAAK,CAAC;CAC3E,OACE,oBAAC,sBAAsB,UAAvB;EAAgC,OAAO;YACrC,oBAAC,OAAD;GACE,aAAU;GACV,WAAW,GACT,sCACA,UAAU,QAAQ,yBAAyB,yBAC3C,SACF;GACA,GAAI;EACL,CAAA;CAC6B,CAAA;AAEpC;AAEA,SAAS,gBAAgB,EACvB,WACA,GAAG,SAC4B;CAC/B,OACE,oBAAC,QAAD;EACE,aAAU;EACV,WAAW,GACT,+EACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAOA,SAAS,cAAc,EACrB,WACA,UACA,WAAW,WACX,GAAG,SACkB;CACrB,MAAM,EAAE,WAAWA,QAAM,WAAW,eAAe;CACnD,MAAM,EAAE,UAAUA,QAAM,WAAW,qBAAqB;CAGxD,MAAM,UAAU,UAAU,CAAC;CAK3B,MAAM,QACJ,oBAAC,QAAD;EACE,aAAU;EACV,WAAW,GACT,UAAU,qBAAqB,yBAC/B,qDACA,UAAU,aAAa,WAAW,cAAc,eAChD,SACF;EACA,GAAI;EAEH;CACG,CAAA;CAGR,IAAI,CAAC,SAGH,OAAO;CAGT,OACE,qBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,oBAAC,QAAD;GACE,eAAY;GACZ,aAAU;GACV,WAAU;EACX,CAAA,GACA,KACG;;AAEV;AAEA,SAAS,gBAAgB,EACvB,WACA,GAAG,SAC4B;CAC/B,OACE,oBAAC,QAAD;EACE,aAAU;EACV,WAAW,GACT,+EACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,iBAAiB,EACxB,WACA,GAAG,SAC2B;CAC9B,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,2CAMA,gEACA,yEACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
|
|
1
|
+
{"version":3,"file":"list.js","names":["React"],"sources":["../../src/components/list.tsx"],"sourcesContent":["import { useRender } from '@base-ui/react/use-render'\nimport { cva } from 'class-variance-authority'\nimport * as React from 'react'\nimport type { ContainerRadius } from '../lib/container-chrome'\nimport type { SparkCardColor, SparkCardVariant } from '../lib/theme-types'\nimport type { Tone } from '../lib/tones'\nimport { cn } from '../utils/cn'\nimport { Skeleton } from './skeleton'\n\ntype ListVariant = SparkCardVariant | 'ambient'\ntype ListRadius = ContainerRadius\n\n// Rounds only the first/last row of a run, regardless of variant — harmless on ghost\n// (nothing painted to clip). Keyed on `data-list-radius` alone: unlike fill (below),\n// rounding doesn't depend on variant/color, so no compound group-data selector is needed.\n// Named `list-radius`, not `radius`: the global Roundness knob (Theme) also writes a\n// bare `data-radius` via an unscoped `[data-radius=...]` selector in spark-default.css —\n// `none`/`full` are spelled the same in both scales, so a plain `data-radius` here would\n// collide and let List silently override the app-wide knob for its whole subtree.\nconst radiusRowClasses: Record<ListRadius, string> = {\n none: 'group-data-[list-radius=none]/list:first:rounded-t-none group-data-[list-radius=none]/list:last:rounded-b-none',\n sm: 'group-data-[list-radius=sm]/list:first:rounded-t-sm group-data-[list-radius=sm]/list:last:rounded-b-sm',\n md: 'group-data-[list-radius=md]/list:first:rounded-t-md group-data-[list-radius=md]/list:last:rounded-b-md',\n lg: 'group-data-[list-radius=lg]/list:first:rounded-t-lg group-data-[list-radius=lg]/list:last:rounded-b-lg',\n xl: 'group-data-[list-radius=xl]/list:first:rounded-t-xl group-data-[list-radius=xl]/list:last:rounded-b-xl',\n '2xl':\n 'group-data-[list-radius=2xl]/list:first:rounded-t-2xl group-data-[list-radius=2xl]/list:last:rounded-b-2xl',\n '3xl':\n 'group-data-[list-radius=3xl]/list:first:rounded-t-3xl group-data-[list-radius=3xl]/list:last:rounded-b-3xl',\n full: 'group-data-[list-radius=full]/list:first:rounded-t-full group-data-[list-radius=full]/list:last:rounded-b-full',\n}\n\n// Fill and border colour depend on variant *and* colour together, so they live on\n// data-look (see the comment on `look` in List, below).\nconst lookClasses = [\n 'group-data-[variant=ambient]/list:border-(--spark-card-border,transparent) group-data-[variant=ambient]/list:bg-(--spark-card-bg,var(--neutral-a3)) group-data-[variant=ambient]/list:[--spark-skeleton-bg:var(--neutral-a5)] group-data-[variant=ambient]/list:[--color-focus-root:var(--spark-card-focus,var(--spark-11))]',\n 'group-data-[look=soft]/list:bg-gray-3 dark:group-data-[look=soft]/list:bg-gray-a3',\n // arbitrary-var form, not the bg-spark-a3 utility: identical colour, but avoids a\n // literal substring collision with the unconditional selection/hover tint below,\n // which a couple of tests distinguish with a plain className.includes check.\n 'group-data-[look=soft-accent]/list:bg-(--spark-a3)',\n 'group-data-[look=surface]/list:border-gray-a6 group-data-[look=surface]/list:bg-gray-3 dark:group-data-[look=surface]/list:bg-gray-a3',\n 'group-data-[look=surface-accent]/list:border-spark-a6 group-data-[look=surface-accent]/list:bg-(--spark-surface)',\n 'group-data-[look=outline]/list:border-gray-a6',\n 'group-data-[look=outline-accent]/list:border-spark-a6',\n 'group-data-[look=solid]/list:bg-gray-1',\n 'group-data-[look=solid-accent]/list:bg-spark-9',\n]\n// The look fills out-specify a plain bg utility, so a selected row with a detail keeps\n// only the borders.\nconst borderLookClasses = [\n 'group-data-[variant=ambient]/list:border-(--spark-card-border,transparent)',\n 'group-data-[look=surface]/list:border-gray-a6',\n 'group-data-[look=surface-accent]/list:border-spark-a6',\n 'group-data-[look=outline]/list:border-gray-a6',\n 'group-data-[look=outline-accent]/list:border-spark-a6',\n]\n\n// ListItem publishes `recent` so ListItemTitle can render the inline recency dot; the\n// column context lets the title show it only on the primary (start) column, never the\n// value column of a two-column row.\nconst ListItemContext = React.createContext<{ recent: boolean }>({\n recent: false,\n})\nconst ListItemColumnContext = React.createContext<{ isEnd: boolean }>({\n isEnd: false,\n})\n\ninterface ListProps extends React.ComponentProps<'ul'> {\n /**\n * A prop, not the ambient `data-style` knob — that would cascade into row content.\n * `ambient` follows the app-level `[data-card-variant]` knob, like an unset Card `variant`.\n */\n variant?: ListVariant\n /** Chrome palette. `neutral` (default) tracks the app neutral; `accent` tints with the brand accent. */\n color?: SparkCardColor\n /** Corner radius, as a named step. Unset → `lg`, today's fixed behaviour. */\n radius?: ListRadius\n /** How many `--spark-layout-gap` units apart each row sits (`soft`/`solid`/`ghost`, or `ambient` under those knobs). */\n gap?: number\n}\n\nfunction List({\n className,\n variant = 'soft',\n color,\n radius = 'lg',\n gap = 0.5,\n style,\n ...props\n}: ListProps) {\n // Fill/border-colour needs BOTH axes at once (e.g. \"soft, tinted with accent\"), which a\n // single group-data selector can't express and chaining two on the same named group\n // can't either (that generates a descendant match between two *different* ancestors,\n // not one ancestor bearing both attributes). Resolving the pair to one value up front\n // sidesteps it entirely — see the `data-look` selectors on ListItem below.\n const look = color === 'accent' ? `${variant}-accent` : variant\n return (\n <ul\n // role=list keeps list semantics even under list-style:none (Safari drops them).\n role=\"list\"\n data-slot=\"list\"\n // group/list + data-variant drive every item's per-variant chrome (below).\n data-variant={variant}\n data-look={look}\n data-card-color={variant === 'ambient' ? color : undefined}\n data-list-radius={radius}\n className={cn(\n 'group/list m-0 list-none p-0',\n // soft/solid = separate rows → stack with a small gap (the gap IS the divider).\n // ghost shares that same gap for spacing but no fill/border (see ListItem's\n // chrome block, which has no ghost selector). surface/outline glue rows into one\n // panel with no gap at all.\n 'data-[variant=soft]:flex data-[variant=soft]:flex-col data-[variant=soft]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n 'data-[variant=solid]:flex data-[variant=solid]:flex-col data-[variant=solid]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n // ambient gaps like the knob's own List variant: --spark-card-list-gapped is 0 for surface/outline.\n 'data-[variant=ambient]:flex data-[variant=ambient]:flex-col data-[variant=ambient]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5)*var(--spark-card-list-gapped,1))]',\n 'data-[variant=ghost]:flex data-[variant=ghost]:flex-col data-[variant=ghost]:gap-[calc(var(--spark-layout-gap)*var(--list-gap-steps,0.5))]',\n className,\n )}\n style={\n {\n '--list-gap-steps': gap,\n // solid's fill inverts far enough that the row's own ink must invert with it\n // (unlike soft/surface/outline's translucent tints, where the default ink\n // stays legible regardless of colour) — matches Card's own solid recipe,\n // where fg and fg-muted both collapse to the one contrast-guaranteed step.\n ...(variant === 'ambient' && {\n '--list-item-fg': 'var(--spark-card-fg,var(--neutral-12))',\n '--list-item-fg-muted':\n 'var(--spark-card-fg-muted,var(--neutral-11))',\n }),\n ...(variant === 'solid' && {\n '--list-item-fg':\n color === 'accent'\n ? 'var(--spark-contrast)'\n : 'var(--neutral-12)',\n '--list-item-fg-muted':\n color === 'accent'\n ? 'var(--spark-contrast)'\n : 'var(--neutral-12)',\n }),\n ...style,\n } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\ninterface ListItemProps extends React.ComponentProps<'li'> {\n /** The \"recently changed\" accent dot. */\n recent?: boolean\n /** Persistent accent-tint (e.g. the active document). */\n selected?: boolean\n /** Loading placeholder (Figma `skeleton=true`). */\n skeleton?: boolean\n /**\n * Base UI polymorphism for the inner row. Pass `<a href/>` or `<button/>` to make\n * the row interactive (accent-on-hover/focus, cursor, focus ring). Omit → static row.\n */\n render?: useRender.RenderProp\n}\n\nfunction splitListItemChildren(\n children: React.ReactNode,\n render: ListItemProps['render'],\n skeleton: boolean | undefined,\n) {\n if (skeleton) return {}\n const childArray = React.Children.toArray(children)\n // Only a ListItemLeading actually marked `interactive` (a Checkbox/Radio/Button) needs\n // to move outside `render`'s element, and only when `render` is literally an <a>/\n // <button> — nesting a focusable control inside another interactive element is invalid\n // HTML and double-fires clicks. A `render` of anything else (a `<Card/>`, say) has no\n // such conflict: it isn't natively interactive itself, so a control inside it is valid,\n // unduplicated HTML, and hoisting would only pull the control outside whatever that\n // element paints (its fill, border, rounded corner) for no reason. A plain icon\n // (interactive unset) is the same story from the other side — either way, Leading stays\n // inside the row by default, so the whole visual row remains one click/tap target.\n const rendersAnchorOrButton =\n React.isValidElement(render) &&\n (render.type === 'a' || render.type === 'button')\n const leadingChild = childArray.find(\n (child): child is React.ReactElement<ListItemLeadingProps> =>\n React.isValidElement(child) && child.type === ListItemLeading,\n )\n const leading =\n rendersAnchorOrButton && leadingChild?.props.interactive\n ? leadingChild\n : undefined\n // Detail always sits outside the row, so it may hold its own controls and never joins\n // the row's link/button.\n const detail = childArray.find(\n (child): child is React.ReactElement<ListItemDetailProps> =>\n React.isValidElement(child) && child.type === ListItemDetail,\n )\n return {\n leading,\n detail,\n rowChildren: childArray.filter(\n (child) => child !== leading && child !== detail,\n ),\n }\n}\n\nfunction itemClassName({\n className,\n hoistLeading,\n hasDetail,\n flushDetail,\n isSelected,\n interactive,\n}: {\n className: string | undefined\n hoistLeading: boolean\n hasDetail: boolean\n flushDetail: boolean\n isSelected: boolean | undefined\n interactive: boolean\n}) {\n const detailSelected = hasDetail && isSelected\n return cn(\n 'relative has-focus-visible:z-10',\n !hoistLeading && detailSelected && 'bg-spark-a3',\n hoistLeading && [\n // a flush detail must reach the item's left edge, so the inset moves to the leading.\n flushDetail\n ? 'flex min-w-0 items-center gap-4 *:data-[slot=list-item-leading]:pl-4'\n : 'flex min-w-0 items-center gap-4 pl-4',\n // a detail wraps onto its own full-width line below the leading + row.\n hasDetail && 'flex-wrap gap-y-0 *:data-[slot=list-item-detail]:pl-0',\n isSelected && 'bg-spark-a3',\n interactive && [\n // neutral at rest → subtle accent tint on hover/focus (the affordance goes\n // accent too, in ListItemTrailing; title + leading stay neutral). The click\n // target itself is `row`, so focus-visible (unlike hover) has to be read off\n // a descendant via has() — it never lands on this <li> directly.\n 'cursor-pointer',\n // hovering the detail must not suggest it navigates.\n hasDetail\n ? 'has-focus-visible:bg-spark-a3 has-[[data-slot=list-item-row]:hover]:bg-spark-a3'\n : 'hover:bg-spark-a3 has-focus-visible:bg-spark-a3',\n 'has-focus-visible:ring-2 has-focus-visible:ring-offset-2',\n ],\n ],\n // soft/solid rows sit in a gapped stack, not a glued panel, but still read as one\n // rounded group: only the first/last row round the outer corners. surface/\n // outline glue rows into one bordered panel instead — border SIDES are variant-\n // only (no colour axis), so they stay on data-variant; fill and border COLOUR\n // live on data-look instead (lookClasses, above).\n 'group-data-[variant=surface]/list:border-x group-data-[variant=surface]/list:border-t group-data-[variant=surface]/list:last:border-b',\n 'group-data-[variant=outline]/list:border-x group-data-[variant=outline]/list:border-t group-data-[variant=outline]/list:last:border-b',\n // ambient glues like surface/outline under a bordered knob; a borderless knob's width is 0, leaving only the gap.\n 'group-data-[variant=ambient]/list:border-x-(length:--spark-card-border-width,0px) group-data-[variant=ambient]/list:border-t-(length:--spark-card-border-width,0px) group-data-[variant=ambient]/list:last:border-b-(length:--spark-card-border-width,0px)',\n detailSelected ? borderLookClasses : lookClasses,\n Object.values(radiusRowClasses),\n className,\n )\n}\n\nfunction ListItem({\n className,\n children,\n recent,\n selected,\n skeleton,\n render,\n ...props\n}: ListItemProps) {\n // Skeleton rows are decorative placeholders — never render as a real <a>/<button>. An\n // aria-hidden anchor would still be focusable (WCAG 4.1.2), so skeleton forces a plain,\n // non-interactive <div> regardless of `render`.\n const interactive = !skeleton && render != null\n\n const recentCtx = React.useMemo(() => ({ recent: recent ?? false }), [recent])\n\n const { leading, detail, rowChildren } = splitListItemChildren(\n children,\n render,\n skeleton,\n )\n const hoistLeading = leading != null\n // With a detail the <li> is the visual item, so selection must tint it, not just the row.\n const selectOnItem = hoistLeading || detail != null\n const isSelected = !skeleton && selected\n\n const row = useRender({\n defaultTagName: 'div',\n render: skeleton ? undefined : render,\n props: {\n 'data-slot': 'list-item-row',\n // skeleton is a decorative placeholder → keep it out of the a11y tree.\n 'aria-hidden': skeleton ? true : undefined,\n className: cn(\n // row layout from Figma 2217-1667; min-h is a HARD 44px floor (WCAG 2.5.5),\n // py/gap/text still scale past it via the spacing/scaling knobs. no-underline\n // resets the default <a> decoration when the row renders as a link.\n 'flex min-h-[2.75rem] items-center gap-4 py-3 text-left no-underline',\n hoistLeading\n ? 'min-w-0 flex-1 pr-4'\n : detail\n ? 'w-full rounded-t-[inherit] px-4'\n : 'w-full rounded-[inherit] px-4',\n // not hoisted → this row IS the whole visual item (no outer <li> chrome), so\n // selection tint lives here, same as a row with no leading slot at all.\n !selectOnItem && isSelected && 'bg-spark-a3',\n interactive && [\n 'group/row cursor-pointer transition-colors duration-150 motion-reduce:transition-none',\n 'disabled:pointer-events-none disabled:opacity-50',\n hoistLeading\n ? // the custom focus ring is drawn on the outer <li> (has-focus-visible,\n // below) so it wraps the leading control too — but the browser's own\n // default outline still targets whichever element actually has focus,\n // which is this one, so it has to be suppressed here specifically.\n 'focus-visible:outline-hidden'\n : [\n 'hover:bg-spark-a3 focus-visible:bg-spark-a3',\n 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n ],\n ],\n ),\n children: skeleton ? <ListItemSkeleton /> : rowChildren,\n },\n })\n\n return (\n <li\n data-slot=\"list-item\"\n className={itemClassName({\n className,\n hoistLeading,\n hasDetail: detail != null,\n flushDetail: Boolean(detail?.props.flush),\n isSelected,\n interactive,\n })}\n {...props}\n >\n <ListItemContext.Provider value={recentCtx}>\n {leading}\n {row}\n {detail}\n </ListItemContext.Provider>\n </li>\n )\n}\n\ninterface ListItemDetailProps extends React.ComponentProps<'div'> {\n /** Drops the padding so the detail runs edge to edge and to the item's bottom, clipped to its rounded corners. */\n flush?: boolean\n}\n\nfunction ListItemDetail({ className, flush, ...props }: ListItemDetailProps) {\n return (\n <div\n data-slot=\"list-item-detail\"\n className={cn(\n 'w-full min-w-0 cursor-auto',\n // clips itself, not the <li>, so the row's focus ring is never cut off.\n flush ? 'overflow-hidden rounded-b-[inherit]' : 'px-4 pb-3',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction ListItemSkeleton() {\n // Compose the shared Skeleton primitive so the placeholder matches every other skeleton\n // (token + radius + animation + motion-reduce).\n return (\n <>\n <Skeleton className=\"size-10 shrink-0 rounded-full\" />\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <Skeleton className=\"h-3.5 w-1/3\" />\n <Skeleton className=\"h-3.5 w-2/3\" />\n </div>\n <div className=\"flex min-w-0 flex-1 flex-col items-end gap-2\">\n <Skeleton className=\"h-3.5 w-1/4\" />\n <Skeleton className=\"h-3.5 w-1/2\" />\n </div>\n </>\n )\n}\n\ninterface ListItemLeadingProps extends React.ComponentProps<'div'> {\n /**\n * Set when this slot holds a focusable control (Checkbox, Radio, Button). ListItem\n * then hoists it outside `render`'s element — nesting a real control inside the row's\n * own <a>/<button> is invalid HTML and double-fires clicks. Leave unset for decorative\n * content (an icon): it stays inside the row, so the whole visual row is one\n * click/tap target, same as a row with no leading slot at all.\n */\n interactive?: boolean\n}\n\nfunction ListItemLeading({\n className,\n interactive: _interactive,\n ...props\n}: ListItemLeadingProps) {\n return (\n <div\n data-slot=\"list-item-leading\"\n className={cn('flex shrink-0 items-center', className)}\n {...props}\n />\n )\n}\n\ninterface ListItemContentProps extends React.ComponentProps<'div'> {\n /** `end` right-aligns the column (the value column). */\n align?: 'start' | 'end'\n}\n\nfunction ListItemContent({\n className,\n align = 'start',\n ...props\n}: ListItemContentProps) {\n const columnCtx = React.useMemo(() => ({ isEnd: align === 'end' }), [align])\n return (\n <ListItemColumnContext.Provider value={columnCtx}>\n <div\n data-slot=\"list-item-content\"\n className={cn(\n 'flex min-w-0 flex-1 flex-col gap-1',\n align === 'end' ? 'items-end text-right' : 'items-start text-left',\n className,\n )}\n {...props}\n />\n </ListItemColumnContext.Provider>\n )\n}\n\nfunction ListItemPreline({\n className,\n ...props\n}: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"list-item-preline\"\n className={cn(\n 'block w-full truncate text-sm text-(--list-item-fg-muted,var(--neutral-11))',\n className,\n )}\n {...props}\n />\n )\n}\n\ninterface ListItemTitleProps extends React.ComponentProps<'span'> {\n /** `strong` keeps the title bold, e.g. for menu rows; `default` is medium, bold on a `recent` row. */\n emphasis?: 'default' | 'strong'\n}\n\nfunction ListItemTitle({\n className,\n children,\n emphasis = 'default',\n ...props\n}: ListItemTitleProps) {\n const { recent } = React.useContext(ListItemContext)\n const { isEnd } = React.useContext(ListItemColumnContext)\n // recency dot sits inline, immediately left of the title (Figma 1310-977) — only on the\n // primary (start) column, so a two-column value row's value never carries it.\n const showDot = recent && !isEnd\n\n // className/{...props} always land on THIS span (the one that truncates), so a caller's\n // override (e.g. `title`, `line-clamp-2 whitespace-normal`) behaves identically whether\n // or not the dot renders — only the wrapping structure differs below.\n const title = (\n <span\n data-slot=\"list-item-title\"\n className={cn(\n showDot ? 'min-w-0 truncate' : 'block w-full truncate',\n 'text-base text-(--list-item-fg,var(--neutral-12))',\n recent || emphasis === 'strong' ? 'font-bold' : 'font-medium',\n className,\n )}\n {...props}\n >\n {children}\n </span>\n )\n\n if (!showDot) {\n // No dot: stay a plain block so the column's text alignment (e.g. an end column's\n // text-right) governs the title directly.\n return title\n }\n\n return (\n <span className=\"flex w-full items-center gap-2\">\n <span\n aria-hidden=\"true\"\n data-slot=\"list-item-indicator\"\n className=\"size-2 shrink-0 rounded-full bg-spark-9\"\n />\n {title}\n </span>\n )\n}\n\nconst listItemSublineVariants = cva('block w-full truncate text-sm', {\n variants: {\n tone: {\n neutral: 'text-(--list-item-fg-muted,var(--neutral-11))',\n success: 'text-success-soft-foreground',\n error: 'text-error-soft-foreground',\n },\n },\n defaultVariants: { tone: 'neutral' },\n})\n\ninterface ListItemSublineProps extends React.ComponentProps<'span'> {\n tone?: Extract<Tone, 'neutral' | 'success' | 'error'>\n}\n\nfunction ListItemSubline({ className, tone, ...props }: ListItemSublineProps) {\n return (\n <span\n data-slot=\"list-item-subline\"\n className={cn(listItemSublineVariants({ tone }), className)}\n {...props}\n />\n )\n}\n\nfunction ListItemTrailing({\n className,\n ...props\n}: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"list-item-trailing\"\n className={cn(\n 'flex shrink-0 items-center text-gray-11',\n // interactive rows tint the affordance accent on hover/focus (group/row is set on\n // the interactive inner row; absent on static rows, so this simply never fires).\n // Relies on the child reading currentColor (Spark icons do) — the colour-\n // independent Icon component hardcodes its own ink and won't pick this up; use\n // a Spark icon here, not Icon.\n 'transition-colors duration-150 motion-reduce:transition-none',\n 'group-hover/row:text-spark-a11 group-focus-visible/row:text-spark-a11',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport {\n List,\n ListItem,\n ListItemContent,\n ListItemDetail,\n ListItemLeading,\n ListItemPreline,\n ListItemSubline,\n ListItemTitle,\n ListItemTrailing,\n}\nexport type {\n ListProps,\n ListItemProps,\n ListItemContentProps,\n ListItemDetailProps,\n ListItemLeadingProps,\n ListItemTitleProps,\n ListVariant,\n ListRadius,\n}\n"],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ let _base_ui_react_merge_props = require("@base-ui/react/merge-props");
|
|
|
9
9
|
let _base_ui_react_use_render = require("@base-ui/react/use-render");
|
|
10
10
|
let cn = require("cn");
|
|
11
11
|
//#region src/components/marker.tsx
|
|
12
|
-
const markerVariants = (0, class_variance_authority.cva)("group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-gray-light [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-gray", { variants: { variant: {
|
|
12
|
+
const markerVariants = (0, class_variance_authority.cva)("group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-gray-light [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-gray [&:is(button,a)]:outline-hidden [&:is(button,a)]:focus-visible:rounded-sm [&:is(button,a)]:focus-visible:ring-2 [&:is(button,a)]:focus-visible:ring-offset-2", { variants: { variant: {
|
|
13
13
|
default: "[&:is(button,a)]:touch-target",
|
|
14
14
|
separator: "before:mr-1 before:h-px before:min-w-0 before:flex-1 before:bg-gray-a6 after:ml-1 after:h-px after:min-w-0 after:flex-1 after:bg-gray-a6",
|
|
15
15
|
border: "border-b border-gray pb-2 [&:is(button,a)]:touch-target"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"marker.cjs","names":[],"sources":["../../src/components/marker.tsx"],"sourcesContent":["import { mergeProps } from '@base-ui/react/merge-props'\nimport { useRender } from '@base-ui/react/use-render'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\nconst markerVariants = cva(\n \"group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-gray-light [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-gray\",\n {\n variants: {\n variant: {\n // touch-target only where the marker is actually a control (a `render`\n // of <button>/<a>) — and never on `separator`, whose flanking rules\n // already own ::after, the same pseudo-element the utility uses.\n default: '[&:is(button,a)]:touch-target',\n separator:\n 'before:mr-1 before:h-px before:min-w-0 before:flex-1 before:bg-gray-a6 after:ml-1 after:h-px after:min-w-0 after:flex-1 after:bg-gray-a6',\n border: 'border-b border-gray pb-2 [&:is(button,a)]:touch-target',\n },\n },\n },\n)\n\n// `render` replaces radix `asChild` — pass an element (e.g. a <button> for a\n// clickable \"jump to\" marker) and Base UI's useRender merges props onto it.\nfunction Marker({\n className,\n variant = 'default',\n render,\n ...props\n}: useRender.ComponentProps<'div'> & VariantProps<typeof markerVariants>) {\n return useRender({\n defaultTagName: 'div',\n render,\n props: mergeProps<'div'>(\n {\n 'data-slot': 'marker',\n 'data-variant': variant,\n className: cn(markerVariants({ variant, className })),\n } as React.ComponentProps<'div'>,\n props,\n ),\n })\n}\n\nfunction MarkerIcon({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"marker-icon\"\n aria-hidden=\"true\"\n className={cn(\n \"size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction MarkerContent({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"marker-content\"\n className={cn(\n 'min-w-0 wrap-break-word group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-gray',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport { Marker, MarkerContent, MarkerIcon, markerVariants }\n"],"mappings":";;;;;;;;;;;AAMA,MAAM,kBAAA,GAAA,yBAAA,KACJ,
|
|
1
|
+
{"version":3,"file":"marker.cjs","names":[],"sources":["../../src/components/marker.tsx"],"sourcesContent":["import { mergeProps } from '@base-ui/react/merge-props'\nimport { useRender } from '@base-ui/react/use-render'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\nconst markerVariants = cva(\n \"group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-gray-light [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-gray [&:is(button,a)]:outline-hidden [&:is(button,a)]:focus-visible:rounded-sm [&:is(button,a)]:focus-visible:ring-2 [&:is(button,a)]:focus-visible:ring-offset-2\",\n {\n variants: {\n variant: {\n // touch-target only where the marker is actually a control (a `render`\n // of <button>/<a>) — and never on `separator`, whose flanking rules\n // already own ::after, the same pseudo-element the utility uses.\n default: '[&:is(button,a)]:touch-target',\n separator:\n 'before:mr-1 before:h-px before:min-w-0 before:flex-1 before:bg-gray-a6 after:ml-1 after:h-px after:min-w-0 after:flex-1 after:bg-gray-a6',\n border: 'border-b border-gray pb-2 [&:is(button,a)]:touch-target',\n },\n },\n },\n)\n\n// `render` replaces radix `asChild` — pass an element (e.g. a <button> for a\n// clickable \"jump to\" marker) and Base UI's useRender merges props onto it.\nfunction Marker({\n className,\n variant = 'default',\n render,\n ...props\n}: useRender.ComponentProps<'div'> & VariantProps<typeof markerVariants>) {\n return useRender({\n defaultTagName: 'div',\n render,\n props: mergeProps<'div'>(\n {\n 'data-slot': 'marker',\n 'data-variant': variant,\n className: cn(markerVariants({ variant, className })),\n } as React.ComponentProps<'div'>,\n props,\n ),\n })\n}\n\nfunction MarkerIcon({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"marker-icon\"\n aria-hidden=\"true\"\n className={cn(\n \"size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction MarkerContent({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"marker-content\"\n className={cn(\n 'min-w-0 wrap-break-word group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-gray',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport { Marker, MarkerContent, MarkerIcon, markerVariants }\n"],"mappings":";;;;;;;;;;;AAMA,MAAM,kBAAA,GAAA,yBAAA,KACJ,6VACA,EACE,UAAU,EACR,SAAS;CAIP,SAAS;CACT,WACE;CACF,QAAQ;AACV,EACF,EACF,CACF;AAIA,SAAS,OAAO,EACd,WACA,UAAU,WACV,QACA,GAAG,SACqE;CACxE,QAAA,GAAA,0BAAA,WAAiB;EACf,gBAAgB;EAChB;EACA,QAAA,GAAA,2BAAA,YACE;GACE,aAAa;GACb,gBAAgB;GAChB,YAAA,GAAA,GAAA,IAAc,eAAe;IAAE;IAAS;GAAU,CAAC,CAAC;EACtD,GACA,KACF;CACF,CAAC;AACH;AAEA,SAAS,WAAW,EAAE,WAAW,GAAG,SAAuC;CACzE,OACE,iBAAA,GAAA,kBAAA,KAAC,QAAD;EACE,aAAU;EACV,eAAY;EACZ,YAAA,GAAA,GAAA,IACE,wDACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,cAAc,EAAE,WAAW,GAAG,SAAuC;CAC5E,OACE,iBAAA,GAAA,kBAAA,KAAC,QAAD;EACE,aAAU;EACV,YAAA,GAAA,GAAA,IACE,4LACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -6,7 +6,7 @@ import { useRender } from "@base-ui/react/use-render";
|
|
|
6
6
|
|
|
7
7
|
//#region src/components/marker.d.ts
|
|
8
8
|
declare const markerVariants: (props?: ({
|
|
9
|
-
variant?: "
|
|
9
|
+
variant?: "separator" | "default" | "border" | null | undefined;
|
|
10
10
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
11
11
|
declare function Marker({
|
|
12
12
|
className,
|
|
@@ -6,7 +6,7 @@ import { useRender } from "@base-ui/react/use-render";
|
|
|
6
6
|
|
|
7
7
|
//#region src/components/marker.d.ts
|
|
8
8
|
declare const markerVariants: (props?: ({
|
|
9
|
-
variant?: "
|
|
9
|
+
variant?: "separator" | "default" | "border" | null | undefined;
|
|
10
10
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
11
11
|
declare function Marker({
|
|
12
12
|
className,
|
|
@@ -6,7 +6,7 @@ import { cva } from "class-variance-authority";
|
|
|
6
6
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
7
7
|
import { useRender } from "@base-ui/react/use-render";
|
|
8
8
|
//#region src/components/marker.tsx
|
|
9
|
-
const markerVariants = cva("group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-gray-light [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-gray", { variants: { variant: {
|
|
9
|
+
const markerVariants = cva("group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-gray-light [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-gray [&:is(button,a)]:outline-hidden [&:is(button,a)]:focus-visible:rounded-sm [&:is(button,a)]:focus-visible:ring-2 [&:is(button,a)]:focus-visible:ring-offset-2", { variants: { variant: {
|
|
10
10
|
default: "[&:is(button,a)]:touch-target",
|
|
11
11
|
separator: "before:mr-1 before:h-px before:min-w-0 before:flex-1 before:bg-gray-a6 after:ml-1 after:h-px after:min-w-0 after:flex-1 after:bg-gray-a6",
|
|
12
12
|
border: "border-b border-gray pb-2 [&:is(button,a)]:touch-target"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"marker.js","names":[],"sources":["../../src/components/marker.tsx"],"sourcesContent":["import { mergeProps } from '@base-ui/react/merge-props'\nimport { useRender } from '@base-ui/react/use-render'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\nconst markerVariants = cva(\n \"group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-gray-light [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-gray\",\n {\n variants: {\n variant: {\n // touch-target only where the marker is actually a control (a `render`\n // of <button>/<a>) — and never on `separator`, whose flanking rules\n // already own ::after, the same pseudo-element the utility uses.\n default: '[&:is(button,a)]:touch-target',\n separator:\n 'before:mr-1 before:h-px before:min-w-0 before:flex-1 before:bg-gray-a6 after:ml-1 after:h-px after:min-w-0 after:flex-1 after:bg-gray-a6',\n border: 'border-b border-gray pb-2 [&:is(button,a)]:touch-target',\n },\n },\n },\n)\n\n// `render` replaces radix `asChild` — pass an element (e.g. a <button> for a\n// clickable \"jump to\" marker) and Base UI's useRender merges props onto it.\nfunction Marker({\n className,\n variant = 'default',\n render,\n ...props\n}: useRender.ComponentProps<'div'> & VariantProps<typeof markerVariants>) {\n return useRender({\n defaultTagName: 'div',\n render,\n props: mergeProps<'div'>(\n {\n 'data-slot': 'marker',\n 'data-variant': variant,\n className: cn(markerVariants({ variant, className })),\n } as React.ComponentProps<'div'>,\n props,\n ),\n })\n}\n\nfunction MarkerIcon({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"marker-icon\"\n aria-hidden=\"true\"\n className={cn(\n \"size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction MarkerContent({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"marker-content\"\n className={cn(\n 'min-w-0 wrap-break-word group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-gray',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport { Marker, MarkerContent, MarkerIcon, markerVariants }\n"],"mappings":";;;;;;;;AAMA,MAAM,iBAAiB,IACrB,
|
|
1
|
+
{"version":3,"file":"marker.js","names":[],"sources":["../../src/components/marker.tsx"],"sourcesContent":["import { mergeProps } from '@base-ui/react/merge-props'\nimport { useRender } from '@base-ui/react/use-render'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\nconst markerVariants = cva(\n \"group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-gray-light [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-gray [&:is(button,a)]:outline-hidden [&:is(button,a)]:focus-visible:rounded-sm [&:is(button,a)]:focus-visible:ring-2 [&:is(button,a)]:focus-visible:ring-offset-2\",\n {\n variants: {\n variant: {\n // touch-target only where the marker is actually a control (a `render`\n // of <button>/<a>) — and never on `separator`, whose flanking rules\n // already own ::after, the same pseudo-element the utility uses.\n default: '[&:is(button,a)]:touch-target',\n separator:\n 'before:mr-1 before:h-px before:min-w-0 before:flex-1 before:bg-gray-a6 after:ml-1 after:h-px after:min-w-0 after:flex-1 after:bg-gray-a6',\n border: 'border-b border-gray pb-2 [&:is(button,a)]:touch-target',\n },\n },\n },\n)\n\n// `render` replaces radix `asChild` — pass an element (e.g. a <button> for a\n// clickable \"jump to\" marker) and Base UI's useRender merges props onto it.\nfunction Marker({\n className,\n variant = 'default',\n render,\n ...props\n}: useRender.ComponentProps<'div'> & VariantProps<typeof markerVariants>) {\n return useRender({\n defaultTagName: 'div',\n render,\n props: mergeProps<'div'>(\n {\n 'data-slot': 'marker',\n 'data-variant': variant,\n className: cn(markerVariants({ variant, className })),\n } as React.ComponentProps<'div'>,\n props,\n ),\n })\n}\n\nfunction MarkerIcon({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"marker-icon\"\n aria-hidden=\"true\"\n className={cn(\n \"size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction MarkerContent({ className, ...props }: React.ComponentProps<'span'>) {\n return (\n <span\n data-slot=\"marker-content\"\n className={cn(\n 'min-w-0 wrap-break-word group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-gray',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport { Marker, MarkerContent, MarkerIcon, markerVariants }\n"],"mappings":";;;;;;;;AAMA,MAAM,iBAAiB,IACrB,6VACA,EACE,UAAU,EACR,SAAS;CAIP,SAAS;CACT,WACE;CACF,QAAQ;AACV,EACF,EACF,CACF;AAIA,SAAS,OAAO,EACd,WACA,UAAU,WACV,QACA,GAAG,SACqE;CACxE,OAAO,UAAU;EACf,gBAAgB;EAChB;EACA,OAAO,WACL;GACE,aAAa;GACb,gBAAgB;GAChB,WAAW,GAAG,eAAe;IAAE;IAAS;GAAU,CAAC,CAAC;EACtD,GACA,KACF;CACF,CAAC;AACH;AAEA,SAAS,WAAW,EAAE,WAAW,GAAG,SAAuC;CACzE,OACE,oBAAC,QAAD;EACE,aAAU;EACV,eAAY;EACZ,WAAW,GACT,wDACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,cAAc,EAAE,WAAW,GAAG,SAAuC;CAC5E,OACE,oBAAC,QAAD;EACE,aAAU;EACV,WAAW,GACT,4LACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const require_chunk = require("../chunk-DY84GLCh.cjs");
|
|
4
4
|
const require_lib_accent_colors = require("../lib/accent-colors.cjs");
|
|
5
|
+
const require_components_field = require("./field.cjs");
|
|
6
|
+
const require_lib_selection_list = require("../lib/selection-list.cjs");
|
|
5
7
|
let react = require("react");
|
|
6
8
|
react = require_chunk.__toESM(react);
|
|
7
9
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -9,12 +11,14 @@ let _base_ui_react_radio = require("@base-ui/react/radio");
|
|
|
9
11
|
let _base_ui_react_radio_group = require("@base-ui/react/radio-group");
|
|
10
12
|
let cn = require("cn");
|
|
11
13
|
//#region src/components/radio.tsx
|
|
12
|
-
function RadioGroup({ className, styleVariant, color, gap = 3, style, ...props }) {
|
|
14
|
+
function RadioGroup({ className, styleVariant, color, gap = 3, listStyle, style, ...props }) {
|
|
13
15
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react_radio_group.RadioGroup, {
|
|
14
16
|
"data-slot": "radio-group",
|
|
15
17
|
"data-style": styleVariant !== void 0 || color !== void 0 ? styleVariant ?? "solid" : void 0,
|
|
16
18
|
"data-accent-color": color ? require_lib_accent_colors.accentByColor[color] : void 0,
|
|
17
|
-
|
|
19
|
+
"data-list-style": listStyle,
|
|
20
|
+
"data-invalid": require_components_field.useFieldSetInvalid(),
|
|
21
|
+
className: (0, cn.cn)("grid", listStyle ? require_lib_selection_list.selectionListClasses[listStyle] : "gap-[calc(var(--spark-layout-gap)*var(--radio-group-gap-steps,3))]", className),
|
|
18
22
|
style: {
|
|
19
23
|
"--radio-group-gap-steps": gap,
|
|
20
24
|
...style
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio.cjs","names":["RadioGroupPrimitive","accentByColor","RadioPrimitive"],"sources":["../../src/components/radio.tsx"],"sourcesContent":["import { Radio as RadioPrimitive } from '@base-ui/react/radio'\nimport { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group'\nimport * as React from 'react'\nimport { accentByColor } from '../lib/accent-colors'\nimport type { SparkStyle } from '../lib/theme-types'\nimport { cn } from '../utils/cn'\n\nexport type SparkRadioColor = 'accent' | 'neutral' | 'danger'\n\ntype RadioGroupProps = Omit<\n React.ComponentProps<typeof RadioGroupPrimitive>,\n 'style'\n> & {\n styleVariant?: SparkStyle\n color?: SparkRadioColor\n /** How many `--spark-layout-gap` units apart each radio sits. */\n gap?: number\n // Base UI's own `style` also accepts a state-callback form; the gap-steps var below\n // needs a plain object to merge into, so narrow it to that here.\n style?: React.CSSProperties\n}\n\nfunction RadioGroup({\n className,\n styleVariant,\n color,\n gap = 3,\n style,\n ...props\n}: RadioGroupProps) {\n const hasLook = styleVariant !== undefined || color !== undefined\n const dataStyle = hasLook ? (styleVariant ?? 'solid') : undefined\n const dataAccentColor = color ? accentByColor[color] : undefined\n\n return (\n <RadioGroupPrimitive\n data-slot=\"radio-group\"\n data-style={dataStyle}\n data-accent-color={dataAccentColor}\n className={cn(\n 'grid gap-[calc(var(--spark-layout-gap)*var(--radio-group-gap-steps,3))]',\n className,\n )}\n style={\n { '--radio-group-gap-steps': gap, ...style } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\ntype RadioProps = React.ComponentProps<typeof RadioPrimitive.Root> & {\n styleVariant?: SparkStyle\n color?: SparkRadioColor\n}\n\nfunction Radio({\n className,\n styleVariant,\n color,\n render,\n nativeButton,\n ...props\n}: RadioProps) {\n const hasLook = styleVariant !== undefined || color !== undefined\n const dataStyle = hasLook ? (styleVariant ?? 'solid') : undefined\n const dataAccentColor = color ? accentByColor[color] : undefined\n\n return (\n <RadioPrimitive.Root\n data-slot=\"radio\"\n data-style={dataStyle}\n data-accent-color={dataAccentColor}\n // a native button owns the caller's `id`; on Base UI's default <span> the\n // `id` lands on the hidden input and the control gets a generated one\n nativeButton={nativeButton ?? render === undefined}\n render={render ?? <button />}\n className={cn(\n // p-0 defends the 24px box against the UA button padding when the\n // consumer's CSS has no Tailwind preflight\n 'peer touch-target relative my-2 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full border-2 p-0 transition-colors any-pointer-coarse:my-[calc((2.75rem-var(--spacing)*6)/2)]',\n // both overlays are reserved as layout, so neither can bleed into a scroller;\n // the coarse margin tracks --spacing so the 44px reserve holds at any [data-scaling]\n \"before:absolute before:top-1/2 before:left-1/2 before:size-10 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\",\n 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n 'data-disabled:cursor-not-allowed data-disabled:opacity-50',\n 'border-(--spark-style-idle-border,var(--color-accent-ui)) bg-base-accent-soft data-unchecked:border-(length:--spark-style-border-width,2px)',\n 'data-checked:border-(--spark-radio-ring,var(--spark-9))',\n className,\n )}\n {...props}\n >\n <RadioPrimitive.Indicator\n data-slot=\"radio-indicator\"\n className=\"flex items-center justify-center\"\n >\n <span className=\"size-4 rounded-full bg-(--spark-radio-dot,var(--spark-9)) motion-safe:animate-dot-pop\" />\n </RadioPrimitive.Indicator>\n </RadioPrimitive.Root>\n )\n}\n\nexport { Radio, RadioGroup }\nexport type { RadioGroupProps, RadioProps }\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"radio.cjs","names":["RadioGroupPrimitive","accentByColor","useFieldSetInvalid","selectionListClasses","RadioPrimitive"],"sources":["../../src/components/radio.tsx"],"sourcesContent":["import { Radio as RadioPrimitive } from '@base-ui/react/radio'\nimport { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group'\nimport * as React from 'react'\nimport { accentByColor } from '../lib/accent-colors'\nimport { selectionListClasses } from '../lib/selection-list'\nimport type { SparkInputStyle, SparkStyle } from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { useFieldSetInvalid } from './field'\n\nexport type SparkRadioColor = 'accent' | 'neutral' | 'danger'\n\ntype RadioGroupProps = Omit<\n React.ComponentProps<typeof RadioGroupPrimitive>,\n 'style'\n> & {\n styleVariant?: SparkStyle\n color?: SparkRadioColor\n /** How many `--spark-layout-gap` units apart each radio sits. */\n gap?: number\n /** Shows the options as a styled list of `SelectionItem` rows; ignores `gap`. */\n listStyle?: SparkInputStyle\n // Base UI's own `style` also accepts a state-callback form; the gap-steps var below\n // needs a plain object to merge into, so narrow it to that here.\n style?: React.CSSProperties\n}\n\nfunction RadioGroup({\n className,\n styleVariant,\n color,\n gap = 3,\n listStyle,\n style,\n ...props\n}: RadioGroupProps) {\n const hasLook = styleVariant !== undefined || color !== undefined\n const dataStyle = hasLook ? (styleVariant ?? 'solid') : undefined\n const dataAccentColor = color ? accentByColor[color] : undefined\n const invalid = useFieldSetInvalid()\n\n return (\n <RadioGroupPrimitive\n data-slot=\"radio-group\"\n data-style={dataStyle}\n data-accent-color={dataAccentColor}\n data-list-style={listStyle}\n data-invalid={invalid}\n className={cn(\n 'grid',\n listStyle\n ? selectionListClasses[listStyle]\n : 'gap-[calc(var(--spark-layout-gap)*var(--radio-group-gap-steps,3))]',\n className,\n )}\n style={\n { '--radio-group-gap-steps': gap, ...style } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\ntype RadioProps = React.ComponentProps<typeof RadioPrimitive.Root> & {\n styleVariant?: SparkStyle\n color?: SparkRadioColor\n}\n\nfunction Radio({\n className,\n styleVariant,\n color,\n render,\n nativeButton,\n ...props\n}: RadioProps) {\n const hasLook = styleVariant !== undefined || color !== undefined\n const dataStyle = hasLook ? (styleVariant ?? 'solid') : undefined\n const dataAccentColor = color ? accentByColor[color] : undefined\n\n return (\n <RadioPrimitive.Root\n data-slot=\"radio\"\n data-style={dataStyle}\n data-accent-color={dataAccentColor}\n // a native button owns the caller's `id`; on Base UI's default <span> the\n // `id` lands on the hidden input and the control gets a generated one\n nativeButton={nativeButton ?? render === undefined}\n render={render ?? <button />}\n className={cn(\n // p-0 defends the 24px box against the UA button padding when the\n // consumer's CSS has no Tailwind preflight\n 'peer touch-target relative my-2 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full border-2 p-0 transition-colors any-pointer-coarse:my-[calc((2.75rem-var(--spacing)*6)/2)]',\n // both overlays are reserved as layout, so neither can bleed into a scroller;\n // the coarse margin tracks --spacing so the 44px reserve holds at any [data-scaling]\n \"before:absolute before:top-1/2 before:left-1/2 before:size-10 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\",\n 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n 'data-disabled:cursor-not-allowed data-disabled:opacity-50',\n 'border-(--spark-style-idle-border,var(--color-accent-ui)) bg-base-accent-soft data-unchecked:border-(length:--spark-style-border-width,2px)',\n 'data-checked:border-(--spark-radio-ring,var(--spark-9))',\n className,\n )}\n {...props}\n >\n <RadioPrimitive.Indicator\n data-slot=\"radio-indicator\"\n className=\"flex items-center justify-center\"\n >\n <span className=\"size-4 rounded-full bg-(--spark-radio-dot,var(--spark-9)) motion-safe:animate-dot-pop\" />\n </RadioPrimitive.Indicator>\n </RadioPrimitive.Root>\n )\n}\n\nexport { Radio, RadioGroup }\nexport type { RadioGroupProps, RadioProps }\n"],"mappings":";;;;;;;;;;;;;AA0BA,SAAS,WAAW,EAClB,WACA,cACA,OACA,MAAM,GACN,WACA,OACA,GAAG,SACe;CAMlB,OACE,iBAAA,GAAA,kBAAA,KAACA,2BAAAA,YAAD;EACE,aAAU;EACV,cARY,iBAAiB,KAAA,KAAa,UAAU,KAAA,IAC3B,gBAAgB,UAAW,KAAA;EAQpD,qBAPoB,QAAQC,0BAAAA,cAAc,SAAS,KAAA;EAQnD,mBAAiB;EACjB,gBARYC,yBAAAA,mBAQQ;EACpB,YAAA,GAAA,GAAA,IACE,QACA,YACIC,2BAAAA,qBAAqB,aACrB,sEACJ,SACF;EACA,OACE;GAAE,2BAA2B;GAAK,GAAG;EAAM;EAE7C,GAAI;CACL,CAAA;AAEL;AAOA,SAAS,MAAM,EACb,WACA,cACA,OACA,QACA,cACA,GAAG,SACU;CAEb,MAAM,YADU,iBAAiB,KAAA,KAAa,UAAU,KAAA,IAC3B,gBAAgB,UAAW,KAAA;CACxD,MAAM,kBAAkB,QAAQF,0BAAAA,cAAc,SAAS,KAAA;CAEvD,OACE,iBAAA,GAAA,kBAAA,KAACG,qBAAAA,MAAe,MAAhB;EACE,aAAU;EACV,cAAY;EACZ,qBAAmB;EAGnB,cAAc,gBAAgB,WAAW,KAAA;EACzC,QAAQ,UAAU,iBAAA,GAAA,kBAAA,KAAC,UAAD,CAAS,CAAA;EAC3B,YAAA,GAAA,GAAA,IAGE,iNAGA,qIACA,iFACA,6DACA,+IACA,2DACA,SACF;EACA,GAAI;YAEJ,iBAAA,GAAA,kBAAA,KAACA,qBAAAA,MAAe,WAAhB;GACE,aAAU;GACV,WAAU;aAEV,iBAAA,GAAA,kBAAA,KAAC,QAAD,EAAM,WAAU,wFAAyF,CAAA;EACjF,CAAA;CACP,CAAA;AAEzB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { g as SparkStyle, l as SparkInputStyle } from "../theme-types-CKZzOmSa.cjs";
|
|
4
4
|
import * as React$1 from "react";
|
|
5
5
|
import { Radio as Radio$1 } from "@base-ui/react/radio";
|
|
6
6
|
import { RadioGroup as RadioGroup$1 } from "@base-ui/react/radio-group";
|
|
@@ -10,7 +10,8 @@ type SparkRadioColor = 'accent' | 'neutral' | 'danger';
|
|
|
10
10
|
type RadioGroupProps = Omit<React$1.ComponentProps<typeof RadioGroup$1>, 'style'> & {
|
|
11
11
|
styleVariant?: SparkStyle;
|
|
12
12
|
color?: SparkRadioColor; /** How many `--spark-layout-gap` units apart each radio sits. */
|
|
13
|
-
gap?: number;
|
|
13
|
+
gap?: number; /** Shows the options as a styled list of `SelectionItem` rows; ignores `gap`. */
|
|
14
|
+
listStyle?: SparkInputStyle;
|
|
14
15
|
style?: React$1.CSSProperties;
|
|
15
16
|
};
|
|
16
17
|
declare function RadioGroup({
|
|
@@ -18,6 +19,7 @@ declare function RadioGroup({
|
|
|
18
19
|
styleVariant,
|
|
19
20
|
color,
|
|
20
21
|
gap,
|
|
22
|
+
listStyle,
|
|
21
23
|
style,
|
|
22
24
|
...props
|
|
23
25
|
}: RadioGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { g as SparkStyle, l as SparkInputStyle } from "../theme-types-CKZzOmSa.js";
|
|
4
4
|
import * as React$1 from "react";
|
|
5
5
|
import { Radio as Radio$1 } from "@base-ui/react/radio";
|
|
6
6
|
import { RadioGroup as RadioGroup$1 } from "@base-ui/react/radio-group";
|
|
@@ -10,7 +10,8 @@ type SparkRadioColor = 'accent' | 'neutral' | 'danger';
|
|
|
10
10
|
type RadioGroupProps = Omit<React$1.ComponentProps<typeof RadioGroup$1>, 'style'> & {
|
|
11
11
|
styleVariant?: SparkStyle;
|
|
12
12
|
color?: SparkRadioColor; /** How many `--spark-layout-gap` units apart each radio sits. */
|
|
13
|
-
gap?: number;
|
|
13
|
+
gap?: number; /** Shows the options as a styled list of `SelectionItem` rows; ignores `gap`. */
|
|
14
|
+
listStyle?: SparkInputStyle;
|
|
14
15
|
style?: React$1.CSSProperties;
|
|
15
16
|
};
|
|
16
17
|
declare function RadioGroup({
|
|
@@ -18,6 +19,7 @@ declare function RadioGroup({
|
|
|
18
19
|
styleVariant,
|
|
19
20
|
color,
|
|
20
21
|
gap,
|
|
22
|
+
listStyle,
|
|
21
23
|
style,
|
|
22
24
|
...props
|
|
23
25
|
}: RadioGroupProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/components/radio.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../utils/cn.js";
|
|
3
3
|
import { accentByColor } from "../lib/accent-colors.js";
|
|
4
|
+
import { useFieldSetInvalid } from "./field.js";
|
|
5
|
+
import { selectionListClasses } from "../lib/selection-list.js";
|
|
4
6
|
import "react";
|
|
5
7
|
import { jsx } from "react/jsx-runtime";
|
|
6
8
|
import { Radio as Radio$1 } from "@base-ui/react/radio";
|
|
7
9
|
import { RadioGroup as RadioGroup$1 } from "@base-ui/react/radio-group";
|
|
8
10
|
//#region src/components/radio.tsx
|
|
9
|
-
function RadioGroup({ className, styleVariant, color, gap = 3, style, ...props }) {
|
|
11
|
+
function RadioGroup({ className, styleVariant, color, gap = 3, listStyle, style, ...props }) {
|
|
10
12
|
return /* @__PURE__ */ jsx(RadioGroup$1, {
|
|
11
13
|
"data-slot": "radio-group",
|
|
12
14
|
"data-style": styleVariant !== void 0 || color !== void 0 ? styleVariant ?? "solid" : void 0,
|
|
13
15
|
"data-accent-color": color ? accentByColor[color] : void 0,
|
|
14
|
-
|
|
16
|
+
"data-list-style": listStyle,
|
|
17
|
+
"data-invalid": useFieldSetInvalid(),
|
|
18
|
+
className: cn("grid", listStyle ? selectionListClasses[listStyle] : "gap-[calc(var(--spark-layout-gap)*var(--radio-group-gap-steps,3))]", className),
|
|
15
19
|
style: {
|
|
16
20
|
"--radio-group-gap-steps": gap,
|
|
17
21
|
...style
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio.js","names":["RadioGroupPrimitive","RadioPrimitive"],"sources":["../../src/components/radio.tsx"],"sourcesContent":["import { Radio as RadioPrimitive } from '@base-ui/react/radio'\nimport { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group'\nimport * as React from 'react'\nimport { accentByColor } from '../lib/accent-colors'\nimport type { SparkStyle } from '../lib/theme-types'\nimport { cn } from '../utils/cn'\n\nexport type SparkRadioColor = 'accent' | 'neutral' | 'danger'\n\ntype RadioGroupProps = Omit<\n React.ComponentProps<typeof RadioGroupPrimitive>,\n 'style'\n> & {\n styleVariant?: SparkStyle\n color?: SparkRadioColor\n /** How many `--spark-layout-gap` units apart each radio sits. */\n gap?: number\n // Base UI's own `style` also accepts a state-callback form; the gap-steps var below\n // needs a plain object to merge into, so narrow it to that here.\n style?: React.CSSProperties\n}\n\nfunction RadioGroup({\n className,\n styleVariant,\n color,\n gap = 3,\n style,\n ...props\n}: RadioGroupProps) {\n const hasLook = styleVariant !== undefined || color !== undefined\n const dataStyle = hasLook ? (styleVariant ?? 'solid') : undefined\n const dataAccentColor = color ? accentByColor[color] : undefined\n\n return (\n <RadioGroupPrimitive\n data-slot=\"radio-group\"\n data-style={dataStyle}\n data-accent-color={dataAccentColor}\n className={cn(\n 'grid gap-[calc(var(--spark-layout-gap)*var(--radio-group-gap-steps,3))]',\n className,\n )}\n style={\n { '--radio-group-gap-steps': gap, ...style } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\ntype RadioProps = React.ComponentProps<typeof RadioPrimitive.Root> & {\n styleVariant?: SparkStyle\n color?: SparkRadioColor\n}\n\nfunction Radio({\n className,\n styleVariant,\n color,\n render,\n nativeButton,\n ...props\n}: RadioProps) {\n const hasLook = styleVariant !== undefined || color !== undefined\n const dataStyle = hasLook ? (styleVariant ?? 'solid') : undefined\n const dataAccentColor = color ? accentByColor[color] : undefined\n\n return (\n <RadioPrimitive.Root\n data-slot=\"radio\"\n data-style={dataStyle}\n data-accent-color={dataAccentColor}\n // a native button owns the caller's `id`; on Base UI's default <span> the\n // `id` lands on the hidden input and the control gets a generated one\n nativeButton={nativeButton ?? render === undefined}\n render={render ?? <button />}\n className={cn(\n // p-0 defends the 24px box against the UA button padding when the\n // consumer's CSS has no Tailwind preflight\n 'peer touch-target relative my-2 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full border-2 p-0 transition-colors any-pointer-coarse:my-[calc((2.75rem-var(--spacing)*6)/2)]',\n // both overlays are reserved as layout, so neither can bleed into a scroller;\n // the coarse margin tracks --spacing so the 44px reserve holds at any [data-scaling]\n \"before:absolute before:top-1/2 before:left-1/2 before:size-10 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\",\n 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n 'data-disabled:cursor-not-allowed data-disabled:opacity-50',\n 'border-(--spark-style-idle-border,var(--color-accent-ui)) bg-base-accent-soft data-unchecked:border-(length:--spark-style-border-width,2px)',\n 'data-checked:border-(--spark-radio-ring,var(--spark-9))',\n className,\n )}\n {...props}\n >\n <RadioPrimitive.Indicator\n data-slot=\"radio-indicator\"\n className=\"flex items-center justify-center\"\n >\n <span className=\"size-4 rounded-full bg-(--spark-radio-dot,var(--spark-9)) motion-safe:animate-dot-pop\" />\n </RadioPrimitive.Indicator>\n </RadioPrimitive.Root>\n )\n}\n\nexport { Radio, RadioGroup }\nexport type { RadioGroupProps, RadioProps }\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"radio.js","names":["RadioGroupPrimitive","RadioPrimitive"],"sources":["../../src/components/radio.tsx"],"sourcesContent":["import { Radio as RadioPrimitive } from '@base-ui/react/radio'\nimport { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group'\nimport * as React from 'react'\nimport { accentByColor } from '../lib/accent-colors'\nimport { selectionListClasses } from '../lib/selection-list'\nimport type { SparkInputStyle, SparkStyle } from '../lib/theme-types'\nimport { cn } from '../utils/cn'\nimport { useFieldSetInvalid } from './field'\n\nexport type SparkRadioColor = 'accent' | 'neutral' | 'danger'\n\ntype RadioGroupProps = Omit<\n React.ComponentProps<typeof RadioGroupPrimitive>,\n 'style'\n> & {\n styleVariant?: SparkStyle\n color?: SparkRadioColor\n /** How many `--spark-layout-gap` units apart each radio sits. */\n gap?: number\n /** Shows the options as a styled list of `SelectionItem` rows; ignores `gap`. */\n listStyle?: SparkInputStyle\n // Base UI's own `style` also accepts a state-callback form; the gap-steps var below\n // needs a plain object to merge into, so narrow it to that here.\n style?: React.CSSProperties\n}\n\nfunction RadioGroup({\n className,\n styleVariant,\n color,\n gap = 3,\n listStyle,\n style,\n ...props\n}: RadioGroupProps) {\n const hasLook = styleVariant !== undefined || color !== undefined\n const dataStyle = hasLook ? (styleVariant ?? 'solid') : undefined\n const dataAccentColor = color ? accentByColor[color] : undefined\n const invalid = useFieldSetInvalid()\n\n return (\n <RadioGroupPrimitive\n data-slot=\"radio-group\"\n data-style={dataStyle}\n data-accent-color={dataAccentColor}\n data-list-style={listStyle}\n data-invalid={invalid}\n className={cn(\n 'grid',\n listStyle\n ? selectionListClasses[listStyle]\n : 'gap-[calc(var(--spark-layout-gap)*var(--radio-group-gap-steps,3))]',\n className,\n )}\n style={\n { '--radio-group-gap-steps': gap, ...style } as React.CSSProperties\n }\n {...props}\n />\n )\n}\n\ntype RadioProps = React.ComponentProps<typeof RadioPrimitive.Root> & {\n styleVariant?: SparkStyle\n color?: SparkRadioColor\n}\n\nfunction Radio({\n className,\n styleVariant,\n color,\n render,\n nativeButton,\n ...props\n}: RadioProps) {\n const hasLook = styleVariant !== undefined || color !== undefined\n const dataStyle = hasLook ? (styleVariant ?? 'solid') : undefined\n const dataAccentColor = color ? accentByColor[color] : undefined\n\n return (\n <RadioPrimitive.Root\n data-slot=\"radio\"\n data-style={dataStyle}\n data-accent-color={dataAccentColor}\n // a native button owns the caller's `id`; on Base UI's default <span> the\n // `id` lands on the hidden input and the control gets a generated one\n nativeButton={nativeButton ?? render === undefined}\n render={render ?? <button />}\n className={cn(\n // p-0 defends the 24px box against the UA button padding when the\n // consumer's CSS has no Tailwind preflight\n 'peer touch-target relative my-2 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full border-2 p-0 transition-colors any-pointer-coarse:my-[calc((2.75rem-var(--spacing)*6)/2)]',\n // both overlays are reserved as layout, so neither can bleed into a scroller;\n // the coarse margin tracks --spacing so the 44px reserve holds at any [data-scaling]\n \"before:absolute before:top-1/2 before:left-1/2 before:size-10 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\",\n 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n 'data-disabled:cursor-not-allowed data-disabled:opacity-50',\n 'border-(--spark-style-idle-border,var(--color-accent-ui)) bg-base-accent-soft data-unchecked:border-(length:--spark-style-border-width,2px)',\n 'data-checked:border-(--spark-radio-ring,var(--spark-9))',\n className,\n )}\n {...props}\n >\n <RadioPrimitive.Indicator\n data-slot=\"radio-indicator\"\n className=\"flex items-center justify-center\"\n >\n <span className=\"size-4 rounded-full bg-(--spark-radio-dot,var(--spark-9)) motion-safe:animate-dot-pop\" />\n </RadioPrimitive.Indicator>\n </RadioPrimitive.Root>\n )\n}\n\nexport { Radio, RadioGroup }\nexport type { RadioGroupProps, RadioProps }\n"],"mappings":";;;;;;;;;;AA0BA,SAAS,WAAW,EAClB,WACA,cACA,OACA,MAAM,GACN,WACA,OACA,GAAG,SACe;CAMlB,OACE,oBAACA,cAAD;EACE,aAAU;EACV,cARY,iBAAiB,KAAA,KAAa,UAAU,KAAA,IAC3B,gBAAgB,UAAW,KAAA;EAQpD,qBAPoB,QAAQ,cAAc,SAAS,KAAA;EAQnD,mBAAiB;EACjB,gBARY,mBAQQ;EACpB,WAAW,GACT,QACA,YACI,qBAAqB,aACrB,sEACJ,SACF;EACA,OACE;GAAE,2BAA2B;GAAK,GAAG;EAAM;EAE7C,GAAI;CACL,CAAA;AAEL;AAOA,SAAS,MAAM,EACb,WACA,cACA,OACA,QACA,cACA,GAAG,SACU;CAEb,MAAM,YADU,iBAAiB,KAAA,KAAa,UAAU,KAAA,IAC3B,gBAAgB,UAAW,KAAA;CACxD,MAAM,kBAAkB,QAAQ,cAAc,SAAS,KAAA;CAEvD,OACE,oBAACC,QAAe,MAAhB;EACE,aAAU;EACV,cAAY;EACZ,qBAAmB;EAGnB,cAAc,gBAAgB,WAAW,KAAA;EACzC,QAAQ,UAAU,oBAAC,UAAD,CAAS,CAAA;EAC3B,WAAW,GAGT,iNAGA,qIACA,iFACA,6DACA,+IACA,2DACA,SACF;EACA,GAAI;YAEJ,oBAACA,QAAe,WAAhB;GACE,aAAU;GACV,WAAU;aAEV,oBAAC,QAAD,EAAM,WAAU,wFAAyF,CAAA;EACjF,CAAA;CACP,CAAA;AAEzB"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const require_chunk = require("../chunk-DY84GLCh.cjs");
|
|
4
|
+
const require_components_field = require("./field.cjs");
|
|
5
|
+
let react = require("react");
|
|
6
|
+
react = require_chunk.__toESM(react);
|
|
7
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let _base_ui_react_radio = require("@base-ui/react/radio");
|
|
9
|
+
let _base_ui_react_radio_group = require("@base-ui/react/radio-group");
|
|
10
|
+
let cn = require("cn");
|
|
11
|
+
//#region src/components/rating.tsx
|
|
12
|
+
const RatingContext = react.createContext(null);
|
|
13
|
+
function useRating() {
|
|
14
|
+
const context = react.useContext(RatingContext);
|
|
15
|
+
if (!context) throw new Error("RatingStars and RatingCaption need a Rating");
|
|
16
|
+
return context;
|
|
17
|
+
}
|
|
18
|
+
function Rating({ value: valueProp, defaultValue = null, onValueChange, max = 5, labels, size = "medium", readOnly = false, disabled = false, name, className, children, ...props }) {
|
|
19
|
+
const [uncontrolled, setUncontrolled] = react.useState(defaultValue);
|
|
20
|
+
const [hovered, setHovered] = react.useState(null);
|
|
21
|
+
const value = valueProp === void 0 ? uncontrolled : valueProp;
|
|
22
|
+
const setValue = react.useCallback((next) => {
|
|
23
|
+
setUncontrolled(next);
|
|
24
|
+
onValueChange?.(next);
|
|
25
|
+
}, [onValueChange]);
|
|
26
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RatingContext.Provider, {
|
|
27
|
+
value: {
|
|
28
|
+
value,
|
|
29
|
+
setValue,
|
|
30
|
+
hovered: readOnly || disabled ? null : hovered,
|
|
31
|
+
setHovered,
|
|
32
|
+
max,
|
|
33
|
+
labels,
|
|
34
|
+
size,
|
|
35
|
+
readOnly,
|
|
36
|
+
disabled,
|
|
37
|
+
name
|
|
38
|
+
},
|
|
39
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
40
|
+
"data-slot": "rating",
|
|
41
|
+
className: (0, cn.cn)("flex flex-col items-start gap-[calc(var(--spark-layout-gap)*1)]", className),
|
|
42
|
+
...props,
|
|
43
|
+
children
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const sizeClasses = {
|
|
48
|
+
small: {
|
|
49
|
+
star: "size-4",
|
|
50
|
+
button: "size-6"
|
|
51
|
+
},
|
|
52
|
+
medium: {
|
|
53
|
+
star: "size-6",
|
|
54
|
+
button: "size-8"
|
|
55
|
+
},
|
|
56
|
+
large: {
|
|
57
|
+
star: "size-8",
|
|
58
|
+
button: "size-11"
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const compactSizeClasses = {
|
|
62
|
+
small: {
|
|
63
|
+
star: "size-3.5",
|
|
64
|
+
button: "size-6"
|
|
65
|
+
},
|
|
66
|
+
medium: {
|
|
67
|
+
star: "size-4.5",
|
|
68
|
+
button: "size-6"
|
|
69
|
+
},
|
|
70
|
+
large: {
|
|
71
|
+
star: "size-5",
|
|
72
|
+
button: "size-6"
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
function starTone(fill, disabled) {
|
|
76
|
+
if (disabled) return fill === "off" ? "fill-none stroke-gray-a6" : "fill-gray-a7 stroke-gray-a7";
|
|
77
|
+
if (fill === "on") return "fill-spark-9 stroke-spark-9";
|
|
78
|
+
if (fill === "preview") return "fill-spark-a4 stroke-spark-9";
|
|
79
|
+
return "fill-none stroke-gray-a9";
|
|
80
|
+
}
|
|
81
|
+
function captionTone(hasLabel, hovered, disabled) {
|
|
82
|
+
if (disabled) return "text-gray-a9";
|
|
83
|
+
if (!hasLabel) return "text-gray-11";
|
|
84
|
+
return hovered ? "text-spark-11" : "text-gray-12";
|
|
85
|
+
}
|
|
86
|
+
function Star({ className }) {
|
|
87
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
88
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
89
|
+
viewBox: "0 0 24 24",
|
|
90
|
+
strokeWidth: 1.5,
|
|
91
|
+
strokeLinejoin: "round",
|
|
92
|
+
"aria-hidden": "true",
|
|
93
|
+
className: (0, cn.cn)("transition-colors motion-reduce:transition-none", className),
|
|
94
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12.60 3.17 L14.72 8.03 Q15.00 8.67 15.69 8.74 L20.97 9.26 Q22.46 9.40 21.34 10.40 L17.37 13.91 Q16.85 14.38 17.00 15.06 L18.14 20.23 Q18.47 21.70 17.17 20.94 L12.60 18.25 Q12.00 17.90 11.40 18.25 L6.83 20.94 Q5.53 21.70 5.86 20.23 L7.00 15.06 Q7.15 14.38 6.63 13.91 L2.66 10.40 Q1.54 9.40 3.03 9.26 L8.31 8.74 Q9.00 8.67 9.28 8.03 L11.40 3.17 Q12.00 1.80 12.60 3.17Z" })
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function RatingStars({ className, onPointerLeave, onKeyDown, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedByProp, ...props }) {
|
|
98
|
+
const rating = useRating();
|
|
99
|
+
const { field, ariaDescribedBy, ariaInvalid } = require_components_field.useFieldControl({ ariaDescribedBy: ariaDescribedByProp });
|
|
100
|
+
const invalid = require_components_field.useFieldSetInvalid();
|
|
101
|
+
const { star: starSize, button: buttonSize } = (rating.max > 5 ? compactSizeClasses : sizeClasses)[rating.size];
|
|
102
|
+
const interactive = !rating.readOnly && !rating.disabled;
|
|
103
|
+
const low = Math.min(rating.value ?? 0, rating.hovered ?? rating.value ?? 0);
|
|
104
|
+
const high = Math.max(rating.value ?? 0, rating.hovered ?? 0);
|
|
105
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react_radio_group.RadioGroup, {
|
|
106
|
+
"data-slot": "rating-stars",
|
|
107
|
+
"data-invalid": invalid,
|
|
108
|
+
"aria-labelledby": ariaLabelledBy ?? (field?.readOnly ? field.labelId : void 0),
|
|
109
|
+
"aria-describedby": ariaDescribedBy,
|
|
110
|
+
"aria-invalid": ariaInvalid,
|
|
111
|
+
value: rating.value,
|
|
112
|
+
onValueChange: (next) => next !== null && rating.setValue(next),
|
|
113
|
+
readOnly: rating.readOnly,
|
|
114
|
+
disabled: rating.disabled,
|
|
115
|
+
name: rating.name,
|
|
116
|
+
className: (0, cn.cn)("flex", className),
|
|
117
|
+
onPointerLeave: (event) => {
|
|
118
|
+
rating.setHovered(null);
|
|
119
|
+
onPointerLeave?.(event);
|
|
120
|
+
},
|
|
121
|
+
onKeyDown: (event) => {
|
|
122
|
+
rating.setHovered(null);
|
|
123
|
+
onKeyDown?.(event);
|
|
124
|
+
},
|
|
125
|
+
...props,
|
|
126
|
+
children: Array.from({ length: rating.max }, (_, index) => {
|
|
127
|
+
const star = index + 1;
|
|
128
|
+
const fill = star <= low ? "on" : star <= high ? "preview" : "off";
|
|
129
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_base_ui_react_radio.Radio.Root, {
|
|
130
|
+
value: star,
|
|
131
|
+
"aria-label": rating.labels?.[index] ?? String(star),
|
|
132
|
+
"data-slot": "rating-star",
|
|
133
|
+
"data-fill": fill,
|
|
134
|
+
"aria-readonly": void 0,
|
|
135
|
+
nativeButton: true,
|
|
136
|
+
render: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {}),
|
|
137
|
+
onPointerEnter: interactive ? () => rating.setHovered(star) : void 0,
|
|
138
|
+
className: (0, cn.cn)("inline-flex min-h-[24px] min-w-[24px] shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 touch-target-row any-pointer-coarse:min-w-11", "focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden", "data-disabled:cursor-not-allowed data-readonly:cursor-default", buttonSize),
|
|
139
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Star, { className: (0, cn.cn)(starSize, starTone(fill, rating.disabled)) })
|
|
140
|
+
}, star);
|
|
141
|
+
})
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
const captionTextSize = {
|
|
145
|
+
small: "text-[0.8125rem]",
|
|
146
|
+
medium: "text-sm",
|
|
147
|
+
large: "text-base"
|
|
148
|
+
};
|
|
149
|
+
function RatingCaption({ className, children, ...props }) {
|
|
150
|
+
const rating = useRating();
|
|
151
|
+
const shown = rating.hovered ?? rating.value;
|
|
152
|
+
const label = shown ? rating.labels?.[shown - 1] : void 0;
|
|
153
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
154
|
+
"data-slot": "rating-caption",
|
|
155
|
+
"aria-hidden": "true",
|
|
156
|
+
"aria-disabled": rating.disabled || void 0,
|
|
157
|
+
className: (0, cn.cn)("leading-5", captionTextSize[rating.size], label !== void 0 && "font-medium", captionTone(label !== void 0, !!rating.hovered, rating.disabled), className),
|
|
158
|
+
...props,
|
|
159
|
+
children: label ?? children
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
exports.Rating = Rating;
|
|
164
|
+
exports.RatingCaption = RatingCaption;
|
|
165
|
+
exports.RatingStars = RatingStars;
|
|
166
|
+
|
|
167
|
+
//# sourceMappingURL=rating.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rating.cjs","names":["React","useFieldControl","useFieldSetInvalid","RadioGroupPrimitive","RadioPrimitive"],"sources":["../../src/components/rating.tsx"],"sourcesContent":["import { Radio as RadioPrimitive } from '@base-ui/react/radio'\nimport { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\nimport { useFieldControl, useFieldSetInvalid } from './field'\n\ntype RatingSize = 'small' | 'medium' | 'large'\n\ntype RatingContextValue = {\n value: number | null\n setValue: (value: number) => void\n hovered: number | null\n setHovered: (value: number | null) => void\n max: number\n labels?: string[]\n size: RatingSize\n readOnly: boolean\n disabled: boolean\n name?: string\n}\n\nconst RatingContext = React.createContext<RatingContextValue | null>(null)\n\nfunction useRating() {\n const context = React.useContext(RatingContext)\n if (!context) throw new Error('RatingStars and RatingCaption need a Rating')\n return context\n}\n\ninterface RatingProps extends Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'defaultValue' | 'onChange'\n> {\n /** The chosen star, 1-based. `null` means nothing is chosen yet. */\n value?: number | null\n defaultValue?: number | null\n onValueChange?: (value: number) => void\n max?: number\n /** One label per star: the star's accessible name and the caption text. */\n labels?: string[]\n size?: RatingSize\n readOnly?: boolean\n disabled?: boolean\n name?: string\n}\n\nfunction Rating({\n value: valueProp,\n defaultValue = null,\n onValueChange,\n max = 5,\n labels,\n size = 'medium',\n readOnly = false,\n disabled = false,\n name,\n className,\n children,\n ...props\n}: RatingProps) {\n const [uncontrolled, setUncontrolled] = React.useState(defaultValue)\n const [hovered, setHovered] = React.useState<number | null>(null)\n const value = valueProp === undefined ? uncontrolled : valueProp\n\n const setValue = React.useCallback(\n (next: number) => {\n setUncontrolled(next)\n onValueChange?.(next)\n },\n [onValueChange],\n )\n\n return (\n <RatingContext.Provider\n value={{\n value,\n setValue,\n hovered: readOnly || disabled ? null : hovered,\n setHovered,\n max,\n labels,\n size,\n readOnly,\n disabled,\n name,\n }}\n >\n <div\n data-slot=\"rating\"\n className={cn(\n 'flex flex-col items-start gap-[calc(var(--spark-layout-gap)*1)]',\n className,\n )}\n {...props}\n >\n {children}\n </div>\n </RatingContext.Provider>\n )\n}\n\ntype SizeClasses = { star: string; button: string }\n\nconst sizeClasses: Record<RatingSize, SizeClasses> = {\n small: { star: 'size-4', button: 'size-6' },\n medium: { star: 'size-6', button: 'size-8' },\n large: { star: 'size-8', button: 'size-11' },\n}\n\n// above five stars the row would outgrow narrow panels\nconst compactSizeClasses: Record<RatingSize, SizeClasses> = {\n small: { star: 'size-3.5', button: 'size-6' },\n medium: { star: 'size-4.5', button: 'size-6' },\n large: { star: 'size-5', button: 'size-6' },\n}\n\ntype StarFill = 'on' | 'preview' | 'off'\n\nfunction starTone(fill: StarFill, disabled: boolean) {\n if (disabled) {\n return fill === 'off'\n ? 'fill-none stroke-gray-a6'\n : 'fill-gray-a7 stroke-gray-a7'\n }\n if (fill === 'on') return 'fill-spark-9 stroke-spark-9'\n if (fill === 'preview') return 'fill-spark-a4 stroke-spark-9'\n return 'fill-none stroke-gray-a9'\n}\n\nfunction captionTone(hasLabel: boolean, hovered: boolean, disabled: boolean) {\n if (disabled) return 'text-gray-a9'\n if (!hasLabel) return 'text-gray-11'\n return hovered ? 'text-spark-11' : 'text-gray-12'\n}\n\nfunction Star({ className }: { className: string }) {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n strokeWidth={1.5}\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className={cn(\n 'transition-colors motion-reduce:transition-none',\n className,\n )}\n >\n <path d=\"M12.60 3.17 L14.72 8.03 Q15.00 8.67 15.69 8.74 L20.97 9.26 Q22.46 9.40 21.34 10.40 L17.37 13.91 Q16.85 14.38 17.00 15.06 L18.14 20.23 Q18.47 21.70 17.17 20.94 L12.60 18.25 Q12.00 17.90 11.40 18.25 L6.83 20.94 Q5.53 21.70 5.86 20.23 L7.00 15.06 Q7.15 14.38 6.63 13.91 L2.66 10.40 Q1.54 9.40 3.03 9.26 L8.31 8.74 Q9.00 8.67 9.28 8.03 L11.40 3.17 Q12.00 1.80 12.60 3.17Z\" />\n </svg>\n )\n}\n\ntype RatingStarsProps = Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'defaultValue' | 'onChange'\n>\n\nfunction RatingStars({\n className,\n onPointerLeave,\n onKeyDown,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedByProp,\n ...props\n}: RatingStarsProps) {\n const rating = useRating()\n const { field, ariaDescribedBy, ariaInvalid } = useFieldControl({\n ariaDescribedBy: ariaDescribedByProp,\n })\n const invalid = useFieldSetInvalid()\n const { star: starSize, button: buttonSize } = (\n rating.max > 5 ? compactSizeClasses : sizeClasses\n )[rating.size]\n const interactive = !rating.readOnly && !rating.disabled\n const low = Math.min(rating.value ?? 0, rating.hovered ?? rating.value ?? 0)\n const high = Math.max(rating.value ?? 0, rating.hovered ?? 0)\n\n return (\n <RadioGroupPrimitive<number | null>\n data-slot=\"rating-stars\"\n data-invalid={invalid}\n // a read-only FieldLabel renders the Field's labelId and drops a caller id\n aria-labelledby={\n ariaLabelledBy ?? (field?.readOnly ? field.labelId : undefined)\n }\n aria-describedby={ariaDescribedBy}\n aria-invalid={ariaInvalid}\n value={rating.value}\n onValueChange={(next) => next !== null && rating.setValue(next)}\n readOnly={rating.readOnly}\n disabled={rating.disabled}\n name={rating.name}\n className={cn('flex', className)}\n onPointerLeave={(event) => {\n rating.setHovered(null)\n onPointerLeave?.(event)\n }}\n onKeyDown={(event) => {\n rating.setHovered(null)\n onKeyDown?.(event)\n }}\n {...props}\n >\n {Array.from({ length: rating.max }, (_, index) => {\n const star = index + 1\n const fill: StarFill =\n star <= low ? 'on' : star <= high ? 'preview' : 'off'\n return (\n <RadioPrimitive.Root\n key={star}\n value={star}\n aria-label={rating.labels?.[index] ?? String(star)}\n data-slot=\"rating-star\"\n data-fill={fill}\n // ARIA allows aria-readonly on the radiogroup, which Base UI also sets, not on a radio\n aria-readonly={undefined}\n nativeButton\n render={<button />}\n onPointerEnter={\n interactive ? () => rating.setHovered(star) : undefined\n }\n className={cn(\n // p-0 keeps the box against the UA button padding without preflight;\n // the px floor keeps axe's 24px target size when [data-scaling] shrinks rems\n 'inline-flex min-h-[24px] min-w-[24px] shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 touch-target-row any-pointer-coarse:min-w-11',\n 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n 'data-disabled:cursor-not-allowed data-readonly:cursor-default',\n buttonSize,\n )}\n >\n <Star className={cn(starSize, starTone(fill, rating.disabled))} />\n </RadioPrimitive.Root>\n )\n })}\n </RadioGroupPrimitive>\n )\n}\n\nconst captionTextSize: Record<RatingSize, string> = {\n small: 'text-[0.8125rem]',\n medium: 'text-sm',\n large: 'text-base',\n}\n\nfunction RatingCaption({\n className,\n children,\n ...props\n}: React.HTMLAttributes<HTMLParagraphElement>) {\n const rating = useRating()\n const shown = rating.hovered ?? rating.value\n const label = shown ? rating.labels?.[shown - 1] : undefined\n\n return (\n <p\n data-slot=\"rating-caption\"\n // the stars already carry each label as their accessible name\n aria-hidden=\"true\"\n // axe skips contrast on inactive text, and the dim tone is the disabled cue\n aria-disabled={rating.disabled || undefined}\n className={cn(\n 'leading-5',\n captionTextSize[rating.size],\n label !== undefined && 'font-medium',\n captionTone(label !== undefined, !!rating.hovered, rating.disabled),\n className,\n )}\n {...props}\n >\n {label ?? children}\n </p>\n )\n}\n\nexport { Rating, RatingCaption, RatingStars }\nexport type { RatingProps, RatingStarsProps }\n"],"mappings":"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"}
|