@adea-ai/ui 0.61.0 → 0.62.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/README.md +44 -6
- package/dist/LICENSE +201 -0
- package/dist/NOTICE +50 -0
- package/dist/components/composites/account-menu/index.js +2 -0
- package/dist/components/composites/list-row/index.js +2 -0
- package/dist/components/composites/settings/index.js +2 -0
- package/dist/components/composites/stat/index.js +2 -0
- package/dist/components/composites/update-dialog/index.js +2 -0
- package/dist/components/composites/workspace-mark/index.js +2 -0
- package/dist/components/conversation/index.js +6 -0
- package/dist/components/layout/app-shell/index.js +2 -0
- package/dist/components/layout/page/index.js +2 -0
- package/dist/components/layout/panel/index.js +2 -0
- package/dist/components/layout/side-rail/index.js +3 -0
- package/dist/components/layout/sidebar-nav/index.js +2 -0
- package/dist/components/layout/status-bar/index.js +2 -0
- package/dist/components/layout/top-bar/index.js +2 -0
- package/dist/components/motion/index.js +2 -2
- package/dist/components/theme/index.js +5 -0
- package/dist/components/ui/accordion/index.js +2 -0
- package/dist/components/ui/alert/index.js +2 -0
- package/dist/components/ui/alert-dialog/index.js +2 -0
- package/dist/components/ui/aspect-ratio/index.js +2 -0
- package/dist/components/ui/avatar/index.js +2 -0
- package/dist/components/ui/badge/index.js +2 -0
- package/dist/components/ui/board/index.js +2 -0
- package/dist/components/ui/breadcrumb/index.js +2 -0
- package/dist/components/ui/button/index.js +2 -0
- package/dist/components/ui/button-group/index.js +2 -0
- package/dist/components/ui/calendar/index.js +2 -0
- package/dist/components/ui/card/index.js +2 -0
- package/dist/components/ui/carousel/index.js +2 -0
- package/dist/components/ui/chart/index.js +2 -0
- package/dist/components/ui/checkbox/index.js +2 -0
- package/dist/components/ui/code-block/index.js +2 -0
- package/dist/components/ui/collapsible/index.js +2 -0
- package/dist/components/ui/combobox/index.js +2 -0
- package/dist/components/ui/command/index.js +2 -0
- package/dist/components/ui/context-menu/index.js +2 -0
- package/dist/components/ui/detail-panel/index.js +2 -0
- package/dist/components/ui/dialog/index.js +2 -0
- package/dist/components/ui/diff-block/index.js +2 -0
- package/dist/components/ui/drawer/index.js +2 -0
- package/dist/components/ui/dropdown-menu/index.js +2 -0
- package/dist/components/ui/empty/index.js +2 -0
- package/dist/components/ui/entity-icon/index.js +2 -0
- package/dist/components/ui/field/index.js +2 -0
- package/dist/components/ui/hover-card/index.js +2 -0
- package/dist/components/ui/input/index.js +2 -0
- package/dist/components/ui/input-group/index.js +2 -0
- package/dist/components/ui/input-otp/index.js +2 -0
- package/dist/components/ui/item/index.js +2 -0
- package/dist/components/ui/kbd/index.js +2 -0
- package/dist/components/ui/label/index.js +2 -0
- package/dist/components/ui/menubar/index.js +2 -0
- package/dist/components/ui/modal-dialog/index.js +2 -0
- package/dist/components/ui/navigation-menu/index.js +2 -0
- package/dist/components/ui/pagination/index.js +2 -0
- package/dist/components/ui/popover/index.js +2 -0
- package/dist/components/ui/progress/index.js +2 -0
- package/dist/components/ui/radio-group/index.js +2 -0
- package/dist/components/ui/resizable/index.js +2 -0
- package/dist/components/ui/scroll-area/index.js +2 -0
- package/dist/components/ui/select/index.js +2 -0
- package/dist/components/ui/separator/index.js +2 -0
- package/dist/components/ui/sheet/index.js +2 -0
- package/dist/components/ui/skeleton/index.js +2 -0
- package/dist/components/ui/slider/index.js +2 -0
- package/dist/components/ui/spinner/index.js +2 -0
- package/dist/components/ui/status-chip/index.js +2 -0
- package/dist/components/ui/switch/index.js +2 -0
- package/dist/components/ui/table/index.js +2 -0
- package/dist/components/ui/tabs/index.js +2 -0
- package/dist/components/ui/textarea/index.js +2 -0
- package/dist/components/ui/toast/index.js +2 -0
- package/dist/components/ui/toggle/index.js +2 -0
- package/dist/components/ui/toggle-group/index.js +2 -0
- package/dist/components/ui/tooltip/index.js +2 -0
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/r/accordion.json +3 -1
- package/dist/r/account-menu.json +2 -1
- package/dist/r/alert-dialog.json +3 -1
- package/dist/r/alert.json +3 -1
- package/dist/r/app-shell.json +3 -1
- package/dist/r/aspect-ratio.json +3 -1
- package/dist/r/avatar.json +3 -1
- package/dist/r/badge.json +3 -1
- package/dist/r/board.json +3 -1
- package/dist/r/breadcrumb.json +3 -1
- package/dist/r/button-group.json +2 -1
- package/dist/r/button.json +3 -1
- package/dist/r/calendar.json +2 -1
- package/dist/r/card.json +3 -1
- package/dist/r/carousel.json +2 -1
- package/dist/r/chart.json +3 -1
- package/dist/r/checkbox.json +3 -1
- package/dist/r/code-block.json +2 -1
- package/dist/r/collapsible.json +3 -1
- package/dist/r/combobox.json +3 -1
- package/dist/r/command.json +3 -1
- package/dist/r/context-menu.json +3 -1
- package/dist/r/conversation.json +2 -1
- package/dist/r/detail-panel.json +2 -1
- package/dist/r/dialog.json +3 -1
- package/dist/r/diff-block.json +3 -1
- package/dist/r/drawer.json +3 -1
- package/dist/r/dropdown-menu.json +3 -1
- package/dist/r/empty.json +3 -1
- package/dist/r/entity-icon.json +3 -1
- package/dist/r/field.json +3 -1
- package/dist/r/hover-card.json +3 -1
- package/dist/r/input-group.json +3 -1
- package/dist/r/input-otp.json +3 -1
- package/dist/r/input.json +3 -1
- package/dist/r/item.json +3 -1
- package/dist/r/kbd.json +3 -1
- package/dist/r/label.json +3 -1
- package/dist/r/lib.json +50 -0
- package/dist/r/list-row.json +3 -1
- package/dist/r/menubar.json +3 -1
- package/dist/r/modal-dialog.json +2 -1
- package/dist/r/motion.json +22 -0
- package/dist/r/navigation-menu.json +3 -1
- package/dist/r/page.json +3 -1
- package/dist/r/pagination.json +3 -1
- package/dist/r/panel.json +3 -1
- package/dist/r/popover.json +3 -1
- package/dist/r/progress.json +3 -1
- package/dist/r/radio-group.json +3 -1
- package/dist/r/registry.json +320 -73
- package/dist/r/resizable.json +3 -1
- package/dist/r/scroll-area.json +3 -1
- package/dist/r/select.json +3 -1
- package/dist/r/separator.json +3 -1
- package/dist/r/settings.json +3 -1
- package/dist/r/sheet.json +2 -1
- package/dist/r/side-rail.json +3 -1
- package/dist/r/sidebar-nav.json +3 -1
- package/dist/r/skeleton.json +3 -1
- package/dist/r/slider.json +3 -1
- package/dist/r/spinner.json +3 -1
- package/dist/r/src/components/composites/account-menu/account-menu.tsx +150 -0
- package/dist/r/src/components/composites/account-menu/index.ts +1 -0
- package/dist/r/src/components/composites/list-row/index.ts +1 -0
- package/dist/r/src/components/composites/list-row/list-row.tsx +116 -0
- package/dist/r/src/components/composites/settings/index.ts +1 -0
- package/dist/r/src/components/composites/settings/settings.tsx +142 -0
- package/dist/r/src/components/composites/stat/index.ts +9 -0
- package/dist/r/src/components/composites/stat/stat.tsx +155 -0
- package/dist/r/src/components/composites/update-dialog/index.ts +7 -0
- package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +529 -0
- package/dist/r/src/components/composites/workspace-mark/index.ts +1 -0
- package/dist/r/src/components/composites/workspace-mark/workspace-mark.tsx +72 -0
- package/dist/r/src/components/conversation/conversation-avatar.tsx +60 -0
- package/dist/r/src/components/conversation/conversation-surface.tsx +107 -0
- package/dist/r/src/components/conversation/index.ts +25 -0
- package/dist/r/src/components/conversation/message-composer.tsx +239 -0
- package/dist/r/src/components/conversation/message-row.tsx +387 -0
- package/dist/r/src/components/conversation/thread-panel.tsx +141 -0
- package/dist/r/src/components/layout/app-shell/app-shell.tsx +92 -0
- package/dist/r/src/components/layout/app-shell/index.ts +1 -0
- package/dist/r/src/components/layout/page/index.ts +9 -0
- package/dist/r/src/components/layout/page/page.tsx +122 -0
- package/dist/r/src/components/layout/panel/index.ts +12 -0
- package/dist/r/src/components/layout/panel/panel.tsx +166 -0
- package/dist/r/src/components/layout/side-rail/index.ts +12 -0
- package/dist/r/src/components/layout/side-rail/side-rail-context.ts +42 -0
- package/dist/r/src/components/layout/side-rail/side-rail.tsx +405 -0
- package/dist/r/src/components/layout/sidebar-nav/index.ts +14 -0
- package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +244 -0
- package/dist/r/src/components/layout/status-bar/index.ts +1 -0
- package/dist/r/src/components/layout/status-bar/status-bar.tsx +73 -0
- package/dist/r/src/components/layout/top-bar/index.ts +10 -0
- package/dist/r/src/components/layout/top-bar/top-bar.tsx +166 -0
- package/dist/r/src/components/motion/index.ts +2 -0
- package/dist/r/src/components/motion/presence.tsx +32 -0
- package/dist/r/src/components/theme/appearance-panel.tsx +179 -0
- package/dist/r/src/components/theme/index.ts +22 -0
- package/dist/r/src/components/theme/theme-picker.tsx +174 -0
- package/dist/r/src/components/theme/theme-provider.tsx +309 -0
- package/dist/r/src/components/theme/theme-toggle.tsx +64 -0
- package/dist/r/src/components/ui/accordion/accordion.tsx +66 -0
- package/dist/r/src/components/ui/accordion/index.ts +1 -0
- package/dist/r/src/components/ui/alert/alert.tsx +95 -0
- package/dist/r/src/components/ui/alert/index.ts +1 -0
- package/dist/r/src/components/ui/alert-dialog/alert-dialog.tsx +176 -0
- package/dist/r/src/components/ui/alert-dialog/index.ts +13 -0
- package/dist/r/src/components/ui/aspect-ratio/aspect-ratio.tsx +38 -0
- package/dist/r/src/components/ui/aspect-ratio/index.ts +1 -0
- package/dist/r/src/components/ui/avatar/avatar.tsx +114 -0
- package/dist/r/src/components/ui/avatar/index.ts +9 -0
- package/dist/r/src/components/ui/badge/badge.tsx +59 -0
- package/dist/r/src/components/ui/badge/index.ts +1 -0
- package/dist/r/src/components/ui/board/board.tsx +254 -0
- package/dist/r/src/components/ui/board/index.ts +10 -0
- package/dist/r/src/components/ui/breadcrumb/breadcrumb.tsx +117 -0
- package/dist/r/src/components/ui/breadcrumb/index.ts +9 -0
- package/dist/r/src/components/ui/button/button.tsx +89 -0
- package/dist/r/src/components/ui/button/index.ts +1 -0
- package/dist/r/src/components/ui/button-group/button-group.tsx +92 -0
- package/dist/r/src/components/ui/button-group/index.ts +6 -0
- package/dist/r/src/components/ui/calendar/calendar.tsx +277 -0
- package/dist/r/src/components/ui/calendar/index.ts +1 -0
- package/dist/r/src/components/ui/card/card.tsx +82 -0
- package/dist/r/src/components/ui/card/index.ts +10 -0
- package/dist/r/src/components/ui/carousel/carousel.tsx +209 -0
- package/dist/r/src/components/ui/carousel/index.ts +1 -0
- package/dist/r/src/components/ui/chart/chart.tsx +413 -0
- package/dist/r/src/components/ui/chart/index.ts +17 -0
- package/dist/r/src/components/ui/checkbox/checkbox.tsx +99 -0
- package/dist/r/src/components/ui/checkbox/index.ts +1 -0
- package/dist/r/src/components/ui/code-block/code-block.tsx +238 -0
- package/dist/r/src/components/ui/code-block/index.ts +1 -0
- package/dist/r/src/components/ui/collapsible/collapsible.tsx +54 -0
- package/dist/r/src/components/ui/collapsible/index.ts +1 -0
- package/dist/r/src/components/ui/combobox/combobox.tsx +142 -0
- package/dist/r/src/components/ui/combobox/index.ts +10 -0
- package/dist/r/src/components/ui/command/command.tsx +214 -0
- package/dist/r/src/components/ui/command/index.ts +12 -0
- package/dist/r/src/components/ui/context-menu/context-menu.tsx +163 -0
- package/dist/r/src/components/ui/context-menu/index.ts +15 -0
- package/dist/r/src/components/ui/detail-panel/detail-panel.tsx +161 -0
- package/dist/r/src/components/ui/detail-panel/index.ts +9 -0
- package/dist/r/src/components/ui/dialog/dialog.tsx +138 -0
- package/dist/r/src/components/ui/dialog/index.ts +14 -0
- package/dist/r/src/components/ui/diff-block/diff-block.tsx +286 -0
- package/dist/r/src/components/ui/diff-block/index.ts +11 -0
- package/dist/r/src/components/ui/drawer/drawer.tsx +144 -0
- package/dist/r/src/components/ui/drawer/index.ts +13 -0
- package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +203 -0
- package/dist/r/src/components/ui/dropdown-menu/index.ts +18 -0
- package/dist/r/src/components/ui/empty/empty.tsx +104 -0
- package/dist/r/src/components/ui/empty/index.ts +10 -0
- package/dist/r/src/components/ui/entity-icon/entity-icon.tsx +154 -0
- package/dist/r/src/components/ui/entity-icon/index.ts +1 -0
- package/dist/r/src/components/ui/field/field.tsx +131 -0
- package/dist/r/src/components/ui/field/index.ts +11 -0
- package/dist/r/src/components/ui/hover-card/hover-card.tsx +48 -0
- package/dist/r/src/components/ui/hover-card/index.ts +1 -0
- package/dist/r/src/components/ui/input/index.ts +1 -0
- package/dist/r/src/components/ui/input/input.tsx +41 -0
- package/dist/r/src/components/ui/input-group/index.ts +8 -0
- package/dist/r/src/components/ui/input-group/input-group.tsx +111 -0
- package/dist/r/src/components/ui/input-otp/index.ts +1 -0
- package/dist/r/src/components/ui/input-otp/input-otp.tsx +108 -0
- package/dist/r/src/components/ui/item/index.ts +10 -0
- package/dist/r/src/components/ui/item/item.tsx +146 -0
- package/dist/r/src/components/ui/kbd/index.ts +1 -0
- package/dist/r/src/components/ui/kbd/kbd.tsx +42 -0
- package/dist/r/src/components/ui/label/index.ts +1 -0
- package/dist/r/src/components/ui/label/label.tsx +36 -0
- package/dist/r/src/components/ui/menubar/index.ts +16 -0
- package/dist/r/src/components/ui/menubar/menubar.tsx +190 -0
- package/dist/r/src/components/ui/modal-dialog/index.ts +1 -0
- package/dist/r/src/components/ui/modal-dialog/modal-dialog.tsx +95 -0
- package/dist/r/src/components/ui/navigation-menu/index.ts +13 -0
- package/dist/r/src/components/ui/navigation-menu/navigation-menu.tsx +229 -0
- package/dist/r/src/components/ui/pagination/index.ts +8 -0
- package/dist/r/src/components/ui/pagination/pagination.tsx +144 -0
- package/dist/r/src/components/ui/popover/index.ts +10 -0
- package/dist/r/src/components/ui/popover/popover.tsx +81 -0
- package/dist/r/src/components/ui/progress/index.ts +1 -0
- package/dist/r/src/components/ui/progress/progress.tsx +67 -0
- package/dist/r/src/components/ui/radio-group/index.ts +1 -0
- package/dist/r/src/components/ui/radio-group/radio-group.tsx +78 -0
- package/dist/r/src/components/ui/resizable/index.ts +6 -0
- package/dist/r/src/components/ui/resizable/resizable.tsx +74 -0
- package/dist/r/src/components/ui/scroll-area/index.ts +1 -0
- package/dist/r/src/components/ui/scroll-area/scroll-area.tsx +66 -0
- package/dist/r/src/components/ui/select/index.ts +11 -0
- package/dist/r/src/components/ui/select/select.tsx +188 -0
- package/dist/r/src/components/ui/separator/index.ts +1 -0
- package/dist/r/src/components/ui/separator/separator.tsx +40 -0
- package/dist/r/src/components/ui/sheet/index.ts +13 -0
- package/dist/r/src/components/ui/sheet/sheet.tsx +132 -0
- package/dist/r/src/components/ui/skeleton/index.ts +1 -0
- package/dist/r/src/components/ui/skeleton/skeleton.tsx +28 -0
- package/dist/r/src/components/ui/slider/index.ts +1 -0
- package/dist/r/src/components/ui/slider/slider.tsx +81 -0
- package/dist/r/src/components/ui/spinner/index.ts +1 -0
- package/dist/r/src/components/ui/spinner/spinner.tsx +58 -0
- package/dist/r/src/components/ui/status-chip/index.ts +8 -0
- package/dist/r/src/components/ui/status-chip/status-chip.tsx +142 -0
- package/dist/r/src/components/ui/switch/index.ts +1 -0
- package/dist/r/src/components/ui/switch/switch.tsx +100 -0
- package/dist/r/src/components/ui/table/index.ts +11 -0
- package/dist/r/src/components/ui/table/table.tsx +125 -0
- package/dist/r/src/components/ui/tabs/index.ts +8 -0
- package/dist/r/src/components/ui/tabs/tabs.tsx +77 -0
- package/dist/r/src/components/ui/textarea/index.ts +1 -0
- package/dist/r/src/components/ui/textarea/textarea.tsx +36 -0
- package/dist/r/src/components/ui/toast/index.ts +1 -0
- package/dist/r/src/components/ui/toast/toast.tsx +220 -0
- package/dist/r/src/components/ui/toggle/index.ts +1 -0
- package/dist/r/src/components/ui/toggle/toggle.tsx +60 -0
- package/dist/r/src/components/ui/toggle-group/index.ts +6 -0
- package/dist/r/src/components/ui/toggle-group/toggle-group.tsx +66 -0
- package/dist/r/src/components/ui/tooltip/index.ts +9 -0
- package/dist/r/src/components/ui/tooltip/tooltip.tsx +102 -0
- package/dist/r/src/lib/keyed-rows.ts +68 -0
- package/dist/r/src/lib/overlay.ts +70 -0
- package/dist/r/src/lib/themes.ts +490 -0
- package/dist/r/src/lib/tokens.ts +688 -0
- package/dist/r/src/lib/utils.ts +76 -0
- package/dist/r/src/lib/variants.ts +62 -0
- package/dist/r/src/lib/version-notes.ts +65 -0
- package/dist/r/src/styles/base.css +644 -0
- package/dist/r/src/styles/fonts.css +26 -0
- package/dist/r/src/styles/globals.css +18 -0
- package/dist/r/src/styles/theme.css +864 -0
- package/dist/r/stat.json +3 -1
- package/dist/r/status-bar.json +3 -1
- package/dist/r/status-chip.json +2 -1
- package/dist/r/switch.json +3 -1
- package/dist/r/table.json +3 -1
- package/dist/r/tabs.json +3 -1
- package/dist/r/textarea.json +3 -1
- package/dist/r/theming.json +42 -0
- package/dist/r/toast.json +3 -1
- package/dist/r/toggle-group.json +2 -1
- package/dist/r/toggle.json +3 -1
- package/dist/r/tooltip.json +3 -1
- package/dist/r/top-bar.json +3 -1
- package/dist/r/update-dialog.json +2 -1
- package/dist/r/workspace-mark.json +2 -1
- package/package.json +10 -9
- package/registry.json +320 -73
- package/src/index.ts +2 -4
- package/dist/components/ui/diff-block/diff-block.test.d.ts +0 -2
- package/dist/components/ui/diff-block/diff-block.test.d.ts.map +0 -1
- package/dist/lib/keyed-rows.test.d.ts +0 -2
- package/dist/lib/keyed-rows.test.d.ts.map +0 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { createEffect, createSignal, For, onCleanup, Show, splitProps, type JSX } from 'solid-js'
|
|
2
|
+
import createEmblaCarousel from 'embla-carousel-solid'
|
|
3
|
+
import type { EmblaCarouselType } from 'embla-carousel'
|
|
4
|
+
import { ChevronLeft, ChevronRight } from 'lucide-solid'
|
|
5
|
+
import { cn } from '../../../lib/utils'
|
|
6
|
+
import { Button } from '../button'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Carousel.
|
|
10
|
+
*
|
|
11
|
+
* A horizontal scroller with snap points, over embla. The primitive owns the
|
|
12
|
+
* physics — momentum, snapping, drag — and this owns the two things a carousel
|
|
13
|
+
* always needs and embla deliberately leaves to the caller: a keyboard path and a
|
|
14
|
+
* way to tell how far along you are.
|
|
15
|
+
*
|
|
16
|
+
* **It is a scroll container, not a slide show.** `slidesToScroll` defaults to 1
|
|
17
|
+
* and the track is keyboard-focusable, so arrow keys move between slides and a
|
|
18
|
+
* slide that contains a link stays reachable. A carousel that hijacks the arrow
|
|
19
|
+
* keys or hides its off-screen slides from the tab order is the standard
|
|
20
|
+
* accessibility failure of this component, and both are avoided here by not
|
|
21
|
+
* disabling anything: the slides stay in the DOM and in the tab order, and the
|
|
22
|
+
* container scrolls.
|
|
23
|
+
*
|
|
24
|
+
* `orientation="vertical"` exists because embla supports it and a vertical
|
|
25
|
+
* carousel is what a short list of cards becomes on a phone. It is a prop rather
|
|
26
|
+
* than a second component because every part — the track, the previous/next
|
|
27
|
+
* buttons, the dots — has the same relationship to the axis.
|
|
28
|
+
*
|
|
29
|
+
* The dot indicators are buttons, not spans: a carousel with twelve slides and a
|
|
30
|
+
* "go to slide 7" affordance is the only way to reach the seventh without
|
|
31
|
+
* dragging through six. Each is labelled, because a row of unlabelled dots is a
|
|
32
|
+
* row of identical controls to a screen reader.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
export type CarouselApi = EmblaCarouselType
|
|
36
|
+
|
|
37
|
+
export type CarouselProps = {
|
|
38
|
+
/** The slides. Each is wrapped in its own element so embla can measure it. */
|
|
39
|
+
children: JSX.Element
|
|
40
|
+
/** The axis. Vertical is what a card list becomes on a narrow screen. */
|
|
41
|
+
orientation?: 'horizontal' | 'vertical'
|
|
42
|
+
/** How many slides are visible at once. */
|
|
43
|
+
slidesToShow?: number
|
|
44
|
+
/** Align the active slide to the start, centre, or end of the viewport. */
|
|
45
|
+
align?: 'start' | 'center' | 'end'
|
|
46
|
+
/** Whether dragging past the ends loops. Off by default: a loop hides the ends. */
|
|
47
|
+
loop?: boolean
|
|
48
|
+
/** Show the previous/next controls. */
|
|
49
|
+
controls?: boolean
|
|
50
|
+
/** Show the dot indicators. Off by default — a single-slide carousel needs none. */
|
|
51
|
+
dots?: boolean
|
|
52
|
+
/** Accessible names. Override for a non-English application. */
|
|
53
|
+
previousLabel?: string
|
|
54
|
+
nextLabel?: string
|
|
55
|
+
/** The label for a dot, given a zero-based index. */
|
|
56
|
+
dotLabel?: (index: number) => string
|
|
57
|
+
/** Receives the embla API, for a caller that needs to drive it. */
|
|
58
|
+
onApi?: (api: CarouselApi) => void
|
|
59
|
+
class?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function Carousel(props: CarouselProps) {
|
|
63
|
+
const [local, rest] = splitProps(props, [
|
|
64
|
+
'children',
|
|
65
|
+
'orientation',
|
|
66
|
+
'slidesToShow',
|
|
67
|
+
'align',
|
|
68
|
+
'loop',
|
|
69
|
+
'controls',
|
|
70
|
+
'dots',
|
|
71
|
+
'previousLabel',
|
|
72
|
+
'nextLabel',
|
|
73
|
+
'dotLabel',
|
|
74
|
+
'onApi',
|
|
75
|
+
'class',
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
const orientation = () => local.orientation ?? 'horizontal'
|
|
79
|
+
|
|
80
|
+
// The first element of the tuple is the ref setter, the second the accessor —
|
|
81
|
+
// so `ref={emblaRef}` is the whole wiring.
|
|
82
|
+
const [emblaRef, embla] = createEmblaCarousel(() => ({
|
|
83
|
+
axis: orientation() === 'vertical' ? 'y' : 'x',
|
|
84
|
+
loop: local.loop ?? false,
|
|
85
|
+
align: local.align ?? 'start',
|
|
86
|
+
slidesToScroll: 1,
|
|
87
|
+
containScroll: 'trimSnaps',
|
|
88
|
+
}))
|
|
89
|
+
|
|
90
|
+
const [selected, setSelected] = createSignal(0)
|
|
91
|
+
const [count, setCount] = createSignal(0)
|
|
92
|
+
const [canPrev, setCanPrev] = createSignal(false)
|
|
93
|
+
const [canNext, setCanNext] = createSignal(false)
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The scroll event, not the select event alone: `select` fires on a settle, so a
|
|
97
|
+
* drag in progress would leave the dots behind the thumb. `scroll` covers both.
|
|
98
|
+
*/
|
|
99
|
+
createEffect(() => {
|
|
100
|
+
const api = embla()
|
|
101
|
+
if (!api) return
|
|
102
|
+
local.onApi?.(api)
|
|
103
|
+
|
|
104
|
+
const sync = () => {
|
|
105
|
+
setSelected(api.selectedScrollSnap())
|
|
106
|
+
setCanPrev(api.canScrollPrev())
|
|
107
|
+
setCanNext(api.canScrollNext())
|
|
108
|
+
}
|
|
109
|
+
sync()
|
|
110
|
+
setCount(api.scrollSnapList().length)
|
|
111
|
+
|
|
112
|
+
api.on('select', sync)
|
|
113
|
+
api.on('scroll', sync)
|
|
114
|
+
api.on('reInit', sync)
|
|
115
|
+
onCleanup(() => {
|
|
116
|
+
api.off('select', sync)
|
|
117
|
+
api.off('scroll', sync)
|
|
118
|
+
api.off('reInit', sync)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div
|
|
124
|
+
class={cn('group/carousel relative grid gap-2', local.class)}
|
|
125
|
+
role="group"
|
|
126
|
+
aria-roledescription="carousel"
|
|
127
|
+
{...rest}
|
|
128
|
+
>
|
|
129
|
+
<div
|
|
130
|
+
ref={emblaRef}
|
|
131
|
+
// A scroll container has to be reachable by keyboard — the same
|
|
132
|
+
// requirement the code block's scroller meets.
|
|
133
|
+
tabindex="0"
|
|
134
|
+
class={cn(
|
|
135
|
+
'scroll-fade overflow-hidden rounded-lg focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none'
|
|
136
|
+
)}
|
|
137
|
+
aria-label="Carousel"
|
|
138
|
+
>
|
|
139
|
+
<div class={cn('flex', orientation() === 'vertical' ? 'flex-col' : 'flex-row')}>
|
|
140
|
+
{local.children}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
<Show when={local.controls ?? true}>
|
|
145
|
+
<div class="flex items-center justify-center gap-1">
|
|
146
|
+
<Button
|
|
147
|
+
variant="ghost"
|
|
148
|
+
size="icon-sm"
|
|
149
|
+
disabled={!canPrev()}
|
|
150
|
+
aria-label={local.previousLabel ?? 'Previous slide'}
|
|
151
|
+
onClick={() => embla()?.scrollPrev()}
|
|
152
|
+
>
|
|
153
|
+
<ChevronLeft aria-hidden="true" class="rotate-0" />
|
|
154
|
+
</Button>
|
|
155
|
+
<Show when={local.dots}>
|
|
156
|
+
<div class="flex items-center gap-1 px-1">
|
|
157
|
+
<For each={Array.from({ length: count() })}>
|
|
158
|
+
{(_, index) => (
|
|
159
|
+
<button
|
|
160
|
+
type="button"
|
|
161
|
+
class={cn(
|
|
162
|
+
'size-1.5 rounded-full bg-muted-foreground/40 transition-colors hover:bg-muted-foreground',
|
|
163
|
+
selected() === index() && 'bg-primary'
|
|
164
|
+
)}
|
|
165
|
+
aria-label={local.dotLabel?.(index()) ?? `Go to slide ${index() + 1}`}
|
|
166
|
+
aria-current={selected() === index() ? 'true' : undefined}
|
|
167
|
+
onClick={() => embla()?.scrollTo(index())}
|
|
168
|
+
/>
|
|
169
|
+
)}
|
|
170
|
+
</For>
|
|
171
|
+
</div>
|
|
172
|
+
</Show>
|
|
173
|
+
<Button
|
|
174
|
+
variant="ghost"
|
|
175
|
+
size="icon-sm"
|
|
176
|
+
disabled={!canNext()}
|
|
177
|
+
aria-label={local.nextLabel ?? 'Next slide'}
|
|
178
|
+
onClick={() => embla()?.scrollNext()}
|
|
179
|
+
>
|
|
180
|
+
<ChevronRight aria-hidden="true" />
|
|
181
|
+
</Button>
|
|
182
|
+
</div>
|
|
183
|
+
</Show>
|
|
184
|
+
</div>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* One slide. `basis` is what makes `slidesToShow` work: embla measures the slide
|
|
190
|
+
* elements, so a slide that is not sized to a fraction of the viewport would show
|
|
191
|
+
* one per screen regardless of the setting.
|
|
192
|
+
*/
|
|
193
|
+
export function CarouselSlide(props: {
|
|
194
|
+
children: JSX.Element
|
|
195
|
+
/** How many slides share the viewport at this slide's widest breakpoint. */
|
|
196
|
+
slidesToShow?: number
|
|
197
|
+
class?: string
|
|
198
|
+
}) {
|
|
199
|
+
return (
|
|
200
|
+
<div
|
|
201
|
+
class={cn('min-w-0 shrink-0 grow-0', props.class)}
|
|
202
|
+
style={{ 'flex-basis': `${100 / (props.slidesToShow ?? 1)}%` }}
|
|
203
|
+
role="group"
|
|
204
|
+
aria-roledescription="slide"
|
|
205
|
+
>
|
|
206
|
+
{props.children}
|
|
207
|
+
</div>
|
|
208
|
+
)
|
|
209
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Carousel, CarouselSlide, type CarouselApi, type CarouselProps } from './carousel'
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { For, splitProps, type JSX } from 'solid-js'
|
|
2
|
+
import { Dynamic } from 'solid-js/web'
|
|
3
|
+
import { Bar, Bubble, Doughnut, Line, Pie, PolarArea, Radar, Scatter } from 'solid-chartjs'
|
|
4
|
+
import {
|
|
5
|
+
Chart as ChartJs,
|
|
6
|
+
ArcElement,
|
|
7
|
+
BarElement,
|
|
8
|
+
CategoryScale,
|
|
9
|
+
Filler,
|
|
10
|
+
Legend,
|
|
11
|
+
LineElement,
|
|
12
|
+
LinearScale,
|
|
13
|
+
LogarithmicScale,
|
|
14
|
+
PointElement,
|
|
15
|
+
RadialLinearScale,
|
|
16
|
+
Tooltip as ChartTooltip,
|
|
17
|
+
} from 'chart.js'
|
|
18
|
+
import type { ChartOptions, ChartType } from 'chart.js'
|
|
19
|
+
import { cn } from '../../../lib/utils'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Chart.
|
|
23
|
+
*
|
|
24
|
+
* A thin wrapper over Chart.js, themed through the same CSS custom properties as
|
|
25
|
+
* everything else. The wrapper is thin on purpose: a design system's job here is
|
|
26
|
+
* to make a chart *look like the rest of the application* — the palette, the
|
|
27
|
+
* type scale, the tooltip surface, the grid lines — and not to re-implement
|
|
28
|
+
* scales and axes, which is where a hand-rolled chart goes wrong.
|
|
29
|
+
*
|
|
30
|
+
* ## Registration, and why it is here rather than left to the caller
|
|
31
|
+
*
|
|
32
|
+
* Chart.js is tree-shakeable through explicit registration: nothing is bundled
|
|
33
|
+
* until a controller, a scale or a plugin is registered. That is the whole reason
|
|
34
|
+
* it was chosen over a batteries-included charting library, and it only pays off
|
|
35
|
+
* if the registrations are scoped to what a component actually uses.
|
|
36
|
+
*
|
|
37
|
+
* This module registers the **shared** pieces once — scales, elements, tooltip,
|
|
38
|
+
* legend, filler — and nothing else. The controllers are imported per component
|
|
39
|
+
* so a `LineChart` does not carry the radar controller, and a consumer that
|
|
40
|
+
* imports only `LineChart` gets only that. Measured: see the story for the
|
|
41
|
+
* per-component figures the tree-shaking gate publishes.
|
|
42
|
+
*
|
|
43
|
+
* A caller with a chart type this file does not export registers its own
|
|
44
|
+
* controller with `registerChartPiece`, which is the escape hatch rather than a
|
|
45
|
+
* reason to widen the registration above.
|
|
46
|
+
*
|
|
47
|
+
* ## Theming
|
|
48
|
+
*
|
|
49
|
+
* Colours resolve from `--chart-1` through `--chart-5`, which every theme
|
|
50
|
+
* defines, plus `--chart-grid`, `--chart-axis` and `--chart-label`. That means a
|
|
51
|
+
* chart follows the accent axis, the appearance, and the font axis with no
|
|
52
|
+
* per-chart configuration — and a caller that wants a specific series colour
|
|
53
|
+
* passes it explicitly rather than reaching into the theme.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Register a piece with Chart.js. The escape hatch for a caller's own chart type.
|
|
58
|
+
*
|
|
59
|
+
* `solid-chartjs` re-exports the typed components but not the registry, so
|
|
60
|
+
* registration comes from `chart.js` itself — which is also the honest place for
|
|
61
|
+
* it, since the registry is Chart.js's global.
|
|
62
|
+
*/
|
|
63
|
+
export function registerChartPiece(piece: Parameters<typeof ChartJs.register>[number]): void {
|
|
64
|
+
ChartJs.register(piece)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
ChartJs.register(
|
|
68
|
+
CategoryScale,
|
|
69
|
+
LinearScale,
|
|
70
|
+
LogarithmicScale,
|
|
71
|
+
RadialLinearScale,
|
|
72
|
+
PointElement,
|
|
73
|
+
LineElement,
|
|
74
|
+
BarElement,
|
|
75
|
+
ArcElement,
|
|
76
|
+
Filler,
|
|
77
|
+
ChartTooltip,
|
|
78
|
+
Legend
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
/** The five series colours, in the order a multi-series chart should use them. */
|
|
82
|
+
export const chartSeriesColors = [
|
|
83
|
+
'var(--chart-1)',
|
|
84
|
+
'var(--chart-2)',
|
|
85
|
+
'var(--chart-3)',
|
|
86
|
+
'var(--chart-4)',
|
|
87
|
+
'var(--chart-5)',
|
|
88
|
+
] as const
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The theme's defaults as Chart.js options.
|
|
92
|
+
*
|
|
93
|
+
* Exported so a caller can spread it and change one thing, rather than
|
|
94
|
+
* restating every colour. Everything a chart draws that is not data — grid,
|
|
95
|
+
* ticks, legend, tooltip — is decided here once, which is what makes two charts
|
|
96
|
+
* in one application look like they came from the same place.
|
|
97
|
+
*/
|
|
98
|
+
export function chartOptions(overrides?: ChartOptions): ChartOptions {
|
|
99
|
+
return {
|
|
100
|
+
responsive: true,
|
|
101
|
+
maintainAspectRatio: false,
|
|
102
|
+
interaction: { mode: 'nearest', intersect: false },
|
|
103
|
+
plugins: {
|
|
104
|
+
legend: {
|
|
105
|
+
labels: {
|
|
106
|
+
color: 'var(--chart-label)',
|
|
107
|
+
boxWidth: 8,
|
|
108
|
+
boxHeight: 8,
|
|
109
|
+
usePointStyle: true,
|
|
110
|
+
pointStyle: 'circle',
|
|
111
|
+
font: { size: 11 },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
tooltip: {
|
|
115
|
+
backgroundColor: 'var(--popover)',
|
|
116
|
+
titleColor: 'var(--popover-foreground)',
|
|
117
|
+
bodyColor: 'var(--popover-foreground)',
|
|
118
|
+
borderColor: 'var(--border)',
|
|
119
|
+
borderWidth: 1,
|
|
120
|
+
padding: 8,
|
|
121
|
+
cornerRadius: 6,
|
|
122
|
+
displayColors: true,
|
|
123
|
+
boxWidth: 8,
|
|
124
|
+
boxHeight: 8,
|
|
125
|
+
usePointStyle: true,
|
|
126
|
+
titleFont: { size: 11, weight: 600 },
|
|
127
|
+
bodyFont: { size: 11 },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
scales: {
|
|
131
|
+
x: {
|
|
132
|
+
border: { color: 'var(--chart-grid)' },
|
|
133
|
+
grid: { color: 'var(--chart-grid)', drawTicks: false },
|
|
134
|
+
ticks: { color: 'var(--chart-axis)', font: { size: 10 } },
|
|
135
|
+
},
|
|
136
|
+
y: {
|
|
137
|
+
border: { display: false },
|
|
138
|
+
grid: { color: 'var(--chart-grid)', drawTicks: false },
|
|
139
|
+
ticks: { color: 'var(--chart-axis)', font: { size: 10 } },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
...overrides,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** A chart's frame: a bordered surface with a title and an optional description. */
|
|
147
|
+
export type ChartFrameProps = {
|
|
148
|
+
/** The chart's title. Required — a chart with no name is not readable. */
|
|
149
|
+
title: string
|
|
150
|
+
/** What the chart shows. Announced to assistive technology, and drawn. */
|
|
151
|
+
description?: string
|
|
152
|
+
/** Trailing controls. */
|
|
153
|
+
actions?: JSX.Element
|
|
154
|
+
children: JSX.Element
|
|
155
|
+
class?: string
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function ChartFrame(props: ChartFrameProps) {
|
|
159
|
+
const [local] = splitProps(props, ['title', 'description', 'actions', 'children', 'class'])
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<figure class={cn('grid gap-3 rounded-lg border border-border bg-card p-4', local.class)}>
|
|
163
|
+
<figcaption class="flex items-start justify-between gap-3">
|
|
164
|
+
<div>
|
|
165
|
+
<h3 class="text-sm font-semibold text-foreground">{local.title}</h3>
|
|
166
|
+
{local.description}
|
|
167
|
+
</div>
|
|
168
|
+
{local.actions}
|
|
169
|
+
</figcaption>
|
|
170
|
+
{local.children}
|
|
171
|
+
</figure>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
type SeriesChartProps = {
|
|
176
|
+
/** The category labels along the axis. */
|
|
177
|
+
labels: readonly string[]
|
|
178
|
+
/** One entry per series. `data` is aligned to `labels`. */
|
|
179
|
+
series: readonly { label: string; data: readonly number[]; color?: string }[]
|
|
180
|
+
/** Chart.js options, merged over the theme's defaults. */
|
|
181
|
+
options?: ChartOptions
|
|
182
|
+
/** Stack the series. */
|
|
183
|
+
stacked?: boolean
|
|
184
|
+
class?: string
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The data as Chart.js datasets, with the theme's colours applied in order and
|
|
189
|
+
* the series' own colour winning when it has one.
|
|
190
|
+
*
|
|
191
|
+
* `fill: false` and a 2px border are deliberate: a filled line chart is a
|
|
192
|
+
* different chart, and a 1px line at a 12px label size reads as a grid line.
|
|
193
|
+
*/
|
|
194
|
+
function datasetsFor(series: SeriesChartProps['series'], filled: boolean) {
|
|
195
|
+
return series.map((entry, index) => ({
|
|
196
|
+
label: entry.label,
|
|
197
|
+
data: [...entry.data],
|
|
198
|
+
borderColor: entry.color ?? chartSeriesColors[index % chartSeriesColors.length],
|
|
199
|
+
backgroundColor: entry.color ?? chartSeriesColors[index % chartSeriesColors.length],
|
|
200
|
+
pointBackgroundColor: entry.color ?? chartSeriesColors[index % chartSeriesColors.length],
|
|
201
|
+
borderWidth: 2,
|
|
202
|
+
pointRadius: 0,
|
|
203
|
+
pointHoverRadius: 4,
|
|
204
|
+
tension: 0.3,
|
|
205
|
+
fill: filled,
|
|
206
|
+
}))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function seriesOptions(props: SeriesChartProps, extra?: ChartOptions): ChartOptions {
|
|
210
|
+
return chartOptions({
|
|
211
|
+
...extra,
|
|
212
|
+
...(props.stacked
|
|
213
|
+
? { scales: { ...extra?.scales, x: { stacked: true }, y: { stacked: true } } }
|
|
214
|
+
: {}),
|
|
215
|
+
...props.options,
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** A line chart. The default for a series over time. */
|
|
220
|
+
export function LineChart(props: SeriesChartProps & { area?: boolean }) {
|
|
221
|
+
return (
|
|
222
|
+
<div class={cn('h-64', props.class)}>
|
|
223
|
+
<Line
|
|
224
|
+
data={{
|
|
225
|
+
labels: [...props.labels],
|
|
226
|
+
datasets: datasetsFor(props.series, props.area ?? false),
|
|
227
|
+
}}
|
|
228
|
+
options={seriesOptions(props)}
|
|
229
|
+
/>
|
|
230
|
+
</div>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** A bar chart. `stacked` for parts of a whole over categories. */
|
|
235
|
+
export function BarChart(props: SeriesChartProps & { horizontal?: boolean }) {
|
|
236
|
+
return (
|
|
237
|
+
<div class={cn('h-64', props.class)}>
|
|
238
|
+
<Bar
|
|
239
|
+
data={{
|
|
240
|
+
labels: [...props.labels],
|
|
241
|
+
datasets: datasetsFor(props.series, true).map((dataset) => ({
|
|
242
|
+
...dataset,
|
|
243
|
+
borderWidth: 0,
|
|
244
|
+
borderRadius: 3,
|
|
245
|
+
})),
|
|
246
|
+
}}
|
|
247
|
+
options={seriesOptions(props, { indexAxis: props.horizontal ? 'y' : 'x' })}
|
|
248
|
+
/>
|
|
249
|
+
</div>
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** A scatter plot, for a correlation rather than a series. */
|
|
254
|
+
export function ScatterChart(props: {
|
|
255
|
+
series: readonly { label: string; data: readonly { x: number; y: number }[]; color?: string }[]
|
|
256
|
+
options?: ChartOptions
|
|
257
|
+
class?: string
|
|
258
|
+
}) {
|
|
259
|
+
return (
|
|
260
|
+
<div class={cn('h-64', props.class)}>
|
|
261
|
+
<Scatter
|
|
262
|
+
data={{
|
|
263
|
+
datasets: props.series.map((entry, index) => ({
|
|
264
|
+
label: entry.label,
|
|
265
|
+
data: entry.data.map((point) => ({ ...point })),
|
|
266
|
+
backgroundColor: entry.color ?? chartSeriesColors[index % chartSeriesColors.length],
|
|
267
|
+
pointRadius: 3,
|
|
268
|
+
pointHoverRadius: 5,
|
|
269
|
+
})),
|
|
270
|
+
}}
|
|
271
|
+
options={chartOptions(props.options)}
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** A bubble chart: scatter with a third dimension carried by the radius. */
|
|
278
|
+
export function BubbleChart(props: {
|
|
279
|
+
series: readonly {
|
|
280
|
+
label: string
|
|
281
|
+
data: readonly { x: number; y: number; r: number }[]
|
|
282
|
+
color?: string
|
|
283
|
+
}[]
|
|
284
|
+
options?: ChartOptions
|
|
285
|
+
class?: string
|
|
286
|
+
}) {
|
|
287
|
+
return (
|
|
288
|
+
<div class={cn('h-64', props.class)}>
|
|
289
|
+
<Bubble
|
|
290
|
+
data={{
|
|
291
|
+
datasets: props.series.map((entry, index) => ({
|
|
292
|
+
label: entry.label,
|
|
293
|
+
data: entry.data.map((point) => ({ ...point })),
|
|
294
|
+
backgroundColor: entry.color ?? chartSeriesColors[index % chartSeriesColors.length],
|
|
295
|
+
})),
|
|
296
|
+
}}
|
|
297
|
+
options={chartOptions(props.options)}
|
|
298
|
+
/>
|
|
299
|
+
</div>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** A radar chart, for a profile across fixed axes. */
|
|
304
|
+
export function RadarChart(props: SeriesChartProps) {
|
|
305
|
+
return (
|
|
306
|
+
<div class={cn('h-64', props.class)}>
|
|
307
|
+
<Radar
|
|
308
|
+
data={{ labels: [...props.labels], datasets: datasetsFor(props.series, true) }}
|
|
309
|
+
options={seriesOptions(props, { scales: { r: { grid: { color: 'var(--chart-grid)' } } } })}
|
|
310
|
+
/>
|
|
311
|
+
</div>
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** A polar area chart: radial bars, for a composition. */
|
|
316
|
+
export function PolarAreaChart(props: {
|
|
317
|
+
labels: readonly string[]
|
|
318
|
+
data: readonly number[]
|
|
319
|
+
options?: ChartOptions
|
|
320
|
+
class?: string
|
|
321
|
+
}) {
|
|
322
|
+
return (
|
|
323
|
+
<div class={cn('h-64', props.class)}>
|
|
324
|
+
<PolarArea
|
|
325
|
+
data={{
|
|
326
|
+
labels: [...props.labels],
|
|
327
|
+
datasets: [
|
|
328
|
+
{
|
|
329
|
+
data: [...props.data],
|
|
330
|
+
backgroundColor: [...chartSeriesColors],
|
|
331
|
+
borderColor: 'var(--card)',
|
|
332
|
+
borderWidth: 2,
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
}}
|
|
336
|
+
options={chartOptions(props.options)}
|
|
337
|
+
/>
|
|
338
|
+
</div>
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* A doughnut or a pie, for a composition with few enough slices to name.
|
|
344
|
+
*
|
|
345
|
+
* Both are one component because the only difference is the hole, and a caller
|
|
346
|
+
* choosing between them is choosing a ratio rather than a chart. `cutout` is the
|
|
347
|
+
* hole's share of the radius: 0 is a pie, 60% is the doughnut most dashboards
|
|
348
|
+
* mean. A composition with more than about six slices should be a bar chart —
|
|
349
|
+
* past that, the smallest slices become unreadable and their labels collide.
|
|
350
|
+
*/
|
|
351
|
+
export function DonutChart(props: {
|
|
352
|
+
labels: readonly string[]
|
|
353
|
+
data: readonly number[]
|
|
354
|
+
/** The hole, as a percentage of the radius. 0 for a pie. */
|
|
355
|
+
cutout?: number
|
|
356
|
+
options?: ChartOptions
|
|
357
|
+
class?: string
|
|
358
|
+
}) {
|
|
359
|
+
// `Dynamic` rather than a local component accessor: Solid's JSX types require
|
|
360
|
+
// a component reference, and an accessor returning one is a call in disguise.
|
|
361
|
+
const component = () => (props.cutout === 0 ? Pie : Doughnut)
|
|
362
|
+
return (
|
|
363
|
+
<div class={cn('h-64', props.class)}>
|
|
364
|
+
<Dynamic
|
|
365
|
+
component={component()}
|
|
366
|
+
data={{
|
|
367
|
+
labels: [...props.labels],
|
|
368
|
+
datasets: [
|
|
369
|
+
{
|
|
370
|
+
data: [...props.data],
|
|
371
|
+
backgroundColor: props.labels.map(
|
|
372
|
+
(_, index) => chartSeriesColors[index % chartSeriesColors.length]
|
|
373
|
+
),
|
|
374
|
+
borderColor: 'var(--card)',
|
|
375
|
+
borderWidth: 2,
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
}}
|
|
379
|
+
options={chartOptions({
|
|
380
|
+
...props.options,
|
|
381
|
+
...(props.cutout === 0 ? {} : { cutout: `${props.cutout ?? 60}%` }),
|
|
382
|
+
})}
|
|
383
|
+
/>
|
|
384
|
+
</div>
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** A legend built from the same five colours, for a chart whose legend is elsewhere. */
|
|
389
|
+
export function ChartLegend(props: {
|
|
390
|
+
series: readonly { label: string; color?: string }[]
|
|
391
|
+
class?: string
|
|
392
|
+
}) {
|
|
393
|
+
return (
|
|
394
|
+
<ul class={cn('flex flex-wrap items-center gap-x-3 gap-y-1', props.class)}>
|
|
395
|
+
<For each={props.series}>
|
|
396
|
+
{(entry, index) => (
|
|
397
|
+
<li class="flex items-center gap-1.5 text-2xs text-muted-foreground">
|
|
398
|
+
<span
|
|
399
|
+
class="size-2 shrink-0 rounded-full"
|
|
400
|
+
style={{
|
|
401
|
+
background: entry.color ?? chartSeriesColors[index() % chartSeriesColors.length],
|
|
402
|
+
}}
|
|
403
|
+
aria-hidden="true"
|
|
404
|
+
/>
|
|
405
|
+
{entry.label}
|
|
406
|
+
</li>
|
|
407
|
+
)}
|
|
408
|
+
</For>
|
|
409
|
+
</ul>
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export type { ChartType, ChartOptions }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
BarChart,
|
|
3
|
+
BubbleChart,
|
|
4
|
+
ChartFrame,
|
|
5
|
+
ChartLegend,
|
|
6
|
+
DonutChart,
|
|
7
|
+
LineChart,
|
|
8
|
+
PolarAreaChart,
|
|
9
|
+
RadarChart,
|
|
10
|
+
ScatterChart,
|
|
11
|
+
chartOptions,
|
|
12
|
+
chartSeriesColors,
|
|
13
|
+
registerChartPiece,
|
|
14
|
+
type ChartFrameProps,
|
|
15
|
+
type ChartOptions,
|
|
16
|
+
type ChartType,
|
|
17
|
+
} from './chart'
|