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
|
@@ -16,6 +16,7 @@ import { Tag, type TagSize } from "./tag"
|
|
|
16
16
|
import { Popover, PopoverAnchor, PopoverContent } from "./popover"
|
|
17
17
|
|
|
18
18
|
import { cn } from "@/lib/utils"
|
|
19
|
+
import { useListboxNavigation } from "@/hooks/use-listbox-navigation"
|
|
19
20
|
|
|
20
21
|
type AutocompleteSelectionMode = "single" | "multiple"
|
|
21
22
|
type AutocompleteValue = string | string[] | undefined
|
|
@@ -153,7 +154,6 @@ const Autocomplete = React.forwardRef<HTMLDivElement, AutocompleteProps>(
|
|
|
153
154
|
getInitialInputValue(defaultValue, selectionMode, options)
|
|
154
155
|
)
|
|
155
156
|
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
|
|
156
|
-
const [activeIndex, setActiveIndex] = React.useState(-1)
|
|
157
157
|
|
|
158
158
|
const selectedValue = isValueControlled ? value : uncontrolledValue
|
|
159
159
|
const selectedValues = normalizeSelectedValues(selectedValue, isMultiple)
|
|
@@ -182,28 +182,18 @@ const Autocomplete = React.forwardRef<HTMLDivElement, AutocompleteProps>(
|
|
|
182
182
|
!isDisabled &&
|
|
183
183
|
(isOpenControlled ? Boolean(open) : uncontrolledOpen) &&
|
|
184
184
|
(filteredOptions.length > 0 || hasRenderableContent(emptyText))
|
|
185
|
+
const listbox = useListboxNavigation({
|
|
186
|
+
idPrefix: listboxId,
|
|
187
|
+
isDisabled: (option) => Boolean(option.disabled),
|
|
188
|
+
open: menuOpen,
|
|
189
|
+
options: filteredOptions,
|
|
190
|
+
})
|
|
191
|
+
const activeIndex = listbox.activeIndex
|
|
185
192
|
const activeOption = filteredOptions[activeIndex]
|
|
186
|
-
const activeOptionId =
|
|
187
|
-
menuOpen && activeOption ? getOptionId(listboxId, activeIndex) : undefined
|
|
188
193
|
const resolvedTagSize =
|
|
189
194
|
tagSize ?? (size === "md" ? ("xs" as const) : ("md" as const))
|
|
190
195
|
const clearOnSelect = clearInputOnSelect ?? isMultiple
|
|
191
196
|
|
|
192
|
-
React.useEffect(() => {
|
|
193
|
-
if (!menuOpen) {
|
|
194
|
-
setActiveIndex(-1)
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
setActiveIndex((currentIndex) => {
|
|
199
|
-
if (currentIndex >= 0 && !filteredOptions[currentIndex]?.disabled) {
|
|
200
|
-
return currentIndex
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return getFirstEnabledIndex(filteredOptions)
|
|
204
|
-
})
|
|
205
|
-
}, [filteredOptions, menuOpen])
|
|
206
|
-
|
|
207
197
|
function setRootRef(node: HTMLDivElement | null) {
|
|
208
198
|
rootRef.current = node
|
|
209
199
|
|
|
@@ -307,30 +297,26 @@ const Autocomplete = React.forwardRef<HTMLDivElement, AutocompleteProps>(
|
|
|
307
297
|
if (event.key === "ArrowDown") {
|
|
308
298
|
event.preventDefault()
|
|
309
299
|
setMenuOpen(true)
|
|
310
|
-
|
|
311
|
-
getNextEnabledIndex(filteredOptions, currentIndex, 1)
|
|
312
|
-
)
|
|
300
|
+
listbox.move(1)
|
|
313
301
|
return
|
|
314
302
|
}
|
|
315
303
|
|
|
316
304
|
if (event.key === "ArrowUp") {
|
|
317
305
|
event.preventDefault()
|
|
318
306
|
setMenuOpen(true)
|
|
319
|
-
|
|
320
|
-
getNextEnabledIndex(filteredOptions, currentIndex, -1)
|
|
321
|
-
)
|
|
307
|
+
listbox.move(-1)
|
|
322
308
|
return
|
|
323
309
|
}
|
|
324
310
|
|
|
325
311
|
if (event.key === "Home" && menuOpen) {
|
|
326
312
|
event.preventDefault()
|
|
327
|
-
|
|
313
|
+
listbox.moveToEdge("first")
|
|
328
314
|
return
|
|
329
315
|
}
|
|
330
316
|
|
|
331
317
|
if (event.key === "End" && menuOpen) {
|
|
332
318
|
event.preventDefault()
|
|
333
|
-
|
|
319
|
+
listbox.moveToEdge("last")
|
|
334
320
|
return
|
|
335
321
|
}
|
|
336
322
|
|
|
@@ -412,7 +398,7 @@ const Autocomplete = React.forwardRef<HTMLDivElement, AutocompleteProps>(
|
|
|
412
398
|
>
|
|
413
399
|
<PopoverAnchor asChild>
|
|
414
400
|
<Input
|
|
415
|
-
aria-activedescendant={activeOptionId}
|
|
401
|
+
aria-activedescendant={listbox.activeOptionId}
|
|
416
402
|
aria-autocomplete="list"
|
|
417
403
|
aria-controls={menuOpen ? listboxId : undefined}
|
|
418
404
|
aria-describedby={shouldRenderHelper ? `${resolvedInputId}-helper` : undefined}
|
|
@@ -470,7 +456,7 @@ const Autocomplete = React.forwardRef<HTMLDivElement, AutocompleteProps>(
|
|
|
470
456
|
aria-selected={selectedValues.includes(option.value)}
|
|
471
457
|
data-active={activeIndex === index ? "true" : undefined}
|
|
472
458
|
disabled={option.disabled}
|
|
473
|
-
id={
|
|
459
|
+
id={listbox.optionId(index)}
|
|
474
460
|
itemType={option.itemType}
|
|
475
461
|
key={option.value}
|
|
476
462
|
label={option.label}
|
|
@@ -486,7 +472,7 @@ const Autocomplete = React.forwardRef<HTMLDivElement, AutocompleteProps>(
|
|
|
486
472
|
onClick={() => selectOption(option)}
|
|
487
473
|
onMouseDown={(event) => event.preventDefault()}
|
|
488
474
|
onMouseEnter={() => {
|
|
489
|
-
if (!option.disabled) setActiveIndex(index)
|
|
475
|
+
if (!option.disabled) listbox.setActiveIndex(index)
|
|
490
476
|
}}
|
|
491
477
|
/>
|
|
492
478
|
))
|
|
@@ -598,45 +584,6 @@ function hasRenderableContent(content: React.ReactNode) {
|
|
|
598
584
|
)
|
|
599
585
|
}
|
|
600
586
|
|
|
601
|
-
function getEnabledIndexes(options: AutocompleteOption[]) {
|
|
602
|
-
return options
|
|
603
|
-
.map((option, index) => (option.disabled ? -1 : index))
|
|
604
|
-
.filter((index) => index >= 0)
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
function getFirstEnabledIndex(options: AutocompleteOption[]) {
|
|
608
|
-
return getEnabledIndexes(options)[0] ?? -1
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
function getLastEnabledIndex(options: AutocompleteOption[]) {
|
|
612
|
-
const enabledIndexes = getEnabledIndexes(options)
|
|
613
|
-
return enabledIndexes[enabledIndexes.length - 1] ?? -1
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
function getNextEnabledIndex(
|
|
617
|
-
options: AutocompleteOption[],
|
|
618
|
-
currentIndex: number,
|
|
619
|
-
offset: 1 | -1
|
|
620
|
-
) {
|
|
621
|
-
const enabledIndexes = getEnabledIndexes(options)
|
|
622
|
-
if (enabledIndexes.length === 0) return -1
|
|
623
|
-
|
|
624
|
-
const currentPosition = enabledIndexes.indexOf(currentIndex)
|
|
625
|
-
if (currentPosition === -1) {
|
|
626
|
-
return offset > 0
|
|
627
|
-
? enabledIndexes[0]
|
|
628
|
-
: enabledIndexes[enabledIndexes.length - 1]
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
const nextPosition =
|
|
632
|
-
(currentPosition + offset + enabledIndexes.length) % enabledIndexes.length
|
|
633
|
-
return enabledIndexes[nextPosition]
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
function getOptionId(listboxId: string, index: number) {
|
|
637
|
-
return `${listboxId}-option-${index}`
|
|
638
|
-
}
|
|
639
|
-
|
|
640
587
|
function formatRemoveLabel(option: AutocompleteOption) {
|
|
641
588
|
const optionText = getOptionText(option)
|
|
642
589
|
return optionText ? `Remove ${optionText}` : "Remove option"
|
|
@@ -74,7 +74,7 @@ const avatarIconVariants = cva(
|
|
|
74
74
|
)
|
|
75
75
|
|
|
76
76
|
const avatarStatusVariants = cva(
|
|
77
|
-
"absolute end-0 bottom-0 flex size-[var(--bh-avatar-status-size)] items-center justify-center rounded-[var(--bh-radius-full)] border-[var(--bh-avatar-status-ring)] border-[var(--bh-bg-default)] shadow-[var(--
|
|
77
|
+
"absolute end-0 bottom-0 flex size-[var(--bh-avatar-status-size)] items-center justify-center rounded-[var(--bh-radius-full)] border-[var(--bh-avatar-status-ring)] border-[var(--bh-bg-default)] shadow-[var(--elevation-avatar-badge)]",
|
|
78
78
|
{
|
|
79
79
|
variants: {
|
|
80
80
|
size: {
|
|
@@ -235,7 +235,7 @@ function AvatarBadge({
|
|
|
235
235
|
<span
|
|
236
236
|
data-slot="avatar-badge"
|
|
237
237
|
className={cn(
|
|
238
|
-
"absolute end-0 bottom-0 flex size-[var(--bh-space-xl-12)] items-center justify-center rounded-[var(--bh-radius-full)] border-[var(--bh-space-xxs-2)] border-[var(--bh-bg-default)] bg-[var(--bh-interactive-brand-default)] text-[var(--bh-content-on-brand)] shadow-[var(--
|
|
238
|
+
"absolute end-0 bottom-0 flex size-[var(--bh-space-xl-12)] items-center justify-center rounded-[var(--bh-radius-full)] border-[var(--bh-space-xxs-2)] border-[var(--bh-bg-default)] bg-[var(--bh-interactive-brand-default)] text-[var(--bh-content-on-brand)] shadow-[var(--elevation-avatar-badge)]",
|
|
239
239
|
className
|
|
240
240
|
)}
|
|
241
241
|
{...props}
|
|
@@ -67,7 +67,8 @@ const legacyVariantMap: Record<
|
|
|
67
67
|
const badgeVariants = cva(
|
|
68
68
|
[
|
|
69
69
|
"inline-flex min-w-0 shrink-0 items-center justify-center whitespace-nowrap",
|
|
70
|
-
|
|
70
|
+
// The border sits inside the padding, like Figma's inside stroke: each side padding below subtracts its width.
|
|
71
|
+
"rounded-[var(--bh-radius-full)] border-[length:var(--badge-border-width,var(--bh-border-width-default))] border-transparent outline-none",
|
|
71
72
|
"transition-[background-color,border-color,color,box-shadow]",
|
|
72
73
|
"focus-visible:border-[var(--bh-border-focus)] focus-visible:ring-[length:var(--bh-badge-focus-ring-width)] focus-visible:ring-[var(--ring)]",
|
|
73
74
|
"aria-invalid:border-[var(--bh-border-focus-danger)] aria-invalid:ring-[var(--danger-ring)]",
|
|
@@ -78,7 +79,7 @@ const badgeVariants = cva(
|
|
|
78
79
|
variants: {
|
|
79
80
|
badgeStyle: {
|
|
80
81
|
light: "border-transparent",
|
|
81
|
-
outline: "border-
|
|
82
|
+
outline: "[--badge-border-width:var(--bh-badge-outline-border-width)] bg-transparent",
|
|
82
83
|
solid: "border-transparent",
|
|
83
84
|
},
|
|
84
85
|
color: {
|
|
@@ -145,30 +146,30 @@ const badgeVariants = cva(
|
|
|
145
146
|
type: "default",
|
|
146
147
|
size: ["xs", "sm", "default"],
|
|
147
148
|
showNumber: false,
|
|
148
|
-
class: "px-[var(--bh-space-md-8)]",
|
|
149
|
+
class: "px-[calc(var(--bh-space-md-8)-var(--badge-border-width,var(--bh-border-width-default)))]",
|
|
149
150
|
},
|
|
150
151
|
{
|
|
151
152
|
type: "default",
|
|
152
153
|
size: "lg",
|
|
153
154
|
showNumber: false,
|
|
154
|
-
class: "px-[var(--bh-space-xl-12)]",
|
|
155
|
+
class: "px-[calc(var(--bh-space-xl-12)-var(--badge-border-width,var(--bh-border-width-default)))]",
|
|
155
156
|
},
|
|
156
157
|
{
|
|
157
158
|
type: ["leading-icon", "trailing-icon", "dot"],
|
|
158
159
|
showNumber: false,
|
|
159
160
|
splitAction: false,
|
|
160
|
-
class: "px-[var(--bh-space-md-8)]",
|
|
161
|
+
class: "px-[calc(var(--bh-space-md-8)-var(--badge-border-width,var(--bh-border-width-default)))]",
|
|
161
162
|
},
|
|
162
163
|
{
|
|
163
164
|
type: "trailing-icon",
|
|
164
165
|
showNumber: false,
|
|
165
166
|
splitAction: true,
|
|
166
|
-
class: "ps-[var(--bh-space-md-8)]",
|
|
167
|
+
class: "ps-[calc(var(--bh-space-md-8)-var(--badge-border-width,var(--bh-border-width-default)))]",
|
|
167
168
|
},
|
|
168
169
|
{
|
|
169
170
|
type: "flag",
|
|
170
171
|
showNumber: false,
|
|
171
|
-
class: "ps-[var(--bh-space-xs-4)] pe-[var(--bh-space-md-8)]",
|
|
172
|
+
class: "ps-[calc(var(--bh-space-xs-4)-var(--badge-border-width,var(--bh-border-width-default)))] pe-[calc(var(--bh-space-md-8)-var(--badge-border-width,var(--bh-border-width-default)))]",
|
|
172
173
|
},
|
|
173
174
|
{
|
|
174
175
|
size: ["xs", "sm", "default"],
|
|
@@ -32,7 +32,7 @@ type ToggleGroupContextValue = {
|
|
|
32
32
|
const toggleGroupContext = React.createContext<ToggleGroupContextValue | null>(null)
|
|
33
33
|
|
|
34
34
|
const buttonGroupVariants = cva(
|
|
35
|
-
"inline-flex w-fit max-w-full items-center overflow-x-auto overflow-y-hidden rounded-[var(--bh-button-group-radius)] bg-[var(--bh-button-group-bg)] text-[var(--bh-button-group-content)] shadow-[var(--
|
|
35
|
+
"inline-flex w-fit max-w-full items-center overflow-x-auto overflow-y-hidden rounded-[var(--bh-button-group-radius)] bg-[var(--bh-button-group-bg)] text-[var(--bh-button-group-content)] shadow-[var(--elevation-button-group)] outline-none focus-visible:shadow-[var(--elevation-button-group-focus)] [--elevation-button-group:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-button-group-border),var(--elevation-component-default)]",
|
|
36
36
|
{
|
|
37
37
|
variants: {
|
|
38
38
|
size: {
|
|
@@ -48,7 +48,7 @@ const buttonGroupVariants = cva(
|
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
const buttonGroupItemVariants = cva(
|
|
51
|
-
"relative inline-flex shrink-0 items-center justify-center whitespace-nowrap bg-[var(--bh-button-group-item-bg)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] text-[var(--bh-button-group-content)] outline-none transition-[background-color,box-shadow,color] before:absolute before:inset-y-0 before:start-0 before:w-[var(--bh-border-width-default)] before:bg-[var(--bh-button-group-divider)] before:content-[''] first:before:hidden hover:bg-[var(--bh-button-group-item-hover-bg)] active:bg-[var(--bh-button-group-item-pressed-bg)] focus-visible:z-[var(--bh-z-raised)] focus-visible:shadow-[var(--
|
|
51
|
+
"relative inline-flex shrink-0 items-center justify-center whitespace-nowrap bg-[var(--bh-button-group-item-bg)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] text-[var(--bh-button-group-content)] outline-none transition-[background-color,box-shadow,color] before:absolute before:inset-y-0 before:start-0 before:w-[var(--bh-border-width-default)] before:bg-[var(--bh-button-group-divider)] before:content-[''] first:before:hidden hover:bg-[var(--bh-button-group-item-hover-bg)] active:bg-[var(--bh-button-group-item-pressed-bg)] focus-visible:z-[var(--bh-z-raised)] focus-visible:shadow-[var(--elevation-button-group-focus)] aria-[pressed=true]:bg-[var(--bh-button-group-item-active-bg)] aria-[pressed=true]:text-[var(--bh-button-group-content-active)] data-[active=true]:bg-[var(--bh-button-group-item-active-bg)] data-[active=true]:text-[var(--bh-button-group-content-active)] disabled:pointer-events-none disabled:bg-[var(--bh-button-group-item-disabled-bg)] disabled:text-[var(--bh-button-group-content-disabled)] [&_svg]:pointer-events-none [&_svg]:size-[var(--bh-button-group-icon-size)] [&_svg]:m-[calc((var(--bh-button-group-icon-slot-size)-var(--bh-button-group-icon-size))/2)] [&_svg]:shrink-0 rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
|
|
52
52
|
{
|
|
53
53
|
variants: {
|
|
54
54
|
size: {
|
|
@@ -6,25 +6,30 @@ import { cn } from "@/lib/utils"
|
|
|
6
6
|
|
|
7
7
|
type ControlDensity = "compact" | "default" | "comfortable"
|
|
8
8
|
|
|
9
|
+
const dangerButtonVariant =
|
|
10
|
+
"bg-[var(--bh-interactive-danger-default)] text-[var(--bh-content-on-color)] shadow-[var(--elevation-button-raised)] hover:bg-[var(--bh-interactive-danger-hover)] active:bg-[var(--bh-interactive-danger-pressed)] focus-visible:shadow-[var(--elevation-button-danger-focus)] disabled:bg-[var(--bh-interactive-danger-disabled)] disabled:text-[var(--bh-content-disabled)]"
|
|
11
|
+
|
|
12
|
+
const softDangerButtonVariant =
|
|
13
|
+
"border-[length:var(--bh-border-width-default)] border-transparent bg-[var(--bh-interactive-danger-soft-default)] text-[var(--bh-content-danger-default)] hover:bg-[var(--bh-interactive-danger-soft-hover)] active:bg-[var(--bh-interactive-danger-soft-pressed)] focus-visible:border-[var(--bh-interactive-danger-default)] focus-visible:shadow-[var(--elevation-button-danger-focus)] disabled:bg-[var(--bh-interactive-danger-soft-disabled)] disabled:text-[var(--bh-content-disabled)]"
|
|
14
|
+
|
|
9
15
|
const buttonVariants = cva(
|
|
10
|
-
"group/button inline-flex min-w-0 items-center justify-center whitespace-nowrap rounded-[var(--bh-control-default)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:shadow-[var(--
|
|
16
|
+
"group/button inline-flex min-w-0 items-center justify-center whitespace-nowrap rounded-[var(--bh-control-default)] text-[length:var(--bh-text-button-font-size)] font-[var(--bh-text-button-font-weight)] leading-[var(--bh-text-button-line-height)] tracking-[var(--bh-text-button-letter-spacing)] outline-none transition-[background-color,border-color,color,box-shadow] focus-visible:shadow-[var(--elevation-button-focus)] disabled:pointer-events-none disabled:shadow-none aria-invalid:border-[length:var(--bh-border-width-default)] aria-invalid:border-[var(--bh-border-danger-strong)] aria-invalid:shadow-[var(--elevation-button-danger-focus)] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[var(--bh-button-icon-size)] [&_svg:not([class*='size-'])]:m-[calc((var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] [&_svg[data-icon='inline-start']]:ms-[calc(var(--bh-button-icon-offset)+(var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] [&_svg[data-icon='inline-end']]:me-[calc(var(--bh-button-icon-offset)+(var(--bh-button-icon-slot-size)-var(--bh-button-icon-size))/2)] rtl:[&_svg[data-rtl-flip='true']]:-scale-x-100",
|
|
11
17
|
{
|
|
12
18
|
variants: {
|
|
13
19
|
variant: {
|
|
14
20
|
default:
|
|
15
|
-
"bg-[var(--bh-interactive-brand-default)] text-[var(--bh-content-on-brand)] shadow-[var(--
|
|
16
|
-
danger:
|
|
17
|
-
|
|
18
|
-
destructive:
|
|
19
|
-
"bg-[var(--bh-interactive-danger-default)] text-[var(--bh-content-on-color)] shadow-[var(--shadow-button-raised)] hover:bg-[var(--bh-interactive-danger-hover)] active:bg-[var(--bh-interactive-danger-pressed)] focus-visible:shadow-[var(--shadow-button-danger-focus)] disabled:bg-[var(--bh-interactive-danger-disabled)] disabled:text-[var(--bh-content-disabled)]",
|
|
21
|
+
"bg-[var(--bh-interactive-brand-default)] text-[var(--bh-content-on-brand)] shadow-[var(--elevation-button-raised)] hover:bg-[var(--bh-interactive-brand-hover)] active:bg-[var(--bh-interactive-brand-pressed)] disabled:bg-[var(--bh-interactive-brand-disabled)] disabled:text-[var(--bh-content-disabled)]",
|
|
22
|
+
danger: dangerButtonVariant,
|
|
23
|
+
/** @deprecated Use `danger` instead. */
|
|
24
|
+
destructive: dangerButtonVariant,
|
|
20
25
|
success:
|
|
21
|
-
"bg-[var(--bh-interactive-success-default)] text-[var(--bh-content-on-success)] shadow-[var(--
|
|
26
|
+
"bg-[var(--bh-interactive-success-default)] text-[var(--bh-content-on-success)] shadow-[var(--elevation-button-raised)] hover:bg-[var(--bh-interactive-success-hover)] active:bg-[var(--bh-interactive-success-pressed)] disabled:bg-[var(--bh-interactive-brand-disabled)] disabled:text-[var(--bh-content-disabled)]",
|
|
22
27
|
warning:
|
|
23
|
-
"bg-[var(--bh-interactive-warning-default)] text-[var(--bh-content-on-warning)] shadow-[var(--
|
|
28
|
+
"bg-[var(--bh-interactive-warning-default)] text-[var(--bh-content-on-warning)] shadow-[var(--elevation-button-raised)] hover:bg-[var(--bh-interactive-warning-hover)] active:bg-[var(--bh-interactive-warning-pressed)] disabled:bg-[var(--bh-interactive-brand-disabled)] disabled:text-[var(--bh-content-disabled)]",
|
|
24
29
|
secondary:
|
|
25
|
-
"bg-[var(--bh-interactive-secondary-default)] text-[var(--bh-content-default)] shadow-[var(--
|
|
30
|
+
"bg-[var(--bh-interactive-secondary-default)] text-[var(--bh-content-default)] shadow-[var(--elevation-button-secondary)] [--bh-button-secondary-border:var(--bh-border-default)] hover:bg-[var(--bh-interactive-secondary-hover)] active:bg-[var(--bh-interactive-secondary-pressed)] focus-visible:[--bh-button-secondary-focus-border:var(--bh-border-brand-strong)] focus-visible:shadow-[var(--elevation-button-secondary-focus)] disabled:border-[length:var(--bh-border-width-default)] disabled:border-[var(--bh-border-disabled)] disabled:bg-[var(--bh-interactive-secondary-disabled)] disabled:text-[var(--bh-content-disabled)]",
|
|
26
31
|
soft:
|
|
27
|
-
"border-[length:var(--bh-border-width-default)] border-transparent bg-[var(--bh-interactive-soft-default)] text-[var(--bh-content-default)] shadow-[var(--
|
|
32
|
+
"border-[length:var(--bh-border-width-default)] border-transparent bg-[var(--bh-interactive-soft-default)] text-[var(--bh-content-default)] shadow-[var(--elevation-button-soft)] hover:bg-[var(--bh-interactive-soft-hover)] active:bg-[var(--bh-interactive-soft-pressed)] focus-visible:border-[var(--bh-border-brand-strong)] disabled:bg-[var(--bh-interactive-soft-disabled)] disabled:text-[var(--bh-content-disabled)]",
|
|
28
33
|
outline:
|
|
29
34
|
"border-[length:var(--bh-border-width-default)] border-[var(--bh-border-default)] bg-[var(--bh-interactive-outlined-default)] text-[var(--bh-content-default)] hover:bg-[var(--bh-interactive-outlined-hover)] active:bg-[var(--bh-interactive-outlined-pressed)] focus-visible:border-[var(--bh-border-brand-strong)] disabled:border-[var(--bh-border-disabled)] disabled:bg-[var(--bh-interactive-outlined-disabled)] disabled:text-[var(--bh-content-disabled)]",
|
|
30
35
|
ghost:
|
|
@@ -33,10 +38,9 @@ const buttonVariants = cva(
|
|
|
33
38
|
"border-[length:var(--bh-border-width-default)] border-transparent bg-[var(--bh-interactive-ghost-default)] text-[var(--bh-content-brand-default)] hover:bg-[var(--bh-interactive-ghost-hover)] active:bg-[var(--bh-interactive-ghost-pressed)] focus-visible:border-[var(--bh-border-brand-strong)] disabled:bg-[var(--bh-interactive-ghost-disabled)] disabled:text-[var(--bh-content-disabled)]",
|
|
34
39
|
white:
|
|
35
40
|
"border-[length:var(--bh-border-width-default)] border-transparent bg-[var(--bh-bg-always-white)] text-[var(--bh-content-on-light)] focus-visible:border-[var(--bh-border-focus)] disabled:opacity-[var(--bh-opacity-30)]",
|
|
36
|
-
"soft-danger":
|
|
37
|
-
|
|
38
|
-
"soft-destructive":
|
|
39
|
-
"border-[length:var(--bh-border-width-default)] border-transparent bg-[var(--bh-interactive-danger-soft-default)] text-[var(--bh-content-danger-default)] hover:bg-[var(--bh-interactive-danger-soft-hover)] active:bg-[var(--bh-interactive-danger-soft-pressed)] focus-visible:border-[var(--bh-interactive-danger-default)] focus-visible:shadow-[var(--shadow-button-danger-focus)] disabled:bg-[var(--bh-interactive-danger-soft-disabled)] disabled:text-[var(--bh-content-disabled)]",
|
|
41
|
+
"soft-danger": softDangerButtonVariant,
|
|
42
|
+
/** @deprecated Use `soft-danger` instead. */
|
|
43
|
+
"soft-destructive": softDangerButtonVariant,
|
|
40
44
|
link: "bg-[var(--bh-interactive-ghost-default)] text-[var(--bh-content-brand-default)] hover:bg-[var(--bh-interactive-ghost-hover)] active:bg-[var(--bh-interactive-ghost-pressed)] focus-visible:border-[var(--bh-border-brand-strong)] disabled:text-[var(--bh-content-disabled)]",
|
|
41
45
|
},
|
|
42
46
|
size: {
|
|
@@ -84,6 +88,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|
|
84
88
|
data-density={density}
|
|
85
89
|
data-size={resolvedSize}
|
|
86
90
|
data-slot="button"
|
|
91
|
+
data-variant={variant ?? "default"}
|
|
87
92
|
ref={ref}
|
|
88
93
|
className={cn(buttonVariants({ variant, size: resolvedSize, className }))}
|
|
89
94
|
{...props}
|
|
@@ -8,6 +8,7 @@ import { Button, type ButtonProps } from "./button"
|
|
|
8
8
|
type CardOrientation = "vertical" | "horizontal"
|
|
9
9
|
type CardType = "leading-image" | "trailing-image" | "full-image"
|
|
10
10
|
type CardBreakpoint = "desktop" | "mobile"
|
|
11
|
+
type CardSize = "default" | "comfortable"
|
|
11
12
|
|
|
12
13
|
const cardVariants = cva(
|
|
13
14
|
[
|
|
@@ -16,10 +17,14 @@ const cardVariants = cva(
|
|
|
16
17
|
"text-[var(--bh-content-default)] shadow-[inset_0_0_0_var(--bh-border-width-default)_var(--bh-card-border)]",
|
|
17
18
|
"[--bh-card-bg:var(--bh-bg-raised)]",
|
|
18
19
|
"[--bh-card-border:var(--bh-border-subtle)]",
|
|
19
|
-
"[--bh-card-radius:var(--bh-radius-3xl-16)]",
|
|
20
20
|
],
|
|
21
21
|
{
|
|
22
22
|
variants: {
|
|
23
|
+
// comfortable is the app-shell panel: a smaller radius than the library cards.
|
|
24
|
+
size: {
|
|
25
|
+
default: "[--bh-card-radius:var(--bh-radius-3xl-16)]",
|
|
26
|
+
comfortable: "[--bh-card-radius:var(--bh-card-comfortable-radius)]",
|
|
27
|
+
},
|
|
23
28
|
orientation: {
|
|
24
29
|
vertical: "flex-col",
|
|
25
30
|
horizontal: "items-stretch",
|
|
@@ -67,6 +72,7 @@ const cardVariants = cva(
|
|
|
67
72
|
},
|
|
68
73
|
],
|
|
69
74
|
defaultVariants: {
|
|
75
|
+
size: "default",
|
|
70
76
|
orientation: "vertical",
|
|
71
77
|
type: "leading-image",
|
|
72
78
|
breakpoint: "desktop",
|
|
@@ -109,15 +115,18 @@ type CardActionsProps = React.ComponentProps<"div"> & {
|
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
type CardFooterProps = React.ComponentProps<"div">
|
|
112
|
-
type CardSurfaceProps = React.ComponentPropsWithoutRef<"article">
|
|
118
|
+
type CardSurfaceProps = React.ComponentPropsWithoutRef<"article"> & {
|
|
119
|
+
size?: CardSize
|
|
120
|
+
}
|
|
113
121
|
|
|
114
122
|
const CardSurface = React.forwardRef<HTMLElement, CardSurfaceProps>(
|
|
115
|
-
function CardSurface({ className, ...props }, ref) {
|
|
123
|
+
function CardSurface({ className, size = "default", ...props }, ref) {
|
|
116
124
|
return (
|
|
117
125
|
<article
|
|
118
126
|
data-slot="card-surface"
|
|
127
|
+
data-size={size}
|
|
119
128
|
ref={ref}
|
|
120
|
-
className={cn(cardVariants(), className)}
|
|
129
|
+
className={cn(cardVariants({ size }), className)}
|
|
121
130
|
{...props}
|
|
122
131
|
/>
|
|
123
132
|
)
|
|
@@ -144,6 +153,7 @@ const Card = React.forwardRef<HTMLElement, CardProps>(function Card({
|
|
|
144
153
|
showImage,
|
|
145
154
|
showMoreButton,
|
|
146
155
|
showSubtitle,
|
|
156
|
+
size = "default",
|
|
147
157
|
subtitle,
|
|
148
158
|
title,
|
|
149
159
|
type = "leading-image",
|
|
@@ -199,10 +209,12 @@ const Card = React.forwardRef<HTMLElement, CardProps>(function Card({
|
|
|
199
209
|
data-slot="card"
|
|
200
210
|
data-breakpoint={cardBreakpoint}
|
|
201
211
|
data-orientation={cardOrientation}
|
|
212
|
+
data-size={size ?? "default"}
|
|
202
213
|
data-type={cardType}
|
|
203
214
|
ref={ref}
|
|
204
215
|
className={cn(
|
|
205
216
|
cardVariants({
|
|
217
|
+
size,
|
|
206
218
|
orientation: cardOrientation,
|
|
207
219
|
type: cardType,
|
|
208
220
|
breakpoint: cardBreakpoint,
|
|
@@ -550,7 +562,7 @@ function CardMoreButton({
|
|
|
550
562
|
"inline-flex size-[var(--bh-card-more-size)] shrink-0 items-center justify-center",
|
|
551
563
|
"rounded-[var(--bh-control-default)] outline-none",
|
|
552
564
|
"transition-[background-color,color,box-shadow]",
|
|
553
|
-
"focus-visible:shadow-[var(--
|
|
565
|
+
"focus-visible:shadow-[var(--elevation-button-focus)]",
|
|
554
566
|
onImage
|
|
555
567
|
? "text-[var(--bh-content-on-color)] hover:bg-[var(--bh-interactive-ghost-hover)]"
|
|
556
568
|
: "text-[var(--bh-content-subtle)] hover:bg-[var(--bh-interactive-ghost-hover)] hover:text-[var(--bh-content-default)]"
|
|
@@ -633,6 +645,7 @@ export {
|
|
|
633
645
|
type CardMediaProps,
|
|
634
646
|
type CardOrientation,
|
|
635
647
|
type CardProps,
|
|
648
|
+
type CardSize,
|
|
636
649
|
type CardSurfaceProps,
|
|
637
650
|
type CardType,
|
|
638
651
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { ChartDatum, ChartSpec } from "./chart-options"
|
|
2
|
+
|
|
3
|
+
export type ChartBlockDefinition = {
|
|
4
|
+
title: string
|
|
5
|
+
description: string
|
|
6
|
+
spec: ChartSpec
|
|
7
|
+
periods?: { label: string; count: number }[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
|
11
|
+
const monthly = (keys: string[], values: number[][]): ChartDatum[] => values.map((row, index) => ({
|
|
12
|
+
name: months[index % 12] + (values.length > 12 ? ` '${24 + Math.floor(index / 12)}` : ""),
|
|
13
|
+
...Object.fromEntries(keys.map((key, column) => [key, row[column]])),
|
|
14
|
+
}))
|
|
15
|
+
const monthPeriods = [{ label: "3 months", count: 3 }, { label: "6 months", count: 6 }, { label: "All", count: Infinity }]
|
|
16
|
+
const series = (key: string, label: string, colorIndex: number) => ({ key, label, colorIndex })
|
|
17
|
+
|
|
18
|
+
// Illustrative source datasets, adapted from EvilCharts' MIT-licensed ECharts blocks.
|
|
19
|
+
// All visual decisions live in the shared token-aware option builders and block CSS.
|
|
20
|
+
export const chartBlockDefinitions = {
|
|
21
|
+
latency: {
|
|
22
|
+
title: "Latency Percentiles",
|
|
23
|
+
description: "Request latency by percentile, in milliseconds.",
|
|
24
|
+
periods: [{ label: "5 minutes", count: 5 }, { label: "15 minutes", count: 15 }, { label: "All", count: Infinity }],
|
|
25
|
+
spec: {
|
|
26
|
+
family: "area", curve: "linear", variant: "gradient", hoverHighlight: true,
|
|
27
|
+
series: [series("p99", "P99 (ms)", 3), series("p95", "P95 (ms)", 2), series("p75", "P75 (ms)", 4), series("p50", "P50 (ms)", 0)],
|
|
28
|
+
data: [
|
|
29
|
+
[188, 92, 44, 20], [196, 95, 46, 21], [181, 89, 43, 20], [192, 94, 47, 22],
|
|
30
|
+
[205, 99, 45, 21], [187, 91, 44, 20], [179, 88, 42, 19], [198, 96, 46, 21],
|
|
31
|
+
[210, 101, 48, 22], [194, 93, 45, 21], [202, 97, 47, 22], [215, 104, 49, 23],
|
|
32
|
+
[231, 112, 52, 23], [278, 131, 57, 24], [306, 142, 61, 25], [289, 135, 58, 24],
|
|
33
|
+
[247, 118, 53, 23], [216, 105, 49, 22], [201, 97, 46, 21], [193, 94, 45, 21],
|
|
34
|
+
[186, 90, 44, 20], [199, 96, 46, 21], [207, 100, 47, 22], [191, 92, 45, 20],
|
|
35
|
+
[184, 89, 43, 20], [196, 95, 46, 21], [209, 101, 48, 22], [198, 96, 46, 21], [204, 98, 46, 21],
|
|
36
|
+
].map(([p99, p95, p75, p50], index) => ({ name: `13:${String(index + 6).padStart(2, "0")}`, p99, p95, p75, p50 })),
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
portfolio: {
|
|
40
|
+
title: "Portfolio Comparison", description: "Compare the change in two example portfolios, in USD.",
|
|
41
|
+
periods: [{ label: "7 days", count: 7 }, { label: "14 days", count: 14 }, { label: "All", count: Infinity }],
|
|
42
|
+
spec: {
|
|
43
|
+
family: "area", curve: "step", variant: "dotted", hoverReveal: true,
|
|
44
|
+
series: [series("portfolioA", "Portfolio A (USD)", 0), series("portfolioB", "Portfolio B (USD)", 1)],
|
|
45
|
+
data: [
|
|
46
|
+
[53916, 53670], [54380, 53080], [54760, 52460], [54480, 51880], [54060, 51340],
|
|
47
|
+
[53820, 50900], [53520, 50520], [53240, 50200], [53080, 49960], [52840, 49760],
|
|
48
|
+
[52700, 49640], [52520, 49600], [52420, 49720], [52340, 49980], [52240, 50360],
|
|
49
|
+
[52140, 50820], [52080, 51340], [52020, 51900], [51960, 52440], [51900, 52960],
|
|
50
|
+
[51860, 53420], [51800, 53780], [51740, 54040], [51700, 54200], [51640, 54300],
|
|
51
|
+
[51580, 54320], [51538, 54287],
|
|
52
|
+
].map(([portfolioA, portfolioB], index) => ({ name: index < 10 ? `Dec ${index + 22}` : `Jan ${index - 9}`, portfolioA, portfolioB })),
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
benchmark: {
|
|
56
|
+
title: "Growth Benchmark", description: "Weekly signups compared with the target.",
|
|
57
|
+
periods: [{ label: "Recent 4", count: 4 }, { label: "Recent 12", count: 12 }, { label: "All", count: Infinity }],
|
|
58
|
+
spec: {
|
|
59
|
+
family: "area", curve: "step", legend: true,
|
|
60
|
+
series: [{ ...series("actual", "Actual", 0), variant: "hatched" }, { ...series("target", "Target", 4), type: "line", stroke: "dashed" }],
|
|
61
|
+
data: [
|
|
62
|
+
["Jan 1", 240, 125], ["Jan 8", 240, 125], ["Jan 15", 175, 200], ["Jan 22", 175, 200],
|
|
63
|
+
["Feb 1", 175, 200], ["Feb 8", 260, 125], ["Feb 15", 260, 125], ["Feb 22", 260, 125],
|
|
64
|
+
["Mar 1", 260, 190], ["Mar 8", 335, 190], ["Mar 15", 335, 190], ["Mar 22", 335, 390],
|
|
65
|
+
["Apr 1", 335, 390], ["Apr 8", 335, 390], ["Apr 15", 190, 390], ["Apr 22", 215, 305],
|
|
66
|
+
["May 1", 215, 305], ["May 8", 215, 175], ["May 15", 215, 175], ["May 22", 275, 175], ["Jun 1", 275, 245],
|
|
67
|
+
].map(([name, actual, target]) => ({ name, actual, target })),
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
audience: {
|
|
71
|
+
title: "Audience Growth", description: "Monthly listeners across every show and episode.", periods: monthPeriods,
|
|
72
|
+
spec: { family: "area", curve: "monotone", variant: "gradient", series: [{ key: "listeners", label: "Listeners", colorIndices: [1, 4, 5] }],
|
|
73
|
+
data: monthly(["listeners"], [[2980], [3120], [3460], [3380], [3720], [4180], [4560]]) },
|
|
74
|
+
},
|
|
75
|
+
payouts: {
|
|
76
|
+
title: "Payout Trend", description: "Paid and pending amounts by month, in USD.", periods: monthPeriods,
|
|
77
|
+
spec: { family: "line", curve: "monotone", legend: true,
|
|
78
|
+
series: [{ key: "payouts", label: "Paid (USD)", colorIndices: [2, 7] }, { key: "pending", label: "Pending (USD)", colorIndices: [4, 5] }],
|
|
79
|
+
data: monthly(["payouts", "pending"], [[312, 548], [388, 502], [342, 561], [455, 470], [521, 398], [486, 441], [573, 372], [640, 316], [598, 358], [662, 284], [617, 331], [690, 262]]) },
|
|
80
|
+
},
|
|
81
|
+
shipments: {
|
|
82
|
+
title: "Shipments", description: "Orders shipped in corresponding slots this week and last week.",
|
|
83
|
+
periods: [{ label: "2 days", count: 8 }, { label: "4 days", count: 16 }, { label: "All", count: Infinity }],
|
|
84
|
+
spec: { family: "line", curve: "linear", legend: true,
|
|
85
|
+
series: [series("current", "This week", 0), { ...series("previous", "Last week", 4), stroke: "dashed" }],
|
|
86
|
+
data: [[14, 34], [9, 41], [18, 37], [26, 29], [31, 24], [27, 33], [19, 45], [12, 52], [16, 47], [22, 39], [15, 44], [11, 50], [17, 43], [24, 31], [20, 26], [13, 22], [21, 28], [29, 36], [34, 42], [28, 55], [23, 49], [30, 40], [36, 35], [32, 44]]
|
|
87
|
+
.map(([current, previous], index) => ({ name: `${["Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][Math.floor(index / 4)]} ${index % 4 + 1}`, current, previous })) },
|
|
88
|
+
},
|
|
89
|
+
peak: {
|
|
90
|
+
title: "Peak Week", description: "Organic and paid signups, with the best week highlighted.",
|
|
91
|
+
periods: [{ label: "4 weeks", count: 4 }, { label: "8 weeks", count: 8 }, { label: "All", count: Infinity }],
|
|
92
|
+
spec: { family: "bar", stack: "stacked", highlightMax: true, legend: true,
|
|
93
|
+
series: [series("organic", "Organic", 0), series("paid", "Paid", 4)],
|
|
94
|
+
data: [[128, 74], [164, 91], [142, 66], [199, 108], [176, 84], [231, 167], [208, 96], [287, 158], [244, 112], [196, 88], [221, 103], [173, 79]]
|
|
95
|
+
.map(([organic, paid], index) => ({ name: `W${String(index + 1).padStart(2, "0")}`, organic, paid })) },
|
|
96
|
+
},
|
|
97
|
+
grid: {
|
|
98
|
+
title: "Grid Bar Chart", description: "Hourly sessions shown as columns of cells.",
|
|
99
|
+
periods: [{ label: "6 hours", count: 6 }, { label: "12 hours", count: 12 }, { label: "All", count: Infinity }],
|
|
100
|
+
spec: { family: "bar", variant: "blocks", series: [series("sessions", "Sessions", 0)],
|
|
101
|
+
data: [42, 28, 19, 14, 12, 18, 34, 66, 98, 124, 147, 163, 158, 171, 186, 174, 152, 138, 119, 96, 84, 71, 58, 47]
|
|
102
|
+
.map((sessions, index) => ({ name: `${String(index).padStart(2, "0")}:00`, sessions })) },
|
|
103
|
+
},
|
|
104
|
+
monospace: {
|
|
105
|
+
title: "Monospace Bar Chart", description: "Monthly sales with bars that expand on hover.", periods: monthPeriods,
|
|
106
|
+
spec: { family: "bar", variant: "expandable", series: [series("sales", "Sales (USD)", 0)],
|
|
107
|
+
data: monthly(["sales"], [342, 876, 512, 629, 458, 781, 394, 925, 647, 532, 803, 271, 388, 912, 564, 671, 499, 838, 427, 968, 702, 585, 861, 314].map((value) => [value])) },
|
|
108
|
+
},
|
|
109
|
+
"market-share": {
|
|
110
|
+
title: "Market Share", description: "Illustrative ecosystem value by company, in billions of USD.",
|
|
111
|
+
spec: { family: "pie", variant: "padded", series: [series("value", "Value (USD billions)", 0)],
|
|
112
|
+
data: [{ name: "Skyline", value: 27 }, { name: "Datawell", value: 21 }, { name: "Cloudpeak", value: 13 }, { name: "Taskbridge", value: 21 }, { name: "Insightloop", value: 6 }, { name: "Streamforge", value: 12 }] },
|
|
113
|
+
},
|
|
114
|
+
"progress-rings": {
|
|
115
|
+
title: "Progress Rings", description: "Where the workday leaks: illustrative findings from 1,240 survey responses.",
|
|
116
|
+
spec: { family: "pie", series: [series("value", "Respondents (%)", 0)], data: [
|
|
117
|
+
{ name: "Additional support requests", value: 48 }, { name: "Inaccurate forecasts", value: 67 },
|
|
118
|
+
] },
|
|
119
|
+
},
|
|
120
|
+
"revenue-mix": {
|
|
121
|
+
title: "Revenue Mix", description: "Revenue by sales channel, in USD.",
|
|
122
|
+
spec: { family: "pie", variant: "padded", series: [series("value", "Revenue (USD)", 0)], data: [
|
|
123
|
+
{ name: "Direct", value: 52400 }, { name: "Marketplace", value: 38900 }, { name: "Wholesale", value: 24150 }, { name: "Affiliate", value: 16300 },
|
|
124
|
+
] },
|
|
125
|
+
},
|
|
126
|
+
"reliability-score": {
|
|
127
|
+
title: "Reliability Score", description: "Delivery reliability: 842 out of 1,000, in the excellent band.",
|
|
128
|
+
spec: { family: "pie", variant: "padded", data: [
|
|
129
|
+
{ name: "At risk", from: 0, value: 450 }, { name: "Fair", from: 450, value: 200 }, { name: "Good", from: 650, value: 170 }, { name: "Excellent", from: 820, value: 180 },
|
|
130
|
+
], series: [series("value", "Band width", 0)] },
|
|
131
|
+
},
|
|
132
|
+
budget: {
|
|
133
|
+
title: "Monthly Budget", description: "The share and amount allocated to each monthly budget category.",
|
|
134
|
+
spec: { family: "radial", max: 100, series: [series("value", "Share (%)", 0)], data: [
|
|
135
|
+
{ name: "Payroll", value: 46, amount: 920000 }, { name: "Infrastructure", value: 21, amount: 420000 }, { name: "Marketing", value: 14, amount: 280000 }, { name: "Tooling", value: 10, amount: 200000 }, { name: "Support", value: 9, amount: 180000 },
|
|
136
|
+
] },
|
|
137
|
+
},
|
|
138
|
+
ride: {
|
|
139
|
+
title: "Ride Summary", description: "Distance, elevation, energy and cadence against their goals.",
|
|
140
|
+
spec: { family: "radial", data: [
|
|
141
|
+
{ name: "Distance", value: 18.4, goal: 25, unit: "km" }, { name: "Elevation", value: 312, goal: 450, unit: "m" },
|
|
142
|
+
{ name: "Energy", value: 684, goal: 1200, unit: "kJ" }, { name: "Cadence", value: 82, goal: 95, unit: "rpm" },
|
|
143
|
+
] },
|
|
144
|
+
},
|
|
145
|
+
"cache-tiers": {
|
|
146
|
+
title: "Cache Tiers", description: "Request routing across four cache tiers, from a sample of 1,000 requests.",
|
|
147
|
+
spec: { family: "radial", variant: "semi", max: 100, series: [series("value", "Share (%)", 0)], data: [
|
|
148
|
+
{ name: "L1 Memory", value: 61, count: 610 }, { name: "L2 Regional", value: 24, count: 240 },
|
|
149
|
+
{ name: "Edge Overflow", value: 10, count: 100 }, { name: "Origin Fetch", value: 5, count: 50 },
|
|
150
|
+
] },
|
|
151
|
+
},
|
|
152
|
+
allocation: {
|
|
153
|
+
title: "Fund Allocation", description: "Where the fund flows, in thousands of USD.",
|
|
154
|
+
spec: { family: "sankey", variant: "gradient", labels: "inside", data: [],
|
|
155
|
+
nodes: ["Inflow", "Equities", "Bonds", "Cash", "Growth", "Income", "Reserve"].map((name) => ({ name })),
|
|
156
|
+
links: [[0, 1, 78], [0, 2, 46], [0, 3, 24], [1, 4, 52], [1, 5, 26], [2, 5, 19], [2, 6, 27], [3, 4, 9], [3, 6, 15]]
|
|
157
|
+
.map(([source, target, value]) => ({ source, target, value })) },
|
|
158
|
+
},
|
|
159
|
+
pipeline: {
|
|
160
|
+
title: "Revenue Pipeline", description: "Four revenue sources flow into four operating allocations, in USD.",
|
|
161
|
+
spec: { family: "sankey", variant: "gradient", labels: "outside", data: [],
|
|
162
|
+
nodes: ["Retail", "Wholesale", "Licensing", "Services", "Pipeline", "Expansion", "Tooling", "Support", "Reserve"].map((name) => ({ name })),
|
|
163
|
+
links: [[0, 4, 31480], [1, 4, 46220], [2, 4, 14960], [3, 4, 28340], [4, 5, 52640], [4, 6, 9180], [4, 7, 12470], [4, 8, 46710]]
|
|
164
|
+
.map(([source, target, value]) => ({ source, target, value })) },
|
|
165
|
+
},
|
|
166
|
+
} satisfies Record<string, ChartBlockDefinition>
|
|
167
|
+
|
|
168
|
+
export type ChartBlockId = keyof typeof chartBlockDefinitions
|
|
169
|
+
|
|
170
|
+
export const chartBlocks = (Object.entries(chartBlockDefinitions) as [ChartBlockId, ChartBlockDefinition][])
|
|
171
|
+
.map(([id, definition]) => ({ id, title: definition.title, description: definition.description, family: definition.spec.family }))
|
|
172
|
+
|
|
173
|
+
export function getChartBlockSpec(block: ChartBlockId, period = "All"): ChartSpec {
|
|
174
|
+
const definition: ChartBlockDefinition = chartBlockDefinitions[block]
|
|
175
|
+
const count = definition.periods?.find((entry) => entry.label === period)?.count ?? Infinity
|
|
176
|
+
return { ...definition.spec, data: definition.spec.data.slice(-count) }
|
|
177
|
+
}
|