banhaten 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -1
- package/CONTRIBUTING.md +6 -4
- package/README.md +4 -2
- package/docs/design-spacing.md +13 -0
- package/docs/design-system/README.md +2 -0
- package/docs/design-system/appearances/default.md +1 -1
- package/docs/design-system/appearances/rounded.md +1 -1
- package/docs/design-system/appearances/sharp.md +1 -1
- package/docs/design-system/component-showcase-consistency-report.md +5 -5
- package/docs/design-system/components/README.md +15 -0
- package/docs/design-system/components/accordion.md +1 -1
- package/docs/design-system/components/activity-feed.md +1 -1
- package/docs/design-system/components/activity-heatmap.md +1 -1
- package/docs/design-system/components/agent-input.md +85 -0
- package/docs/design-system/components/alert-dialog.md +1 -1
- package/docs/design-system/components/alert.md +10 -4
- package/docs/design-system/components/app-header.md +97 -0
- package/docs/design-system/components/approval-card.md +134 -0
- package/docs/design-system/components/attribute.md +14 -2
- package/docs/design-system/components/avatar.md +1 -1
- package/docs/design-system/components/badge.md +1 -1
- package/docs/design-system/components/banner.md +1 -1
- package/docs/design-system/components/breadcrumbs.md +8 -3
- package/docs/design-system/components/button-group.md +1 -1
- package/docs/design-system/components/button.md +8 -5
- package/docs/design-system/components/card.md +15 -1
- package/docs/design-system/components/catalog-components.md +1 -1
- package/docs/design-system/components/chart.md +95 -24
- package/docs/design-system/components/checkbox.md +1 -1
- package/docs/design-system/components/collapsible.md +20 -6
- package/docs/design-system/components/command.md +1 -1
- package/docs/design-system/components/context-menu.md +1 -1
- package/docs/design-system/components/date-picker.md +1 -1
- package/docs/design-system/components/file-upload.md +1 -1
- package/docs/design-system/components/hover-card.md +1 -1
- package/docs/design-system/components/inline-citations.md +93 -0
- package/docs/design-system/components/input-group.md +1 -1
- package/docs/design-system/components/input.md +59 -3
- package/docs/design-system/components/item.md +1 -1
- package/docs/design-system/components/kbd.md +6 -4
- package/docs/design-system/components/menu.md +35 -4
- package/docs/design-system/components/menubar.md +1 -1
- package/docs/design-system/components/modal.md +2 -2
- package/docs/design-system/components/native-select.md +1 -1
- package/docs/design-system/components/navigation-menu.md +1 -1
- package/docs/design-system/components/onboarding-step-list-item.md +1 -1
- package/docs/design-system/components/page-header.md +58 -17
- package/docs/design-system/components/pagination.md +2 -2
- package/docs/design-system/components/popover.md +1 -1
- package/docs/design-system/components/progress-slider.md +1 -1
- package/docs/design-system/components/radio-card.md +1 -1
- package/docs/design-system/components/radio-group.md +1 -1
- package/docs/design-system/components/resizable.md +1 -1
- package/docs/design-system/components/rolling-count.md +61 -0
- package/docs/design-system/components/scroll-area.md +1 -1
- package/docs/design-system/components/select.md +12 -2
- package/docs/design-system/components/sheet.md +1 -1
- package/docs/design-system/components/shimmer-text.md +67 -0
- package/docs/design-system/components/sidebar.md +75 -8
- package/docs/design-system/components/skeleton.md +2 -2
- package/docs/design-system/components/slideout.md +1 -1
- package/docs/design-system/components/slider.md +2 -2
- package/docs/design-system/components/social-button.md +2 -2
- package/docs/design-system/components/streaming-text.md +82 -0
- package/docs/design-system/components/table.md +103 -21
- package/docs/design-system/components/tabs.md +25 -6
- package/docs/design-system/components/tag.md +1 -1
- package/docs/design-system/components/task-list.md +95 -0
- package/docs/design-system/components/textarea.md +1 -1
- package/docs/design-system/components/thinking-reasoning.md +72 -0
- package/docs/design-system/components/thinking-state.md +67 -0
- package/docs/design-system/components/timeline.md +1 -1
- package/docs/design-system/components/toast.md +1 -1
- package/docs/design-system/components/toggle.md +2 -2
- package/docs/design-system/components/toolbar.md +11 -6
- package/docs/design-system/components/tooltip.md +6 -4
- package/docs/design-system/density-and-elevation.md +16 -16
- package/docs/design-system/design-system-test-missing-items.md +8 -8
- package/docs/design-system/installation.md +2 -0
- package/docs/design-system/tabs.md +2 -1
- package/docs/design-system/template-gap-audit.md +32 -0
- package/docs/design-system/templates.md +59 -0
- package/docs/design-system/token-governance.md +15 -2
- package/package.json +2 -2
- package/registry/components/accordion.tsx +2 -2
- package/registry/components/agent-input-parts.tsx +552 -0
- package/registry/components/agent-input-state.ts +156 -0
- package/registry/components/agent-input.css +307 -0
- package/registry/components/agent-input.tsx +382 -0
- package/registry/components/alert-dialog.tsx +2 -2
- package/registry/components/alert.tsx +23 -7
- package/registry/components/app-header.tsx +151 -0
- package/registry/components/approval-card.css +409 -0
- package/registry/components/approval-card.tsx +401 -0
- package/registry/components/approval-frame.tsx +108 -0
- package/registry/components/approval-questions.tsx +559 -0
- package/registry/components/attribute.tsx +48 -13
- package/registry/components/autocomplete.tsx +15 -68
- package/registry/components/avatar.tsx +2 -2
- package/registry/components/badge.tsx +8 -7
- package/registry/components/button-group.tsx +2 -2
- package/registry/components/button.tsx +19 -14
- package/registry/components/card.tsx +18 -5
- package/registry/components/chart-block-data.ts +177 -0
- package/registry/components/chart-block.css +171 -0
- package/registry/components/chart-block.tsx +260 -0
- package/registry/components/chart-interactions.ts +123 -0
- package/registry/components/chart-options.cartesian.ts +162 -0
- package/registry/components/chart-options.paint.ts +99 -0
- package/registry/components/chart-options.parts.ts +72 -0
- package/registry/components/chart-options.radial.ts +109 -0
- package/registry/components/chart-options.ts +44 -0
- package/registry/components/chart-options.types.ts +67 -0
- package/registry/components/chart.tsx +1 -1
- package/registry/components/checkbox.tsx +6 -6
- package/registry/components/collapsible.tsx +43 -3
- package/registry/components/command.tsx +1 -1
- package/registry/components/context-menu.tsx +1 -1
- package/registry/components/data-chart.tsx +40 -0
- package/registry/components/date-picker.tsx +9 -9
- package/registry/components/echart-theme.ts +246 -0
- package/registry/components/echart.css +49 -0
- package/registry/components/echart.tsx +178 -0
- package/registry/components/expanded/Breadcrumbs.tsx +5 -1
- package/registry/components/expanded/PageHeader.tsx +67 -24
- package/registry/components/expanded/Table.tsx +60 -22
- package/registry/components/expanded/activityFeed.css +2 -2
- package/registry/components/expanded/activityHeatmap.css +1 -1
- package/registry/components/expanded/banner.css +2 -2
- package/registry/components/expanded/breadcrumbs.css +19 -2
- package/registry/components/expanded/catalogComponentsShowcase.css +1 -1
- package/registry/components/expanded/fileUpload.css +7 -7
- package/registry/components/expanded/onboardingStepListItem.css +2 -2
- package/registry/components/expanded/pageHeader.css +38 -44
- package/registry/components/expanded/slideout.css +4 -4
- package/registry/components/expanded/table.css +58 -9
- package/registry/components/expanded/timeline.css +1 -1
- package/registry/components/hover-card.tsx +1 -1
- package/registry/components/inline-citations.css +126 -0
- package/registry/components/inline-citations.tsx +292 -0
- package/registry/components/input-group.tsx +4 -4
- package/registry/components/input.tsx +150 -137
- package/registry/components/item.tsx +1 -1
- package/registry/components/kbd.tsx +3 -2
- package/registry/components/menu.tsx +117 -12
- package/registry/components/menubar.tsx +2 -2
- package/registry/components/modal.tsx +3 -3
- package/registry/components/native-select.tsx +4 -4
- package/registry/components/navigation-menu.tsx +2 -2
- package/registry/components/pagination.tsx +4 -4
- package/registry/components/popover.tsx +3 -3
- package/registry/components/progress-slider.tsx +1 -1
- package/registry/components/radio-card.tsx +7 -7
- package/registry/components/radio-group.tsx +2 -2
- package/registry/components/resizable.tsx +2 -2
- package/registry/components/rolling-count.css +37 -0
- package/registry/components/rolling-count.tsx +110 -0
- package/registry/components/scroll-area.tsx +1 -1
- package/registry/components/select.tsx +15 -10
- package/registry/components/sheet.tsx +2 -2
- package/registry/components/shimmer-text.css +69 -0
- package/registry/components/shimmer-text.tsx +43 -0
- package/registry/components/sidebar.tsx +528 -223
- package/registry/components/skeleton.tsx +1 -1
- package/registry/components/slider.tsx +2 -2
- package/registry/components/social-button.tsx +1 -1
- package/registry/components/streaming-text.css +45 -0
- package/registry/components/streaming-text.tsx +165 -0
- package/registry/components/table-elements.tsx +164 -5
- package/registry/components/tabs.css +32 -4
- package/registry/components/tabs.tsx +5 -1
- package/registry/components/tag.tsx +1 -1
- package/registry/components/task-list.css +328 -0
- package/registry/components/task-list.tsx +429 -0
- package/registry/components/textarea.tsx +4 -4
- package/registry/components/thinking-reasoning.css +178 -0
- package/registry/components/thinking-reasoning.tsx +253 -0
- package/registry/components/thinking-state.css +8 -0
- package/registry/components/thinking-state.tsx +46 -0
- package/registry/components/toast.tsx +1 -1
- package/registry/components/toggle.tsx +2 -2
- package/registry/components/toolbar.tsx +6 -6
- package/registry/components/tooltip.tsx +6 -4
- package/registry/examples/agent-input-demo.tsx +90 -0
- package/registry/examples/app-header-demo.tsx +40 -0
- package/registry/examples/approval-card-demo.tsx +83 -0
- package/registry/examples/button-demo.tsx +3 -0
- package/registry/examples/chart-block-demo.tsx +5 -0
- package/registry/examples/chart-demo.tsx +6 -21
- package/registry/examples/collection-filter-bar-demo.tsx +1 -1
- package/registry/examples/control-density-contract-demo.tsx +5 -1
- package/registry/examples/inline-citations-demo.tsx +31 -0
- package/registry/examples/input-actions-demo.tsx +36 -0
- package/registry/examples/menu-demo.tsx +1 -1
- package/registry/examples/popover-demo.tsx +1 -0
- package/registry/examples/rolling-count-demo.tsx +39 -0
- package/registry/examples/shimmer-text-demo.tsx +20 -0
- package/registry/examples/streaming-text-demo.tsx +28 -0
- package/registry/examples/task-list-demo.tsx +74 -0
- package/registry/examples/thinking-reasoning-demo.tsx +74 -0
- package/registry/examples/thinking-state-demo.tsx +13 -0
- package/registry/guidance/charts.LICENSE.txt +24 -0
- package/registry/hooks/use-controllable-state.ts +33 -0
- package/registry/hooks/use-listbox-navigation.ts +73 -0
- package/registry/hooks/use-motion-end.ts +55 -0
- package/registry/hooks/use-reduced-motion.ts +28 -0
- package/registry/index.json +1808 -190
- package/registry/styles/globals.css +992 -1088
- package/registry.json +385 -10
- package/schema/registry.schema.json +5 -1
- package/schema/tokens.schema.json +8 -4
- package/src/cli/file-transaction.js +15 -8
- package/src/cli/index.js +22 -29
- package/tokens/banhaten.tokens.json +490 -192
|
@@ -252,7 +252,7 @@ function SkeletonCard({
|
|
|
252
252
|
aria-hidden="true"
|
|
253
253
|
data-slot="skeleton-card"
|
|
254
254
|
className={cn(
|
|
255
|
-
"grid gap-[var(--bh-space-4xl-20)] rounded-[var(--bh-radius-xl-10)] border border-[var(--bh-border-default)] bg-[var(--bh-bg-raised)] p-[var(--bh-card-desktop-content-padding)] shadow-[var(--
|
|
255
|
+
"grid gap-[var(--bh-space-4xl-20)] rounded-[var(--bh-radius-xl-10)] border border-[var(--bh-border-default)] bg-[var(--bh-bg-raised)] p-[var(--bh-card-desktop-content-padding)] shadow-[var(--elevation-component-default)]",
|
|
256
256
|
className
|
|
257
257
|
)}
|
|
258
258
|
{...props}
|
|
@@ -244,7 +244,7 @@ function Slider({
|
|
|
244
244
|
data-has-label={showInlineLabel ? "true" : undefined}
|
|
245
245
|
data-slot="slider-thumb"
|
|
246
246
|
key={index}
|
|
247
|
-
className="relative z-[var(--bh-z-popover)] flex size-[var(--bh-slider-thumb-size-current)] shrink-0 items-center justify-center rounded-[var(--bh-slider-thumb-radius)] border-[var(--bh-slider-thumb-ring-width-current)] border-[var(--bh-slider-thumb-ring-bg)] bg-[var(--bh-slider-thumb-bg)] text-[var(--bh-content-default)] shadow-[var(--
|
|
247
|
+
className="relative z-[var(--bh-z-popover)] flex size-[var(--bh-slider-thumb-size-current)] shrink-0 items-center justify-center rounded-[var(--bh-slider-thumb-radius)] border-[var(--bh-slider-thumb-ring-width-current)] border-[var(--bh-slider-thumb-ring-bg)] bg-[var(--bh-slider-thumb-bg)] text-[var(--bh-content-default)] shadow-[var(--elevation-slider-thumb)] outline-none transition-[background-color,border-color,box-shadow] focus-visible:shadow-[var(--elevation-slider-thumb-focus)] group-data-[variant=pill]/slider:h-[var(--bh-slider-pill-height)] group-data-[variant=pill]/slider:w-[var(--bh-slider-pill-thumb-width)] group-data-[variant=pill]/slider:justify-end group-data-[variant=pill]/slider:rounded-[var(--bh-slider-pill-radius)] group-data-[variant=pill]/slider:border-[length:var(--bh-slider-pill-thumb-border-width)] group-data-[variant=pill]/slider:border-[var(--bh-slider-thumb-pill-border)] group-data-[variant=pill]/slider:bg-[var(--bh-slider-thumb-pill-bg)] group-data-[variant=pill]/slider:px-[var(--bh-space-lg-10)] group-data-[disabled]/slider:border-[var(--bh-bg-default)] group-data-[disabled]/slider:bg-[var(--bh-content-disabled)] group-data-[disabled]/slider:text-[var(--bh-content-disabled)] group-data-[variant=pill]/slider:group-data-[disabled]/slider:border-[var(--bh-border-disabled)] group-data-[variant=pill]/slider:group-data-[disabled]/slider:bg-[var(--bh-bg-disabled)]"
|
|
248
248
|
>
|
|
249
249
|
{showIndicator ? (
|
|
250
250
|
<SliderThumbIndicator
|
|
@@ -296,7 +296,7 @@ function SliderThumbIndicator({
|
|
|
296
296
|
? "bottom-[calc(100%+var(--bh-slider-indicator-gap))]"
|
|
297
297
|
: "top-[calc(100%+var(--bh-slider-indicator-gap))]",
|
|
298
298
|
isTooltip
|
|
299
|
-
? "rounded-[var(--bh-radius-md-6)] border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-raised)] px-[var(--bh-space-md-8)] py-[var(--bh-space-sm-6)] text-[length:var(--bh-text-body-2xs-medium-font-size)] leading-[var(--bh-text-body-2xs-medium-line-height)] font-[var(--bh-text-body-2xs-medium-font-weight)] tracking-[var(--bh-text-body-2xs-medium-letter-spacing)] text-[var(--bh-content-default)] shadow-[var(--
|
|
299
|
+
? "rounded-[var(--bh-radius-md-6)] border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-raised)] px-[var(--bh-space-md-8)] py-[var(--bh-space-sm-6)] text-[length:var(--bh-text-body-2xs-medium-font-size)] leading-[var(--bh-text-body-2xs-medium-line-height)] font-[var(--bh-text-body-2xs-medium-font-weight)] tracking-[var(--bh-text-body-2xs-medium-letter-spacing)] text-[var(--bh-content-default)] shadow-[var(--elevation-slider-tooltip)]"
|
|
300
300
|
: "text-[length:var(--bh-text-body-2xs-regular-font-size)] leading-[var(--bh-text-body-2xs-regular-line-height)] font-[var(--bh-text-body-2xs-regular-font-weight)] tracking-[var(--bh-text-body-2xs-regular-letter-spacing)] text-[var(--bh-content-subtle)]"
|
|
301
301
|
)}
|
|
302
302
|
>
|
|
@@ -116,7 +116,7 @@ const arabicSocialButtonMessages: SocialButtonMessages = {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
const socialButtonVariants = cva(
|
|
119
|
-
"group/social-button relative inline-flex max-w-full min-w-0 items-center justify-center whitespace-nowrap rounded-[var(--bh-social-button-radius)] bg-[var(--bh-social-button-bg)] text-[var(--bh-social-button-fg)] shadow-[var(--
|
|
119
|
+
"group/social-button relative inline-flex max-w-full min-w-0 items-center justify-center whitespace-nowrap rounded-[var(--bh-social-button-radius)] bg-[var(--bh-social-button-bg)] text-[var(--bh-social-button-fg)] shadow-[var(--elevation-social-button)] outline-none transition-[background-color,box-shadow,color] [--bh-social-button-current-border:var(--bh-social-button-border)] [--elevation-social-button:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-social-button-current-border,var(--bh-social-button-border,transparent)),var(--elevation-component-default)] hover:bg-[var(--bh-social-button-bg-hover)] focus-visible:[--bh-social-button-current-border:var(--bh-social-button-focus-border)] focus-visible:shadow-[var(--elevation-social-button-focus)] disabled:pointer-events-none disabled:opacity-[var(--bh-opacity-50)] [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
120
120
|
{
|
|
121
121
|
variants: {
|
|
122
122
|
variant: {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[data-slot="streaming-text"] {
|
|
2
|
+
color: var(--bh-content-default);
|
|
3
|
+
font-family: var(--bh-font-family);
|
|
4
|
+
font-size: var(--bh-text-body-sm-regular-font-size);
|
|
5
|
+
font-weight: var(--bh-text-body-sm-regular-font-weight);
|
|
6
|
+
letter-spacing: var(--bh-text-body-sm-regular-letter-spacing);
|
|
7
|
+
line-height: var(--bh-text-body-sm-regular-line-height);
|
|
8
|
+
overflow-wrap: anywhere;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* A slim text caret, not a block cursor: caret width comes from the scale. */
|
|
12
|
+
[data-slot="streaming-text-caret"] {
|
|
13
|
+
animation: bh-streaming-text-caret var(--bh-streaming-text-caret-duration)
|
|
14
|
+
step-end infinite;
|
|
15
|
+
background-color: currentColor;
|
|
16
|
+
block-size: 1em;
|
|
17
|
+
border-radius: var(--bh-radius-full);
|
|
18
|
+
display: inline-block;
|
|
19
|
+
inline-size: var(--bh-streaming-text-caret-width);
|
|
20
|
+
margin-inline-start: var(--bh-space-xxs-2);
|
|
21
|
+
vertical-align: text-bottom;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Solid while tokens arrive; the blink only starts once the text is complete. */
|
|
25
|
+
[data-slot="streaming-text"][data-state="streaming"]
|
|
26
|
+
[data-slot="streaming-text-caret"] {
|
|
27
|
+
animation: none;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@keyframes bh-streaming-text-caret {
|
|
31
|
+
0%,
|
|
32
|
+
100% {
|
|
33
|
+
opacity: var(--bh-opacity-100);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
50% {
|
|
37
|
+
opacity: var(--bh-opacity-0);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@media (prefers-reduced-motion: reduce) {
|
|
42
|
+
[data-slot="streaming-text-caret"] {
|
|
43
|
+
animation: none;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
import { useReducedMotion } from "@/hooks/use-reduced-motion"
|
|
7
|
+
|
|
8
|
+
import "./streaming-text.css"
|
|
9
|
+
|
|
10
|
+
type StreamingTextElement = "p" | "div" | "span"
|
|
11
|
+
|
|
12
|
+
type StreamingTextProps = Omit<React.HTMLAttributes<HTMLElement>, "children"> & {
|
|
13
|
+
/** Render element. Defaults to a paragraph. */
|
|
14
|
+
as?: StreamingTextElement
|
|
15
|
+
/** Hides the caret entirely. */
|
|
16
|
+
caret?: boolean
|
|
17
|
+
/**
|
|
18
|
+
* True while more text is on its way: the caret stays solid and the element
|
|
19
|
+
* reports itself busy.
|
|
20
|
+
*/
|
|
21
|
+
streaming?: boolean
|
|
22
|
+
/** The text that has arrived so far. */
|
|
23
|
+
text: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type UseTypewriterOptions = {
|
|
27
|
+
/** Called once each time the reveal reaches the end of a text. */
|
|
28
|
+
onComplete?: () => void
|
|
29
|
+
/** Characters revealed per second. */
|
|
30
|
+
speed?: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type TypewriterState = {
|
|
34
|
+
/** The part of the text revealed so far. */
|
|
35
|
+
text: string
|
|
36
|
+
/** True until the whole text is revealed. */
|
|
37
|
+
typing: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DEFAULT_SPEED = 220
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reveals a finished text locally, a few characters per frame. Text that grows
|
|
44
|
+
* continues from what is already shown and different text starts over. Under
|
|
45
|
+
* reduced motion the whole text shows at once.
|
|
46
|
+
*/
|
|
47
|
+
function useTypewriter(
|
|
48
|
+
text: string,
|
|
49
|
+
{ onComplete, speed = DEFAULT_SPEED }: UseTypewriterOptions = {}
|
|
50
|
+
): TypewriterState {
|
|
51
|
+
const reducedMotion = useReducedMotion()
|
|
52
|
+
const [shown, setShown] = React.useState(() => (reducedMotion ? text : ""))
|
|
53
|
+
const shownRef = React.useRef(shown)
|
|
54
|
+
const onCompleteRef = React.useRef(onComplete)
|
|
55
|
+
// The text each completion was reported for, so a speed change after the
|
|
56
|
+
// reveal (or a reduced-motion rerender) does not report it twice.
|
|
57
|
+
const completedFor = React.useRef<string | null>(null)
|
|
58
|
+
|
|
59
|
+
React.useEffect(() => {
|
|
60
|
+
onCompleteRef.current = onComplete
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const reveal = React.useCallback((value: string) => {
|
|
64
|
+
shownRef.current = value
|
|
65
|
+
setShown(value)
|
|
66
|
+
}, [])
|
|
67
|
+
|
|
68
|
+
const complete = React.useCallback((value: string) => {
|
|
69
|
+
if (completedFor.current === value) return
|
|
70
|
+
completedFor.current = value
|
|
71
|
+
onCompleteRef.current?.()
|
|
72
|
+
}, [])
|
|
73
|
+
|
|
74
|
+
React.useEffect(() => {
|
|
75
|
+
if (reducedMotion) {
|
|
76
|
+
reveal(text)
|
|
77
|
+
complete(text)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Continue from the current prefix when more text is appended, restart
|
|
82
|
+
// when the text changes to something unrelated.
|
|
83
|
+
const start = text.startsWith(shownRef.current) ? shownRef.current.length : 0
|
|
84
|
+
// Nothing left to type, including an empty response: report it once so a
|
|
85
|
+
// host waiting on onComplete never hangs.
|
|
86
|
+
if (start >= text.length) {
|
|
87
|
+
reveal(text)
|
|
88
|
+
complete(text)
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const charsPerMs = Math.max(1, speed) / 1000
|
|
93
|
+
let frame = 0
|
|
94
|
+
let origin: number | null = null
|
|
95
|
+
|
|
96
|
+
const tick = (now: number) => {
|
|
97
|
+
if (origin === null) origin = now
|
|
98
|
+
const count = Math.min(
|
|
99
|
+
text.length,
|
|
100
|
+
start + Math.floor((now - origin) * charsPerMs)
|
|
101
|
+
)
|
|
102
|
+
const next = text.slice(0, count)
|
|
103
|
+
if (next !== shownRef.current) reveal(next)
|
|
104
|
+
if (count < text.length) {
|
|
105
|
+
frame = window.requestAnimationFrame(tick)
|
|
106
|
+
} else {
|
|
107
|
+
complete(text)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
frame = window.requestAnimationFrame(tick)
|
|
112
|
+
return () => window.cancelAnimationFrame(frame)
|
|
113
|
+
}, [complete, reducedMotion, reveal, speed, text])
|
|
114
|
+
|
|
115
|
+
// Always a prefix of the current text, even in the render before the reveal
|
|
116
|
+
// catches up with a new one.
|
|
117
|
+
const visible = text.startsWith(shown) ? shown : ""
|
|
118
|
+
return { text: visible, typing: visible.length < text.length }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Text that arrives token by token. While `streaming` the caret stays solid;
|
|
123
|
+
* once the text is complete it settles into a slow blink. Pair it with
|
|
124
|
+
* `useTypewriter` to reveal a finished text locally.
|
|
125
|
+
*/
|
|
126
|
+
const StreamingText = React.forwardRef<HTMLElement, StreamingTextProps>(
|
|
127
|
+
function StreamingText(
|
|
128
|
+
{
|
|
129
|
+
as: Comp = "p",
|
|
130
|
+
caret = true,
|
|
131
|
+
className,
|
|
132
|
+
streaming = false,
|
|
133
|
+
text,
|
|
134
|
+
...props
|
|
135
|
+
},
|
|
136
|
+
ref
|
|
137
|
+
) {
|
|
138
|
+
return (
|
|
139
|
+
<Comp
|
|
140
|
+
aria-busy={streaming || undefined}
|
|
141
|
+
data-caret={caret ? "true" : "false"}
|
|
142
|
+
data-slot="streaming-text"
|
|
143
|
+
data-state={streaming ? "streaming" : "complete"}
|
|
144
|
+
// JSX checks a union tag against one member's ref type; every allowed
|
|
145
|
+
// element is an HTMLElement, which is what the public ref promises.
|
|
146
|
+
ref={ref as React.Ref<HTMLDivElement>}
|
|
147
|
+
className={cn("m-0 min-w-0 max-w-full", className)}
|
|
148
|
+
{...props}
|
|
149
|
+
>
|
|
150
|
+
{text}
|
|
151
|
+
{caret ? (
|
|
152
|
+
<span aria-hidden="true" data-slot="streaming-text-caret" />
|
|
153
|
+
) : null}
|
|
154
|
+
</Comp>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
export { StreamingText, useTypewriter }
|
|
160
|
+
export type {
|
|
161
|
+
StreamingTextElement,
|
|
162
|
+
StreamingTextProps,
|
|
163
|
+
TypewriterState,
|
|
164
|
+
UseTypewriterOptions,
|
|
165
|
+
}
|
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from "react"
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
4
5
|
|
|
5
6
|
import { cn } from "@/lib/utils"
|
|
6
7
|
|
|
7
|
-
type
|
|
8
|
+
type TableElementsSize = "comfortable"
|
|
9
|
+
type TableRootProps = React.ComponentPropsWithoutRef<"table"> & {
|
|
10
|
+
/** Applies the Table item 1.2 spec. Without a size the elements stay unstyled. */
|
|
11
|
+
size?: TableElementsSize
|
|
12
|
+
}
|
|
13
|
+
type TableContainerProps = React.ComponentPropsWithoutRef<"div">
|
|
8
14
|
type TableSectionProps = React.ComponentPropsWithoutRef<"thead">
|
|
9
15
|
type TableBodyProps = React.ComponentPropsWithoutRef<"tbody">
|
|
10
16
|
type TableRowElementProps = React.ComponentPropsWithoutRef<"tr">
|
|
11
17
|
type TableHeadProps = React.ComponentPropsWithoutRef<"th">
|
|
12
18
|
type TableCellProps = React.ComponentPropsWithoutRef<"td">
|
|
19
|
+
type TableCellTextProps = React.ComponentPropsWithoutRef<"span"> & {
|
|
20
|
+
/** Second line under the label, in body-2xs and content/subtle. */
|
|
21
|
+
caption?: React.ReactNode
|
|
22
|
+
/** Icon, avatar or logo before the text, 6px from it. */
|
|
23
|
+
leading?: React.ReactNode
|
|
24
|
+
tone?: "default" | "subtle"
|
|
25
|
+
weight?: "regular" | "medium"
|
|
26
|
+
}
|
|
27
|
+
type TableCellLinkProps = React.ComponentPropsWithoutRef<"button"> & {
|
|
28
|
+
/** Renders the child element, such as an anchor, with the link styling. */
|
|
29
|
+
asChild?: boolean
|
|
30
|
+
}
|
|
13
31
|
type DataTableSurfaceProps = React.ComponentPropsWithoutRef<"div">
|
|
14
32
|
|
|
15
33
|
const DataTableSurface = React.forwardRef<
|
|
@@ -27,13 +45,44 @@ const DataTableSurface = React.forwardRef<
|
|
|
27
45
|
)
|
|
28
46
|
})
|
|
29
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Rounded table frame. Its border is drawn over the content, like a Figma
|
|
50
|
+
* inside stroke, so header fills reach the edge; the table scrolls inside it.
|
|
51
|
+
*/
|
|
52
|
+
const TableContainer = React.forwardRef<HTMLDivElement, TableContainerProps>(
|
|
53
|
+
function TableContainer({ children, className, ...props }, ref) {
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
data-slot="table-container"
|
|
57
|
+
ref={ref}
|
|
58
|
+
className={cn(
|
|
59
|
+
"relative min-w-0 max-w-full overflow-hidden rounded-[var(--bh-table-container-radius)] bg-[var(--bh-table-container-bg)]",
|
|
60
|
+
"after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:shadow-[inset_0_0_0_var(--bh-border-width-default)_var(--bh-table-container-border)] after:content-['']",
|
|
61
|
+
className
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
>
|
|
65
|
+
<div data-slot="table-scroll" className="max-w-full overflow-x-auto">
|
|
66
|
+
{children}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
30
73
|
const TableRoot = React.forwardRef<HTMLTableElement, TableRootProps>(
|
|
31
|
-
function TableRoot({ className, ...props }, ref) {
|
|
74
|
+
function TableRoot({ className, size, ...props }, ref) {
|
|
32
75
|
return (
|
|
33
76
|
<table
|
|
77
|
+
data-size={size}
|
|
34
78
|
data-slot="table-root"
|
|
35
79
|
ref={ref}
|
|
36
|
-
className={cn(
|
|
80
|
+
className={cn(
|
|
81
|
+
"group/table w-full border-collapse text-start",
|
|
82
|
+
size === "comfortable" &&
|
|
83
|
+
"min-w-[var(--bh-table-comfortable-min-width)] text-[var(--bh-table-comfortable-cell-content)]",
|
|
84
|
+
className
|
|
85
|
+
)}
|
|
37
86
|
{...props}
|
|
38
87
|
/>
|
|
39
88
|
)
|
|
@@ -87,7 +136,12 @@ const TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(
|
|
|
87
136
|
<th
|
|
88
137
|
data-slot="table-head"
|
|
89
138
|
ref={ref}
|
|
90
|
-
className={cn(
|
|
139
|
+
className={cn(
|
|
140
|
+
"text-start",
|
|
141
|
+
"group-data-[size=comfortable]/table:h-[var(--bh-table-comfortable-header-height)] group-data-[size=comfortable]/table:bg-[var(--bh-table-comfortable-header-bg)] group-data-[size=comfortable]/table:px-[var(--bh-table-comfortable-cell-padding-x)] group-data-[size=comfortable]/table:py-[var(--bh-space-none)] group-data-[size=comfortable]/table:align-middle",
|
|
142
|
+
"group-data-[size=comfortable]/table:text-[length:var(--bh-text-body-sm-medium-font-size)] group-data-[size=comfortable]/table:font-[var(--bh-text-body-sm-medium-font-weight)] group-data-[size=comfortable]/table:leading-[var(--bh-text-body-sm-medium-line-height)] group-data-[size=comfortable]/table:tracking-[var(--bh-text-body-sm-medium-letter-spacing)] group-data-[size=comfortable]/table:text-[var(--bh-table-comfortable-header-content)]",
|
|
143
|
+
className
|
|
144
|
+
)}
|
|
91
145
|
scope={scope}
|
|
92
146
|
{...props}
|
|
93
147
|
/>
|
|
@@ -101,7 +155,105 @@ const TableCell = React.forwardRef<HTMLTableCellElement, TableCellProps>(
|
|
|
101
155
|
<td
|
|
102
156
|
data-slot="table-cell"
|
|
103
157
|
ref={ref}
|
|
104
|
-
className={
|
|
158
|
+
className={cn(
|
|
159
|
+
"group-data-[size=comfortable]/table:h-[var(--bh-table-comfortable-row-height)] group-data-[size=comfortable]/table:px-[var(--bh-table-comfortable-cell-padding-x)] group-data-[size=comfortable]/table:py-[var(--bh-space-none)] group-data-[size=comfortable]/table:text-start group-data-[size=comfortable]/table:align-middle",
|
|
160
|
+
"group-data-[size=comfortable]/table:text-[length:var(--bh-text-body-md-regular-font-size)] group-data-[size=comfortable]/table:font-[var(--bh-text-body-md-regular-font-weight)] group-data-[size=comfortable]/table:leading-[var(--bh-text-body-md-regular-line-height)] group-data-[size=comfortable]/table:tracking-[var(--bh-text-body-md-regular-letter-spacing)]",
|
|
161
|
+
className
|
|
162
|
+
)}
|
|
163
|
+
{...props}
|
|
164
|
+
/>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Table item 1.2 text content: a label with an optional caption under it
|
|
171
|
+
* (line + subline) and an optional leading icon or avatar. The label keeps
|
|
172
|
+
* the cell's text size, so a captioned cell is 40px of text in a 56px or
|
|
173
|
+
* taller row. When the label opens a record, pass a TableCellLink so the
|
|
174
|
+
* label and caption keep one start edge.
|
|
175
|
+
*/
|
|
176
|
+
const TableCellText = React.forwardRef<HTMLSpanElement, TableCellTextProps>(
|
|
177
|
+
function TableCellText(
|
|
178
|
+
{
|
|
179
|
+
caption,
|
|
180
|
+
children,
|
|
181
|
+
className,
|
|
182
|
+
leading,
|
|
183
|
+
tone = "default",
|
|
184
|
+
weight = "regular",
|
|
185
|
+
...props
|
|
186
|
+
},
|
|
187
|
+
ref
|
|
188
|
+
) {
|
|
189
|
+
return (
|
|
190
|
+
<span
|
|
191
|
+
data-slot="table-cell-text"
|
|
192
|
+
data-tone={tone}
|
|
193
|
+
data-weight={weight}
|
|
194
|
+
ref={ref}
|
|
195
|
+
className={cn(
|
|
196
|
+
"inline-flex min-w-0 max-w-full items-center gap-[var(--bh-table-cell-text-gap)] align-middle",
|
|
197
|
+
className
|
|
198
|
+
)}
|
|
199
|
+
{...props}
|
|
200
|
+
>
|
|
201
|
+
{leading && (
|
|
202
|
+
<span
|
|
203
|
+
data-slot="table-cell-text-leading"
|
|
204
|
+
className="inline-flex shrink-0 items-center justify-center"
|
|
205
|
+
>
|
|
206
|
+
{leading}
|
|
207
|
+
</span>
|
|
208
|
+
)}
|
|
209
|
+
<span className="grid min-w-0 grid-cols-[minmax(0,1fr)]">
|
|
210
|
+
{/* Label and caption clip long text with an ellipsis; the clip margin leaves room for a link's focus ring and offset. */}
|
|
211
|
+
<span
|
|
212
|
+
data-slot="table-cell-text-label"
|
|
213
|
+
dir="auto"
|
|
214
|
+
className={cn(
|
|
215
|
+
"overflow-clip text-ellipsis whitespace-nowrap [overflow-clip-margin:var(--bh-space-md-8)]",
|
|
216
|
+
weight === "medium"
|
|
217
|
+
? "font-[var(--bh-font-weight-medium)]"
|
|
218
|
+
: "font-[var(--bh-font-weight-regular)]",
|
|
219
|
+
tone === "subtle" && "text-[var(--bh-content-subtle)]"
|
|
220
|
+
)}
|
|
221
|
+
>
|
|
222
|
+
{children}
|
|
223
|
+
</span>
|
|
224
|
+
{caption && (
|
|
225
|
+
<span
|
|
226
|
+
data-slot="table-cell-text-caption"
|
|
227
|
+
dir="auto"
|
|
228
|
+
className="overflow-clip text-ellipsis whitespace-nowrap [overflow-clip-margin:var(--bh-space-md-8)] text-[length:var(--bh-text-body-2xs-regular-font-size)] font-[var(--bh-text-body-2xs-regular-font-weight)] leading-[var(--bh-text-body-2xs-regular-line-height)] tracking-[var(--bh-text-body-2xs-regular-letter-spacing)] text-[var(--bh-table-caption-content)]"
|
|
229
|
+
>
|
|
230
|
+
{caption}
|
|
231
|
+
</span>
|
|
232
|
+
)}
|
|
233
|
+
</span>
|
|
234
|
+
</span>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* A cell label that opens a record. It reads as the cell's own text, flush
|
|
241
|
+
* with the cell padding, and underlines on hover; Table item 1.2 has no
|
|
242
|
+
* padded control in text cells. Renders a button, or its child with asChild.
|
|
243
|
+
*/
|
|
244
|
+
const TableCellLink = React.forwardRef<HTMLButtonElement, TableCellLinkProps>(
|
|
245
|
+
function TableCellLink({ asChild = false, className, type, ...props }, ref) {
|
|
246
|
+
const Comp = asChild ? Slot : "button"
|
|
247
|
+
return (
|
|
248
|
+
<Comp
|
|
249
|
+
data-slot="table-cell-link"
|
|
250
|
+
ref={ref}
|
|
251
|
+
type={asChild ? undefined : (type ?? "button")}
|
|
252
|
+
className={cn(
|
|
253
|
+
"inline-block max-w-full cursor-pointer truncate rounded-[var(--bh-radius-sm-4)] border-0 bg-transparent p-[var(--bh-space-none)] text-start align-top [color:inherit] [font:inherit] tracking-[inherit] no-underline outline-none hover:underline",
|
|
254
|
+
"focus-visible:underline focus-visible:outline-solid focus-visible:outline-[length:var(--bh-focus-ring-width)] focus-visible:outline-offset-[var(--bh-space-xxs-2)] focus-visible:outline-[var(--bh-border-focus)]",
|
|
255
|
+
className
|
|
256
|
+
)}
|
|
105
257
|
{...props}
|
|
106
258
|
/>
|
|
107
259
|
)
|
|
@@ -112,6 +264,9 @@ export {
|
|
|
112
264
|
DataTableSurface,
|
|
113
265
|
TableBody,
|
|
114
266
|
TableCell,
|
|
267
|
+
TableCellLink,
|
|
268
|
+
TableCellText,
|
|
269
|
+
TableContainer,
|
|
115
270
|
TableHead,
|
|
116
271
|
TableHeader,
|
|
117
272
|
TableRoot,
|
|
@@ -120,7 +275,11 @@ export {
|
|
|
120
275
|
export type {
|
|
121
276
|
DataTableSurfaceProps,
|
|
122
277
|
TableBodyProps,
|
|
278
|
+
TableCellLinkProps,
|
|
123
279
|
TableCellProps,
|
|
280
|
+
TableCellTextProps,
|
|
281
|
+
TableContainerProps,
|
|
282
|
+
TableElementsSize,
|
|
124
283
|
TableHeadProps,
|
|
125
284
|
TableRootProps,
|
|
126
285
|
TableRowElementProps,
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
--tabs-height-underline-md: calc(var(--bh-space-8xl-48) + var(--bh-space-md-8));
|
|
23
23
|
--tabs-height-underline-sm: var(--bh-space-8xl-48);
|
|
24
24
|
--tabs-height-underline-content: calc(var(--bh-space-6xl-32) + var(--bh-space-xs-4));
|
|
25
|
-
--tabs-
|
|
25
|
+
--tabs-elevation-selected: var(--elevation-button-soft);
|
|
26
26
|
|
|
27
27
|
box-sizing: border-box;
|
|
28
28
|
display: flex;
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
display: flex;
|
|
82
82
|
align-items: center;
|
|
83
83
|
justify-content: center;
|
|
84
|
-
min-
|
|
84
|
+
min-inline-size: max-content;
|
|
85
85
|
color: var(--tabs-content-subtle);
|
|
86
86
|
background: transparent;
|
|
87
87
|
font: inherit;
|
|
@@ -90,6 +90,10 @@
|
|
|
90
90
|
cursor: pointer;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
.ds-tabs--hug .ds-tabs__tab {
|
|
94
|
+
flex: 0 0 auto;
|
|
95
|
+
}
|
|
96
|
+
|
|
93
97
|
.ds-tabs__tab:disabled,
|
|
94
98
|
.ds-tabs__tab[data-disabled] {
|
|
95
99
|
cursor: not-allowed;
|
|
@@ -143,11 +147,13 @@
|
|
|
143
147
|
.ds-tabs--rounded .ds-tabs__tab[aria-selected="true"],
|
|
144
148
|
.ds-tabs--rounded .ds-tabs__tab[data-state="active"] {
|
|
145
149
|
background: var(--tabs-bg-default);
|
|
146
|
-
box-shadow: var(--tabs-
|
|
150
|
+
box-shadow: var(--tabs-elevation-selected);
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
.ds-tabs--segment.ds-tabs--md .ds-tabs__tab,
|
|
150
|
-
.ds-tabs--rounded.ds-tabs--md .ds-tabs__tab
|
|
154
|
+
.ds-tabs--rounded.ds-tabs--md .ds-tabs__tab,
|
|
155
|
+
.ds-tabs--segment.ds-tabs--comfortable .ds-tabs__tab,
|
|
156
|
+
.ds-tabs--rounded.ds-tabs--comfortable .ds-tabs__tab {
|
|
151
157
|
min-height: var(--tabs-height-segment-md);
|
|
152
158
|
padding: var(--tabs-space-md) var(--tabs-space-xl);
|
|
153
159
|
}
|
|
@@ -197,6 +203,28 @@
|
|
|
197
203
|
flex: 1 0 0;
|
|
198
204
|
}
|
|
199
205
|
|
|
206
|
+
/* Comfortable: view navigation in an app page. Tabs sit md apart with no divider under the row, and the active
|
|
207
|
+
indicator is an inset shadow so the label stays centred in the full tab height. */
|
|
208
|
+
.ds-tabs--underline.ds-tabs--comfortable {
|
|
209
|
+
gap: var(--tabs-space-md);
|
|
210
|
+
border-bottom: 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.ds-tabs--underline.ds-tabs--comfortable .ds-tabs__tab {
|
|
214
|
+
min-height: var(--tabs-height-underline-sm);
|
|
215
|
+
border-bottom: 0;
|
|
216
|
+
box-shadow: inset 0 calc(var(--tabs-space-xxs) * -1) 0 0 transparent;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.ds-tabs--underline.ds-tabs--comfortable .ds-tabs__content {
|
|
220
|
+
height: var(--tabs-height-underline-content);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.ds-tabs--underline.ds-tabs--comfortable .ds-tabs__tab[aria-selected="true"],
|
|
224
|
+
.ds-tabs--underline.ds-tabs--comfortable .ds-tabs__tab[data-state="active"] {
|
|
225
|
+
box-shadow: inset 0 calc(var(--tabs-space-xxs) * -1) 0 0 var(--tabs-content-brand);
|
|
226
|
+
}
|
|
227
|
+
|
|
200
228
|
.ds-tabs--rtl .ds-tabs__tab {
|
|
201
229
|
text-align: start;
|
|
202
230
|
}
|
|
@@ -9,7 +9,11 @@ import { useResolvedDirection } from "@/hooks/use-direction"
|
|
|
9
9
|
import "./tabs.css"
|
|
10
10
|
|
|
11
11
|
export type TabsVariant = "underline" | "segment" | "rounded"
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* `comfortable` is the view navigation in an app page (ATS360 frames): underline tabs 48px tall, 8px apart, with the
|
|
14
|
+
* active indicator drawn inside the tab and no divider under the row. Segment and rounded tabs render it like `md`.
|
|
15
|
+
*/
|
|
16
|
+
export type TabsSize = "md" | "sm" | "xs" | "comfortable"
|
|
13
17
|
|
|
14
18
|
export type TabsItem = {
|
|
15
19
|
content?: React.ReactNode
|
|
@@ -396,7 +396,7 @@ function TagCloseButton({
|
|
|
396
396
|
type={type || "button"}
|
|
397
397
|
className={cn(
|
|
398
398
|
"relative z-[var(--bh-z-raised)] inline-flex size-[var(--bh-space-3xl-16)] shrink-0 items-center justify-center rounded-[var(--bh-radius-full)] text-current outline-none transition-[background-color,box-shadow]",
|
|
399
|
-
"hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:shadow-[var(--
|
|
399
|
+
"hover:bg-[var(--bh-interactive-ghost-hover)] focus-visible:shadow-[var(--elevation-button-focus)] disabled:pointer-events-none",
|
|
400
400
|
offset && "ms-[var(--bh-space-xs-4)]",
|
|
401
401
|
className
|
|
402
402
|
)}
|