@teamofsilicons/silicon-ui 0.0.0-stage → 0.1.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/LICENSE +22 -0
- package/README.md +63 -2
- package/components/accordion/accordion.d.ts +13 -0
- package/components/accordion/accordion.js +35 -0
- package/components/accordion/accordion.module.css +15 -0
- package/components/action-button/action-button.d.ts +11 -0
- package/components/action-button/action-button.js +57 -0
- package/components/action-button/action-button.module.css +15 -0
- package/components/activity-heatmap/activity-heatmap.d.ts +37 -0
- package/components/activity-heatmap/activity-heatmap.js +303 -0
- package/components/activity-heatmap/activity-heatmap.module.css +83 -0
- package/components/alert/alert.d.ts +13 -0
- package/components/alert/alert.js +79 -0
- package/components/alert/alert.module.css +15 -0
- package/components/animated-counter/animated-counter.d.ts +13 -0
- package/components/animated-counter/animated-counter.js +73 -0
- package/components/animated-counter/animated-counter.module.css +12 -0
- package/components/announcement-bar/announcement-bar.d.ts +53 -0
- package/components/announcement-bar/announcement-bar.js +187 -0
- package/components/announcement-bar/announcement-bar.module.css +60 -0
- package/components/avatar/avatar.d.ts +8 -0
- package/components/avatar/avatar.js +18 -0
- package/components/avatar/avatar.module.css +60 -0
- package/components/avatar-group/avatar-group.d.ts +13 -0
- package/components/avatar-group/avatar-group.js +24 -0
- package/components/avatar-group/avatar-group.module.css +27 -0
- package/components/badge/badge.d.ts +10 -0
- package/components/badge/badge.js +66 -0
- package/components/badge/badge.module.css +52 -0
- package/components/bar-chart/bar-chart.d.ts +32 -0
- package/components/bar-chart/bar-chart.js +327 -0
- package/components/bar-chart/bar-chart.module.css +49 -0
- package/components/billing-toggle/billing-toggle.d.ts +35 -0
- package/components/billing-toggle/billing-toggle.js +83 -0
- package/components/billing-toggle/billing-toggle.module.css +139 -0
- package/components/bottom-sheet/bottom-sheet.d.ts +27 -0
- package/components/bottom-sheet/bottom-sheet.js +386 -0
- package/components/bottom-sheet/bottom-sheet.module.css +69 -0
- package/components/breadcrumb/breadcrumb.d.ts +13 -0
- package/components/breadcrumb/breadcrumb.js +19 -0
- package/components/breadcrumb/breadcrumb.module.css +12 -0
- package/components/brush-chart/brush-chart.d.ts +45 -0
- package/components/brush-chart/brush-chart.js +426 -0
- package/components/brush-chart/brush-chart.module.css +69 -0
- package/components/button/button.d.ts +10 -0
- package/components/button/button.js +97 -0
- package/components/button/button.module.css +39 -0
- package/components/button-group/button-group.d.ts +59 -0
- package/components/button-group/button-group.js +280 -0
- package/components/button-group/button-group.module.css +87 -0
- package/components/button-link/button-link.d.ts +24 -0
- package/components/button-link/button-link.js +11 -0
- package/components/button-link/button-link.module.css +7 -0
- package/components/calendar/calendar.d.ts +22 -0
- package/components/calendar/calendar.js +253 -0
- package/components/calendar/calendar.module.css +51 -0
- package/components/card/card.d.ts +19 -0
- package/components/card/card.js +108 -0
- package/components/card/card.module.css +51 -0
- package/components/card-stack/card-stack.d.ts +34 -0
- package/components/card-stack/card-stack.js +291 -0
- package/components/card-stack/card-stack.module.css +63 -0
- package/components/carousel/carousel.d.ts +28 -0
- package/components/carousel/carousel.js +332 -0
- package/components/carousel/carousel.module.css +42 -0
- package/components/chat-thread/chat-thread.d.ts +135 -0
- package/components/chat-thread/chat-thread.js +406 -0
- package/components/chat-thread/chat-thread.module.css +242 -0
- package/components/checkbox/checkbox.d.ts +7 -0
- package/components/checkbox/checkbox.js +23 -0
- package/components/checkbox/checkbox.module.css +15 -0
- package/components/chip-group/chip-group.d.ts +24 -0
- package/components/chip-group/chip-group.js +196 -0
- package/components/chip-group/chip-group.module.css +38 -0
- package/components/code-block/code-block.d.ts +11 -0
- package/components/code-block/code-block.js +133 -0
- package/components/code-block/code-block.module.css +43 -0
- package/components/color-picker/color-picker.d.ts +40 -0
- package/components/color-picker/color-picker.js +449 -0
- package/components/color-picker/color-picker.module.css +111 -0
- package/components/combobox/combobox.d.ts +19 -0
- package/components/combobox/combobox.js +167 -0
- package/components/combobox/combobox.module.css +28 -0
- package/components/command-palette/command-palette.d.ts +20 -0
- package/components/command-palette/command-palette.js +151 -0
- package/components/command-palette/command-palette.module.css +94 -0
- package/components/comment-thread/comment-thread.d.ts +120 -0
- package/components/comment-thread/comment-thread.js +343 -0
- package/components/comment-thread/comment-thread.module.css +195 -0
- package/components/confirm-morph/confirm-morph.d.ts +65 -0
- package/components/confirm-morph/confirm-morph.js +355 -0
- package/components/confirm-morph/confirm-morph.module.css +146 -0
- package/components/context-menu/context-menu.d.ts +18 -0
- package/components/context-menu/context-menu.js +148 -0
- package/components/context-menu/context-menu.module.css +17 -0
- package/components/copy-button/copy-button.d.ts +10 -0
- package/components/copy-button/copy-button.js +69 -0
- package/components/copy-button/copy-button.module.css +21 -0
- package/components/countdown/countdown.d.ts +31 -0
- package/components/countdown/countdown.js +177 -0
- package/components/countdown/countdown.module.css +49 -0
- package/components/date-picker/date-picker.d.ts +17 -0
- package/components/date-picker/date-picker.js +100 -0
- package/components/date-picker/date-picker.module.css +34 -0
- package/components/date-range-picker/date-range-picker.d.ts +44 -0
- package/components/date-range-picker/date-range-picker.js +534 -0
- package/components/date-range-picker/date-range-picker.module.css +214 -0
- package/components/dialog/dialog.d.ts +11 -0
- package/components/dialog/dialog.js +52 -0
- package/components/dialog/dialog.module.css +36 -0
- package/components/donut-chart/donut-chart.d.ts +46 -0
- package/components/donut-chart/donut-chart.js +382 -0
- package/components/donut-chart/donut-chart.module.css +48 -0
- package/components/drawer/drawer.d.ts +20 -0
- package/components/drawer/drawer.js +111 -0
- package/components/drawer/drawer.module.css +97 -0
- package/components/dropdown-menu/dropdown-menu.d.ts +16 -0
- package/components/dropdown-menu/dropdown-menu.js +39 -0
- package/components/dropdown-menu/dropdown-menu.module.css +38 -0
- package/components/elastic-slider/elastic-slider.d.ts +39 -0
- package/components/elastic-slider/elastic-slider.js +261 -0
- package/components/elastic-slider/elastic-slider.module.css +40 -0
- package/components/empty-state/empty-state.d.ts +12 -0
- package/components/empty-state/empty-state.js +69 -0
- package/components/empty-state/empty-state.module.css +13 -0
- package/components/expandable-card/expandable-card.d.ts +13 -0
- package/components/expandable-card/expandable-card.js +89 -0
- package/components/expandable-card/expandable-card.module.css +22 -0
- package/components/expanding-button-group/expanding-button-group.d.ts +41 -0
- package/components/expanding-button-group/expanding-button-group.js +258 -0
- package/components/expanding-button-group/expanding-button-group.module.css +44 -0
- package/components/expanding-search/expanding-search.d.ts +38 -0
- package/components/expanding-search/expanding-search.js +242 -0
- package/components/expanding-search/expanding-search.module.css +52 -0
- package/components/file-dropzone/file-dropzone.d.ts +44 -0
- package/components/file-dropzone/file-dropzone.js +379 -0
- package/components/file-dropzone/file-dropzone.module.css +115 -0
- package/components/file-upload/file-upload.d.ts +22 -0
- package/components/file-upload/file-upload.js +192 -0
- package/components/file-upload/file-upload.module.css +49 -0
- package/components/filter-toolbar/filter-toolbar.d.ts +48 -0
- package/components/filter-toolbar/filter-toolbar.js +446 -0
- package/components/filter-toolbar/filter-toolbar.module.css +70 -0
- package/components/floating-button-group/floating-button-group.d.ts +51 -0
- package/components/floating-button-group/floating-button-group.js +215 -0
- package/components/floating-button-group/floating-button-group.module.css +74 -0
- package/components/gauge/gauge.d.ts +18 -0
- package/components/gauge/gauge.js +101 -0
- package/components/gauge/gauge.module.css +22 -0
- package/components/hold-to-confirm/hold-to-confirm.d.ts +25 -0
- package/components/hold-to-confirm/hold-to-confirm.js +165 -0
- package/components/hold-to-confirm/hold-to-confirm.module.css +20 -0
- package/components/hover-card/hover-card.d.ts +38 -0
- package/components/hover-card/hover-card.js +145 -0
- package/components/hover-card/hover-card.module.css +20 -0
- package/components/image-compare/image-compare.d.ts +27 -0
- package/components/image-compare/image-compare.js +267 -0
- package/components/image-compare/image-compare.module.css +25 -0
- package/components/in-view-title/in-view-title.d.ts +17 -0
- package/components/in-view-title/in-view-title.js +70 -0
- package/components/in-view-title/in-view-title.module.css +13 -0
- package/components/inline-edit/inline-edit.d.ts +27 -0
- package/components/inline-edit/inline-edit.js +315 -0
- package/components/inline-edit/inline-edit.module.css +73 -0
- package/components/input/input.d.ts +7 -0
- package/components/input/input.js +60 -0
- package/components/input/input.module.css +22 -0
- package/components/json-viewer/json-viewer.d.ts +48 -0
- package/components/json-viewer/json-viewer.js +463 -0
- package/components/json-viewer/json-viewer.module.css +92 -0
- package/components/line-chart/line-chart.d.ts +58 -0
- package/components/line-chart/line-chart.js +383 -0
- package/components/line-chart/line-chart.module.css +60 -0
- package/components/mention-input/mention-input.d.ts +72 -0
- package/components/mention-input/mention-input.js +417 -0
- package/components/mention-input/mention-input.module.css +134 -0
- package/components/metric-card/metric-card.d.ts +8 -0
- package/components/metric-card/metric-card.js +44 -0
- package/components/metric-card/metric-card.module.css +18 -0
- package/components/money-input/money-input.d.ts +48 -0
- package/components/money-input/money-input.js +503 -0
- package/components/money-input/money-input.module.css +77 -0
- package/components/morph-select/morph-select.d.ts +50 -0
- package/components/morph-select/morph-select.js +395 -0
- package/components/morph-select/morph-select.module.css +88 -0
- package/components/multi-select/multi-select.d.ts +18 -0
- package/components/multi-select/multi-select.js +105 -0
- package/components/multi-select/multi-select.module.css +37 -0
- package/components/notification-center/notification-center.d.ts +24 -0
- package/components/notification-center/notification-center.js +146 -0
- package/components/notification-center/notification-center.module.css +79 -0
- package/components/number-field/number-field.d.ts +41 -0
- package/components/number-field/number-field.js +455 -0
- package/components/number-field/number-field.module.css +71 -0
- package/components/otp-input/otp-input.d.ts +13 -0
- package/components/otp-input/otp-input.js +129 -0
- package/components/otp-input/otp-input.module.css +28 -0
- package/components/pagination/pagination.d.ts +7 -0
- package/components/pagination/pagination.js +60 -0
- package/components/pagination/pagination.module.css +16 -0
- package/components/password-field/password-field.d.ts +14 -0
- package/components/password-field/password-field.js +51 -0
- package/components/password-field/password-field.module.css +27 -0
- package/components/password-strength/password-strength.d.ts +107 -0
- package/components/password-strength/password-strength.js +161 -0
- package/components/password-strength/password-strength.module.css +57 -0
- package/components/phone-input/phone-input.d.ts +73 -0
- package/components/phone-input/phone-input.js +612 -0
- package/components/phone-input/phone-input.module.css +98 -0
- package/components/popover/popover.d.ts +6 -0
- package/components/popover/popover.js +19 -0
- package/components/popover/popover.module.css +32 -0
- package/components/progress/progress.d.ts +8 -0
- package/components/progress/progress.js +42 -0
- package/components/progress/progress.module.css +1 -0
- package/components/radio-cards/radio-cards.d.ts +35 -0
- package/components/radio-cards/radio-cards.js +110 -0
- package/components/radio-cards/radio-cards.module.css +84 -0
- package/components/radio-group/radio-group.d.ts +13 -0
- package/components/radio-group/radio-group.js +55 -0
- package/components/radio-group/radio-group.module.css +30 -0
- package/components/resizable-panels/resizable-panels.d.ts +37 -0
- package/components/resizable-panels/resizable-panels.js +338 -0
- package/components/resizable-panels/resizable-panels.module.css +40 -0
- package/components/rich-text-editor/rich-text-editor.d.ts +55 -0
- package/components/rich-text-editor/rich-text-editor.js +1733 -0
- package/components/rich-text-editor/rich-text-editor.module.css +201 -0
- package/components/ridgeline/ridgeline.d.ts +37 -0
- package/components/ridgeline/ridgeline.js +346 -0
- package/components/ridgeline/ridgeline.module.css +58 -0
- package/components/scroll-area/scroll-area.d.ts +40 -0
- package/components/scroll-area/scroll-area.js +153 -0
- package/components/scroll-area/scroll-area.module.css +79 -0
- package/components/search-field/search-field.d.ts +7 -0
- package/components/search-field/search-field.js +20 -0
- package/components/search-field/search-field.module.css +17 -0
- package/components/segmented-control/segmented-control.d.ts +16 -0
- package/components/segmented-control/segmented-control.js +56 -0
- package/components/segmented-control/segmented-control.module.css +15 -0
- package/components/select/select.d.ts +15 -0
- package/components/select/select.js +36 -0
- package/components/select/select.module.css +35 -0
- package/components/shortcut-recorder/shortcut-recorder.d.ts +108 -0
- package/components/shortcut-recorder/shortcut-recorder.js +380 -0
- package/components/shortcut-recorder/shortcut-recorder.module.css +85 -0
- package/components/signature-pad/signature-pad.d.ts +72 -0
- package/components/signature-pad/signature-pad.js +450 -0
- package/components/signature-pad/signature-pad.module.css +84 -0
- package/components/skeleton/skeleton.d.ts +13 -0
- package/components/skeleton/skeleton.js +53 -0
- package/components/skeleton/skeleton.module.css +11 -0
- package/components/slider/slider.d.ts +45 -0
- package/components/slider/slider.js +347 -0
- package/components/slider/slider.module.css +45 -0
- package/components/slope-chart/slope-chart.d.ts +38 -0
- package/components/slope-chart/slope-chart.js +183 -0
- package/components/slope-chart/slope-chart.module.css +51 -0
- package/components/slot-text/slot-text.d.ts +26 -0
- package/components/slot-text/slot-text.js +119 -0
- package/components/slot-text/slot-text.module.css +19 -0
- package/components/sortable-data-table/sortable-data-table.d.ts +38 -0
- package/components/sortable-data-table/sortable-data-table.js +240 -0
- package/components/sortable-data-table/sortable-data-table.module.css +116 -0
- package/components/sparkline/sparkline.d.ts +19 -0
- package/components/sparkline/sparkline.js +239 -0
- package/components/sparkline/sparkline.module.css +26 -0
- package/components/split-button/split-button.d.ts +19 -0
- package/components/split-button/split-button.js +33 -0
- package/components/split-button/split-button.module.css +51 -0
- package/components/stepper/stepper.d.ts +36 -0
- package/components/stepper/stepper.js +108 -0
- package/components/stepper/stepper.module.css +74 -0
- package/components/streamgraph/streamgraph.d.ts +51 -0
- package/components/streamgraph/streamgraph.js +376 -0
- package/components/streamgraph/streamgraph.module.css +55 -0
- package/components/swipe-actions/swipe-actions.d.ts +35 -0
- package/components/swipe-actions/swipe-actions.js +330 -0
- package/components/swipe-actions/swipe-actions.module.css +64 -0
- package/components/switch/switch.d.ts +7 -0
- package/components/switch/switch.js +45 -0
- package/components/switch/switch.module.css +12 -0
- package/components/tabs/tabs.d.ts +8 -0
- package/components/tabs/tabs.js +138 -0
- package/components/tabs/tabs.module.css +27 -0
- package/components/tag-input/tag-input.d.ts +15 -0
- package/components/tag-input/tag-input.js +150 -0
- package/components/tag-input/tag-input.module.css +34 -0
- package/components/text-morph/text-morph.d.ts +13 -0
- package/components/text-morph/text-morph.js +69 -0
- package/components/text-morph/text-morph.module.css +8 -0
- package/components/text-reveal/text-reveal.d.ts +14 -0
- package/components/text-reveal/text-reveal.js +20 -0
- package/components/text-reveal/text-reveal.module.css +13 -0
- package/components/text-shimmer/text-shimmer.d.ts +18 -0
- package/components/text-shimmer/text-shimmer.js +90 -0
- package/components/text-shimmer/text-shimmer.module.css +9 -0
- package/components/textarea/textarea.d.ts +7 -0
- package/components/textarea/textarea.js +56 -0
- package/components/textarea/textarea.module.css +22 -0
- package/components/theme-switch/theme-switch.d.ts +11 -0
- package/components/theme-switch/theme-switch.js +37 -0
- package/components/theme-switch/theme-switch.module.css +31 -0
- package/components/time-picker/time-picker.d.ts +13 -0
- package/components/time-picker/time-picker.js +95 -0
- package/components/time-picker/time-picker.module.css +23 -0
- package/components/timeline/timeline.d.ts +47 -0
- package/components/timeline/timeline.js +171 -0
- package/components/timeline/timeline.module.css +51 -0
- package/components/toast/toast.d.ts +8 -0
- package/components/toast/toast.js +118 -0
- package/components/toast/toast.module.css +100 -0
- package/components/toast-stack/toast-stack.d.ts +57 -0
- package/components/toast-stack/toast-stack.js +399 -0
- package/components/toast-stack/toast-stack.module.css +61 -0
- package/components/tooltip/tooltip.d.ts +8 -0
- package/components/tooltip/tooltip.js +62 -0
- package/components/tooltip/tooltip.module.css +18 -0
- package/components/tree-view/tree-view.d.ts +17 -0
- package/components/tree-view/tree-view.js +147 -0
- package/components/tree-view/tree-view.module.css +22 -0
- package/components/treemap/treemap.d.ts +35 -0
- package/components/treemap/treemap.js +375 -0
- package/components/treemap/treemap.module.css +59 -0
- package/components/usage-meter/usage-meter.d.ts +24 -0
- package/components/usage-meter/usage-meter.js +296 -0
- package/components/usage-meter/usage-meter.module.css +61 -0
- package/components/user-menu/user-menu.d.ts +62 -0
- package/components/user-menu/user-menu.js +316 -0
- package/components/user-menu/user-menu.module.css +97 -0
- package/components/waffle-chart/waffle-chart.d.ts +39 -0
- package/components/waffle-chart/waffle-chart.js +244 -0
- package/components/waffle-chart/waffle-chart.module.css +52 -0
- package/components.json +1330 -0
- package/lib/escape.d.ts +19 -0
- package/lib/escape.js +50 -0
- package/lib/media.d.ts +353 -0
- package/lib/media.js +65 -0
- package/lib/menu-highlight.d.ts +28 -0
- package/lib/menu-highlight.js +43 -0
- package/lib/motion-tokens.d.ts +60 -0
- package/lib/motion-tokens.js +25 -0
- package/lib/return-focus.d.ts +9 -0
- package/lib/return-focus.js +53 -0
- package/lib/silicon-provider.d.ts +60 -0
- package/lib/silicon-provider.js +47 -0
- package/lib/use-copy-feedback.d.ts +8 -0
- package/lib/use-copy-feedback.js +36 -0
- package/lib/use-today.d.ts +2 -0
- package/lib/use-today.js +29 -0
- package/package.json +66 -4
- package/styles.css +218 -0
- package/styles.css.d.ts +1 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
5
|
+
import { SiliconImage } from "../../lib/silicon-provider.js";
|
|
6
|
+
import { motionTokens } from "../../lib/motion-tokens.js";
|
|
7
|
+
import styles from "./avatar.module.css";
|
|
8
|
+
export function Avatar({ name, src, size = "md", status, className, ...props }) {
|
|
9
|
+
const initials = name.trim().split(/\s+/).slice(0, 2).map(part => part[0]?.toUpperCase()).join("");
|
|
10
|
+
const reduceMotion = !!useReducedMotion();
|
|
11
|
+
const image = useRef(null);
|
|
12
|
+
const [failedSrc, setFailedSrc] = useState();
|
|
13
|
+
// A photo that is already decoded shows at once. One that is still loading waits, then fades in from a soft blur.
|
|
14
|
+
useLayoutEffect(() => { const node = image.current; if (node && !node.complete)
|
|
15
|
+
node.dataset.loading = ""; }, [src]);
|
|
16
|
+
const showImage = src && failedSrc !== src;
|
|
17
|
+
return _jsxs("span", { ...props, className: [styles.avatar, styles[size], className].filter(Boolean).join(" "), role: "img", "aria-label": `${name}${status ? `, ${status}` : ""}`, children: [showImage ? _jsx(SiliconImage, { ref: image, src: src, alt: "", fill: true, sizes: size === "xl" ? "88px" : size === "lg" ? "48px" : size === "md" ? "36px" : "28px", onLoad: event => { delete event.currentTarget.dataset.loading; }, onError: () => setFailedSrc(src) }, src) : _jsx("span", { className: src ? styles.fallback : undefined, "aria-hidden": "true", children: initials }), _jsx(AnimatePresence, { initial: false, children: status && _jsx(motion.i, { className: [styles.status, styles[status]].join(" "), "aria-hidden": "true", initial: { opacity: 0, scale: .6 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: .6, transition: { duration: reduceMotion ? 0 : motionTokens.duration.fast } }, transition: reduceMotion ? { duration: 0 } : motionTokens.spring.snappy }, status) })] });
|
|
18
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
.avatar {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: inline-grid;
|
|
4
|
+
flex: 0 0 auto;
|
|
5
|
+
place-items: center;
|
|
6
|
+
overflow: visible;
|
|
7
|
+
border: 1px solid var(--border);
|
|
8
|
+
border-radius: var(--avatar-radius); corner-shape: var(--avatar-corner-shape);
|
|
9
|
+
background: var(--surface-muted);
|
|
10
|
+
color: var(--foreground);
|
|
11
|
+
font-size: var(--text-xs);
|
|
12
|
+
font-weight: 500;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.avatar img {
|
|
16
|
+
pointer-events: none;
|
|
17
|
+
border-radius: inherit; corner-shape: inherit;
|
|
18
|
+
object-fit: cover;
|
|
19
|
+
transition: opacity var(--duration-standard) var(--ease-enter), filter var(--duration-standard) var(--ease-enter);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Set only while a photo is still downloading after mount, so cached photos never fade. */
|
|
23
|
+
.avatar img[data-loading] {
|
|
24
|
+
opacity: 0;
|
|
25
|
+
filter: blur(4px);
|
|
26
|
+
transition: none;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.fallback { animation: avatar-fallback-in var(--duration-standard) var(--ease-enter) both; }
|
|
30
|
+
|
|
31
|
+
.sm { width: 28px; height: 28px; font-size: 10px; }
|
|
32
|
+
.md { width: 36px; height: 36px; }
|
|
33
|
+
.lg { width: 48px; height: 48px; font-size: var(--text-sm); }
|
|
34
|
+
.xl { width: 88px; height: 88px; font-size: var(--text-xl); }
|
|
35
|
+
|
|
36
|
+
.status {
|
|
37
|
+
position: absolute;
|
|
38
|
+
right: 0;
|
|
39
|
+
bottom: 0;
|
|
40
|
+
z-index: 2;
|
|
41
|
+
width: 10px;
|
|
42
|
+
height: 10px;
|
|
43
|
+
border: 2px solid var(--surface);
|
|
44
|
+
border-radius: var(--radius-full); corner-shape: round;
|
|
45
|
+
box-shadow: 0 0 0 1px color-mix(in oklab, var(--border) 70%, transparent);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.sm .status { width: 8px; height: 8px; border-width: 1.5px; }
|
|
49
|
+
.lg .status { width: 12px; height: 12px; }
|
|
50
|
+
.xl .status { width: 14px; height: 14px; right: 4px; bottom: 4px; }
|
|
51
|
+
.online { background: var(--success); }
|
|
52
|
+
.offline { background: var(--text-muted); }
|
|
53
|
+
|
|
54
|
+
@keyframes avatar-fallback-in { from { opacity: 0; } to { opacity: 1; } }
|
|
55
|
+
|
|
56
|
+
@media (prefers-reduced-motion: reduce) {
|
|
57
|
+
.avatar img { transition: none; }
|
|
58
|
+
.avatar img[data-loading] { filter: none; }
|
|
59
|
+
.fallback { animation: none; }
|
|
60
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface AvatarGroupMember {
|
|
2
|
+
name: string;
|
|
3
|
+
src?: string;
|
|
4
|
+
status?: "online" | "offline";
|
|
5
|
+
}
|
|
6
|
+
export interface AvatarGroupProps {
|
|
7
|
+
members: AvatarGroupMember[];
|
|
8
|
+
max?: number;
|
|
9
|
+
size?: "sm" | "md" | "lg";
|
|
10
|
+
label?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function AvatarGroup({ members, max, size, label }: AvatarGroupProps): import("react").JSX.Element;
|
|
13
|
+
export default AvatarGroup;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
5
|
+
import { motionTokens } from "../../lib/motion-tokens.js";
|
|
6
|
+
import { Avatar } from "../avatar/avatar.js";
|
|
7
|
+
import styles from "./avatar-group.module.css";
|
|
8
|
+
/** The overflow count rolls the way it moved: more people rise in from below, fewer drop in from above. */
|
|
9
|
+
const rise = { hidden: (direction) => ({ opacity: 0, y: `${.4 * direction}em`, filter: `blur(${motionTokens.blur.subtle}px)` }), shown: { opacity: 1, y: 0, filter: "blur(0px)", transition: { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter] } }, gone: (direction) => ({ opacity: 0, y: `${-.4 * direction}em`, filter: `blur(${motionTokens.blur.subtle}px)`, transition: { duration: motionTokens.duration.fast, ease: [...motionTokens.ease.standard] } }) };
|
|
10
|
+
// Same keys as `rise` so the settled style is identical whichever branch renders on the server.
|
|
11
|
+
const fade = { hidden: { opacity: 0, y: 0, filter: "blur(0px)" }, shown: { opacity: 1, y: 0, filter: "blur(0px)", transition: { duration: motionTokens.duration.instant } }, gone: { opacity: 0, y: 0, filter: "blur(0px)", transition: { duration: motionTokens.duration.instant } } };
|
|
12
|
+
// A person joining or leaving opens or closes their slot, so the rest of the stack slides instead of jumping.
|
|
13
|
+
const slot = { initial: { width: 0, opacity: 0, scale: .9 }, animate: { width: "auto", opacity: 1, scale: 1 }, exit: { width: 0, opacity: 0, scale: .9 } };
|
|
14
|
+
export function AvatarGroup({ members, max = 4, size = "md", label = "Team members" }) {
|
|
15
|
+
const reduceMotion = !!useReducedMotion();
|
|
16
|
+
const visible = members.slice(0, Math.max(0, max));
|
|
17
|
+
const overflow = Math.max(0, members.length - visible.length);
|
|
18
|
+
const transition = reduceMotion ? { duration: 0 } : motionTokens.spring.morph;
|
|
19
|
+
const [count, setCount] = useState({ overflow, direction: 1 });
|
|
20
|
+
if (count.overflow !== overflow)
|
|
21
|
+
setCount({ overflow, direction: overflow < count.overflow ? -1 : 1 });
|
|
22
|
+
return _jsx("div", { className: [styles.group, styles[size]].join(" "), role: "group", "aria-label": label, style: { "--count": visible.length + (overflow > 0 ? 1 : 0) }, children: _jsxs(AnimatePresence, { initial: false, children: [visible.map((member, index) => _jsx(motion.span, { className: styles.slot, style: { "--index": index }, ...slot, transition: transition, children: _jsxs("span", { className: styles.lift, children: [_jsx(Avatar, { className: styles.avatar, name: member.name, src: member.src, status: member.status, size: size }), _jsx("span", { className: styles.tip, "aria-hidden": "true", children: member.name })] }) }, member.name)), overflow > 0 ? _jsx(motion.span, { className: styles.slot, style: { "--index": visible.length }, ...slot, transition: transition, children: _jsx("span", { className: [styles.lift, styles.overflow, styles[size]].join(" "), role: "img", "aria-label": `${overflow} more ${label.toLowerCase()}`, children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, custom: count.direction, children: _jsxs(motion.span, { className: styles.count, custom: count.direction, variants: reduceMotion ? fade : rise, initial: "hidden", animate: "shown", exit: "gone", "aria-hidden": "true", children: ["+", overflow] }, overflow) }) }) }, "overflow") : null] }) });
|
|
23
|
+
}
|
|
24
|
+
export default AvatarGroup;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.group { --avatar-size: 36px; display: inline-flex; height: var(--avatar-size); align-items: center; padding-left: 6px; }
|
|
2
|
+
.group.sm { --avatar-size: 28px; }
|
|
3
|
+
.group.lg { --avatar-size: 48px; }
|
|
4
|
+
/* Each slot is the avatar's visible width, so a slot that opens or closes moves the rest of the stack smoothly. */
|
|
5
|
+
.slot { position: relative; display: inline-flex; flex: 0 0 auto; height: var(--avatar-size); align-items: center; }
|
|
6
|
+
/* One wrapper per person carries every hover move as a single translate, so the stack never changes size. */
|
|
7
|
+
.lift { --fan: 0px; --aside: 0px; --rise: 0px; position: relative; display: inline-flex; margin-left: -6px; border-radius: var(--avatar-radius); corner-shape: var(--avatar-corner-shape); transform: translate(calc(var(--fan) + var(--aside)), var(--rise)); transition: transform var(--duration-standard) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard); }
|
|
8
|
+
.avatar, .overflow { border: 2px solid var(--surface); box-shadow: 0 0 0 1px var(--border); transition: box-shadow var(--duration-fast) var(--ease-standard); }
|
|
9
|
+
.overflow { position: relative; display: inline-grid; place-items: center; overflow: hidden; border-radius: var(--avatar-radius); corner-shape: var(--avatar-corner-shape); background: var(--surface-muted); color: var(--text-secondary); font-size: var(--text-xs); font-weight: 500; font-variant-numeric: tabular-nums; }
|
|
10
|
+
.count { display: inline-block; }
|
|
11
|
+
.overflow.sm { width: 28px; height: 28px; font-size: 10px; }
|
|
12
|
+
.overflow.md { width: 36px; height: 36px; }
|
|
13
|
+
.overflow.lg { width: 48px; height: 48px; font-size: var(--text-sm); }
|
|
14
|
+
/* The name floats above the stack, outside the layout, so showing it never moves anything. */
|
|
15
|
+
.tip { position: absolute; z-index: 2; bottom: calc(100% + 8px); left: 50%; padding: 4px 8px; border-radius: var(--radius-full); corner-shape: round; background: var(--foreground); color: var(--background); font-size: var(--text-xs); font-weight: 500; line-height: var(--leading-body); white-space: nowrap; pointer-events: none; opacity: 0; transform: translate(-50%, 3px); transition: opacity var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-standard); }
|
|
16
|
+
/* Pointing at the stack loosens its overlap from the center; the person under the pointer lifts with a crisp ring and names themselves, and their neighbors ease aside. */
|
|
17
|
+
@media (hover: hover) and (pointer: fine) {
|
|
18
|
+
.group:hover .lift { --fan: calc((var(--index) - (var(--count) - 1) / 2) * 4px); }
|
|
19
|
+
.slot:hover { z-index: 1; }
|
|
20
|
+
.slot:hover .lift { --rise: -2px; }
|
|
21
|
+
.slot:hover ~ .slot .lift { --aside: 3px; }
|
|
22
|
+
.slot:has(~ .slot:hover) .lift { --aside: -3px; }
|
|
23
|
+
.slot:hover .avatar, .slot:hover .overflow { box-shadow: 0 0 0 1px var(--border-strong); }
|
|
24
|
+
.slot:hover .tip { opacity: 1; transform: translate(-50%, 0); transition-delay: 60ms; }
|
|
25
|
+
}
|
|
26
|
+
/* Reduced motion keeps the stack still; the ring and name still answer the pointer. */
|
|
27
|
+
@media (prefers-reduced-motion: reduce) { .lift { transform: none !important; transition: none; } .tip { transform: translate(-50%, 0); transition: none; } }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
export type BadgeTone = "neutral" | "success" | "info" | "warning" | "danger";
|
|
3
|
+
export type BadgeSize = "sm" | "md";
|
|
4
|
+
export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
5
|
+
tone?: BadgeTone;
|
|
6
|
+
size?: BadgeSize;
|
|
7
|
+
icon?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
export declare function Badge({ tone, size, icon, className, children, ...props }: BadgeProps): import("react").JSX.Element;
|
|
10
|
+
export default Badge;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { isValidElement, useEffect, useLayoutEffect, useRef } from "react";
|
|
4
|
+
import { AnimatePresence, animate, motion, useIsPresent, useMotionValue, useReducedMotion } from "motion/react";
|
|
5
|
+
import { motionTokens } from "../../lib/motion-tokens.js";
|
|
6
|
+
import styles from "./badge.module.css";
|
|
7
|
+
const exitFast = { duration: motionTokens.duration.fast, ease: [...motionTokens.ease.standard] };
|
|
8
|
+
const textIn = { opacity: 0, y: "0.3em", filter: `blur(${motionTokens.blur.soft}px)` };
|
|
9
|
+
const textOut = { opacity: 0, y: "-0.3em", filter: `blur(${motionTokens.blur.subtle}px)`, transition: exitFast };
|
|
10
|
+
const iconIn = { opacity: 0, scale: .6, filter: `blur(${motionTokens.blur.subtle}px)` };
|
|
11
|
+
const shown = { opacity: 1, y: "0em", scale: 1, filter: "blur(0px)" };
|
|
12
|
+
const fadeOnly = { opacity: 0, transition: { duration: motionTokens.duration.instant } };
|
|
13
|
+
/** Outgoing copies are hidden from assistive tech while they fade, so only the current text is read. */
|
|
14
|
+
function Swap(props) {
|
|
15
|
+
const present = useIsPresent();
|
|
16
|
+
return _jsx(motion.span, { ...props, "aria-hidden": present ? props["aria-hidden"] : true });
|
|
17
|
+
}
|
|
18
|
+
/** A new icon component crossfades in; re-rendering the same icon stays still. */
|
|
19
|
+
function iconKey(icon) {
|
|
20
|
+
if (!isValidElement(icon))
|
|
21
|
+
return "icon";
|
|
22
|
+
const type = icon.type;
|
|
23
|
+
return typeof type === "string" ? type : type.displayName ?? type.name ?? "icon";
|
|
24
|
+
}
|
|
25
|
+
export function Badge({ tone = "neutral", size = "md", icon, className, children, ...props }) {
|
|
26
|
+
const reduce = useReducedMotion();
|
|
27
|
+
const text = typeof children === "string" || typeof children === "number" ? String(children) : null;
|
|
28
|
+
const glyphKey = icon ? iconKey(icon) : "";
|
|
29
|
+
const body = useRef(null);
|
|
30
|
+
const content = useRef(null);
|
|
31
|
+
// Width stays auto at rest. Only a new label or icon springs it from the old size to the new one; passive reflows (a font swap, a hidden parent) follow instantly.
|
|
32
|
+
const width = useMotionValue("auto");
|
|
33
|
+
const changedAt = useRef(0);
|
|
34
|
+
useLayoutEffect(() => { changedAt.current = performance.now(); }, [text, glyphKey]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const node = content.current;
|
|
37
|
+
if (!node || typeof ResizeObserver === "undefined")
|
|
38
|
+
return;
|
|
39
|
+
let last;
|
|
40
|
+
let controls;
|
|
41
|
+
const settle = () => { width.jump("auto"); if (body.current)
|
|
42
|
+
body.current.style.width = "auto"; };
|
|
43
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
44
|
+
if (!entry)
|
|
45
|
+
return;
|
|
46
|
+
const next = entry.borderBoxSize?.[0]?.inlineSize ?? node.offsetWidth;
|
|
47
|
+
const current = width.get();
|
|
48
|
+
const from = typeof current === "number" ? current : last;
|
|
49
|
+
last = next;
|
|
50
|
+
controls?.stop();
|
|
51
|
+
if (reduce || from === undefined || from === next || performance.now() - changedAt.current > 120)
|
|
52
|
+
return settle();
|
|
53
|
+
// Pin the old width before this frame paints, then spring to the new one.
|
|
54
|
+
if (body.current)
|
|
55
|
+
body.current.style.width = `${from}px`;
|
|
56
|
+
controls = animate(width, [from, next], { ...motionTokens.spring.morph, onComplete: settle });
|
|
57
|
+
});
|
|
58
|
+
observer.observe(node);
|
|
59
|
+
return () => { observer.disconnect(); controls?.stop(); };
|
|
60
|
+
}, [width, reduce]);
|
|
61
|
+
const enter = reduce ? { duration: motionTokens.duration.instant } : { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter] };
|
|
62
|
+
const classes = [styles.badge, styles[tone], styles[size], className].filter(Boolean).join(" ");
|
|
63
|
+
// The pill follows its content: text and icon swap in place while the width springs to the new size.
|
|
64
|
+
return _jsx("span", { ...props, className: classes, children: _jsx(motion.span, { ref: body, className: styles.body, style: { width }, children: _jsxs("span", { ref: content, className: styles.content, children: [icon ? _jsx("span", { className: styles.icon, "aria-hidden": "true", children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, children: _jsx(Swap, { className: styles.glyph, initial: reduce ? { opacity: 0 } : iconIn, animate: shown, exit: reduce ? fadeOnly : { ...iconIn, transition: exitFast }, transition: reduce ? enter : motionTokens.spring.snappy, children: icon }, glyphKey) }) }) : null, text === null ? children : _jsx("span", { className: styles.label, children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, children: _jsx(Swap, { className: styles.text, initial: reduce ? { opacity: 0 } : textIn, animate: shown, exit: reduce ? fadeOnly : textOut, transition: enter, children: text }, text) }) })] }) }) });
|
|
65
|
+
}
|
|
66
|
+
export default Badge;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
.badge {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
min-height: 26px;
|
|
4
|
+
align-items: center;
|
|
5
|
+
border: 1px solid var(--badge-border, var(--border));
|
|
6
|
+
border-radius: var(--radius-full); corner-shape: round;
|
|
7
|
+
padding: 0 10px;
|
|
8
|
+
color: var(--badge-foreground, var(--foreground));
|
|
9
|
+
background: var(--badge-background, var(--surface));
|
|
10
|
+
font-size: var(--text-xs);
|
|
11
|
+
font-weight: 500;
|
|
12
|
+
letter-spacing: -.01em;
|
|
13
|
+
line-height: 1;
|
|
14
|
+
white-space: nowrap;
|
|
15
|
+
/* Clip keeps the inline baseline while the pill morphs around swapped text. */
|
|
16
|
+
overflow: clip;
|
|
17
|
+
transition: border-color var(--duration-standard) var(--ease-standard), background-color var(--duration-standard) var(--ease-standard), color var(--duration-standard) var(--ease-standard);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@media (hover: hover) and (pointer: fine) {
|
|
21
|
+
.badge:hover { border-color: color-mix(in oklab, var(--badge-color, var(--foreground)) 32%, var(--border)); background: color-mix(in oklab, var(--badge-color, var(--foreground)) 10%, var(--surface)); }
|
|
22
|
+
}
|
|
23
|
+
.sm { min-height: 22px; padding: 0 8px; font-size: 11px; }
|
|
24
|
+
.md { font-size: var(--text-xs); }
|
|
25
|
+
|
|
26
|
+
.body { display: block; }
|
|
27
|
+
.content { position: relative; display: inline-flex; width: max-content; align-items: center; gap: 5px; }
|
|
28
|
+
.sm .content { gap: 4px; }
|
|
29
|
+
.label, .glyph { position: relative; display: inline-flex; align-items: center; }
|
|
30
|
+
.text { display: block; }
|
|
31
|
+
|
|
32
|
+
.icon {
|
|
33
|
+
position: relative;
|
|
34
|
+
display: inline-flex;
|
|
35
|
+
width: auto;
|
|
36
|
+
height: auto;
|
|
37
|
+
flex: 0 0 auto;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
color: var(--badge-color, var(--foreground));
|
|
41
|
+
line-height: 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.neutral { --badge-color: var(--text-secondary); --badge-foreground: var(--text-secondary); --badge-background: var(--surface-muted); --badge-border: var(--border); }
|
|
45
|
+
.success { --badge-color: var(--success); --badge-foreground: var(--success); --badge-background: color-mix(in oklab, var(--success) 10%, var(--surface)); --badge-border: color-mix(in oklab, var(--success) 25%, var(--border)); }
|
|
46
|
+
.info { --badge-color: var(--accent); --badge-foreground: var(--accent-strong); --badge-background: var(--accent-subtle); --badge-border: color-mix(in oklab, var(--accent) 24%, var(--border)); }
|
|
47
|
+
.warning { --badge-color: var(--warning); --badge-foreground: var(--warning); --badge-background: color-mix(in oklab, var(--warning) 11%, var(--surface)); --badge-border: color-mix(in oklab, var(--warning) 27%, var(--border)); }
|
|
48
|
+
.danger { --badge-color: var(--danger); --badge-foreground: var(--danger); --badge-background: color-mix(in oklab, var(--danger) 10%, var(--surface)); --badge-border: color-mix(in oklab, var(--danger) 26%, var(--border)); }
|
|
49
|
+
|
|
50
|
+
@media (prefers-reduced-motion: reduce) {
|
|
51
|
+
.badge { transition-duration: var(--duration-instant); }
|
|
52
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface BarChartDatum {
|
|
2
|
+
/** Stable identity, such as an ISO date. A bar whose key survives a data change keeps its place, so switching ranges morphs instead of redrawing. */
|
|
3
|
+
key: string;
|
|
4
|
+
/** Full label for the headline and screen readers, such as "Tue, Sep 15". */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Short label under the bar, such as "T" or "Sep 14". Leave it out to keep the axis quiet. */
|
|
7
|
+
axisLabel?: string;
|
|
8
|
+
value: number;
|
|
9
|
+
}
|
|
10
|
+
/** Use a bar chart for one measure across a run of days, weeks, or months, when people compare each period against the rest and against the average. */
|
|
11
|
+
export interface BarChartProps {
|
|
12
|
+
data: BarChartDatum[];
|
|
13
|
+
/** What is measured, such as "Active minutes". Names the chart for assistive technology. */
|
|
14
|
+
label: string;
|
|
15
|
+
/** The range on show, such as "Sep 15–21, 2026". It rests under the headline. */
|
|
16
|
+
period: string;
|
|
17
|
+
/** Unit after each value, such as "min". */
|
|
18
|
+
unit?: string;
|
|
19
|
+
/** Headline label at rest, above the average. */
|
|
20
|
+
averageLabel?: string;
|
|
21
|
+
/** Headline label while a bar is scrubbed. */
|
|
22
|
+
valueLabel?: string;
|
|
23
|
+
/** Header of the first column in the data table read by screen readers. */
|
|
24
|
+
categoryLabel?: string;
|
|
25
|
+
/** Draws the average as a reference line that springs to each new range. */
|
|
26
|
+
showAverage?: boolean;
|
|
27
|
+
/** Plot height in pixels. */
|
|
28
|
+
height?: number;
|
|
29
|
+
formatValue?: (value: number) => string;
|
|
30
|
+
}
|
|
31
|
+
export declare function BarChart({ data, label, period, unit, averageLabel, valueLabel, categoryLabel, showAverage, height, formatValue }: BarChartProps): import("react").JSX.Element;
|
|
32
|
+
export default BarChart;
|