@lt-three/love3 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 +21 -0
- package/README.md +86 -2
- package/dist/native/analytics/animation.d.ts +4 -0
- package/dist/native/analytics/animation.js +6 -0
- package/dist/native/analytics/chart.types.d.ts +25 -0
- package/dist/native/analytics/chart.types.js +0 -0
- package/dist/native/analytics/line-chart.d.ts +4 -0
- package/dist/native/analytics/line-chart.ios.d.ts +4 -0
- package/dist/native/analytics/line-chart.ios.js +24 -0
- package/dist/native/analytics/line-chart.js +33 -0
- package/dist/native/analytics/metric-card.d.ts +10 -0
- package/dist/native/analytics/metric-card.js +29 -0
- package/dist/native/analytics/multi-line-chart.d.ts +17 -0
- package/dist/native/analytics/multi-line-chart.js +215 -0
- package/dist/native/analytics/pie-chart.d.ts +15 -0
- package/dist/native/analytics/pie-chart.ios.d.ts +4 -0
- package/dist/native/analytics/pie-chart.ios.js +24 -0
- package/dist/native/analytics/pie-chart.js +92 -0
- package/dist/native/analytics/single-number.d.ts +19 -0
- package/dist/native/analytics/single-number.js +101 -0
- package/dist/native/atom/avatar.d.ts +21 -0
- package/dist/native/atom/avatar.js +46 -0
- package/dist/native/atom/badge.d.ts +28 -0
- package/dist/native/atom/badge.js +51 -0
- package/dist/native/atom/button.d.ts +46 -0
- package/dist/native/atom/button.js +109 -0
- package/dist/native/atom/checkbox.d.ts +21 -0
- package/dist/native/atom/checkbox.js +47 -0
- package/dist/native/atom/colored-value.d.ts +12 -0
- package/dist/native/atom/colored-value.js +24 -0
- package/dist/native/atom/image.d.ts +21 -0
- package/dist/native/atom/image.js +72 -0
- package/dist/native/atom/label.d.ts +18 -0
- package/dist/native/atom/label.js +33 -0
- package/dist/native/atom/link.d.ts +18 -0
- package/dist/native/atom/link.js +25 -0
- package/dist/native/atom/readout.d.ts +22 -0
- package/dist/native/atom/readout.js +39 -0
- package/dist/native/atom/search-input.d.ts +12 -0
- package/dist/native/atom/search-input.js +36 -0
- package/dist/native/atom/select.d.ts +54 -0
- package/dist/native/atom/select.js +200 -0
- package/dist/native/atom/separator.d.ts +13 -0
- package/dist/native/atom/separator.js +17 -0
- package/dist/native/atom/skeleton.d.ts +12 -0
- package/dist/native/atom/skeleton.js +39 -0
- package/dist/native/atom/slider.d.ts +32 -0
- package/dist/native/atom/slider.js +75 -0
- package/dist/native/atom/spinner.d.ts +15 -0
- package/dist/native/atom/spinner.js +25 -0
- package/dist/native/atom/switch.d.ts +18 -0
- package/dist/native/atom/switch.js +52 -0
- package/dist/native/atom/symbol.d.ts +16 -0
- package/dist/native/atom/symbol.js +38 -0
- package/dist/native/atom/symbol.types.d.ts +25 -0
- package/dist/native/atom/symbol.types.js +49 -0
- package/dist/native/atom/text-field.d.ts +49 -0
- package/dist/native/atom/text-field.js +56 -0
- package/dist/native/atom/text.d.ts +2 -0
- package/dist/native/atom/text.js +3 -0
- package/dist/native/atom/theme-toggle.d.ts +21 -0
- package/dist/native/atom/theme-toggle.js +36 -0
- package/dist/native/atom/timezone-select.d.ts +16 -0
- package/dist/native/atom/timezone-select.js +28 -0
- package/dist/native/atom/typography.d.ts +13 -0
- package/dist/native/atom/typography.js +28 -0
- package/dist/native/base/button.d.ts +61 -0
- package/dist/native/base/button.js +92 -0
- package/dist/native/base/form.d.ts +72 -0
- package/dist/native/base/form.js +113 -0
- package/dist/native/base/glass-card.d.ts +15 -0
- package/dist/native/base/glass-card.js +38 -0
- package/dist/native/base/input.d.ts +29 -0
- package/dist/native/base/input.js +27 -0
- package/dist/native/base/sheet.d.ts +71 -0
- package/dist/native/base/sheet.js +187 -0
- package/dist/native/base/text.d.ts +14 -0
- package/dist/native/base/text.js +53 -0
- package/dist/native/form/checkbox.d.ts +13 -0
- package/dist/native/form/checkbox.js +55 -0
- package/dist/native/form/date-picker.d.ts +23 -0
- package/dist/native/form/date-picker.js +119 -0
- package/dist/native/form/form.d.ts +23 -0
- package/dist/native/form/form.js +45 -0
- package/dist/native/form/radio-group.d.ts +23 -0
- package/dist/native/form/radio-group.js +88 -0
- package/dist/native/form/select.d.ts +13 -0
- package/dist/native/form/select.js +45 -0
- package/dist/native/form/slider.d.ts +14 -0
- package/dist/native/form/slider.js +42 -0
- package/dist/native/form/switch.d.ts +9 -0
- package/dist/native/form/switch.js +59 -0
- package/dist/native/form/text-area.d.ts +11 -0
- package/dist/native/form/text-area.js +50 -0
- package/dist/native/form/text-input.d.ts +18 -0
- package/dist/native/form/text-input.js +90 -0
- package/dist/native/index.d.ts +1 -0
- package/dist/native/index.js +0 -0
- package/dist/native/layout/accordion.d.ts +25 -0
- package/dist/native/layout/accordion.js +54 -0
- package/dist/native/layout/card.d.ts +47 -0
- package/dist/native/layout/card.js +57 -0
- package/dist/native/layout/description.d.ts +16 -0
- package/dist/native/layout/description.js +32 -0
- package/dist/native/layout/error.d.ts +21 -0
- package/dist/native/layout/error.js +44 -0
- package/dist/native/layout/information.d.ts +25 -0
- package/dist/native/layout/information.js +35 -0
- package/dist/native/layout/locked.d.ts +38 -0
- package/dist/native/layout/locked.js +94 -0
- package/dist/native/layout/modal.d.ts +25 -0
- package/dist/native/layout/modal.js +93 -0
- package/dist/native/layout/not-found.d.ts +17 -0
- package/dist/native/layout/not-found.js +30 -0
- package/dist/native/layout/page-frame.d.ts +23 -0
- package/dist/native/layout/page-frame.js +46 -0
- package/dist/native/layout/panel.d.ts +18 -0
- package/dist/native/layout/panel.js +23 -0
- package/dist/native/layout/reorderable-list.d.ts +42 -0
- package/dist/native/layout/reorderable-list.js +52 -0
- package/dist/native/layout/sheet.d.ts +30 -0
- package/dist/native/layout/sheet.js +46 -0
- package/dist/native/layout/simple-dialog.d.ts +16 -0
- package/dist/native/layout/simple-dialog.js +20 -0
- package/dist/native/layout/simple-table.d.ts +50 -0
- package/dist/native/layout/simple-table.js +75 -0
- package/dist/native/layout/tab-bar.d.ts +33 -0
- package/dist/native/layout/tab-bar.js +48 -0
- package/dist/native/layout/tabs.d.ts +28 -0
- package/dist/native/layout/tabs.js +54 -0
- package/dist/native/layout/tuning-panel.d.ts +24 -0
- package/dist/native/layout/tuning-panel.js +246 -0
- package/dist/native/lib/autocomplete.d.ts +7 -0
- package/dist/native/lib/autocomplete.js +34 -0
- package/dist/native/lib/breakpoints.d.ts +14 -0
- package/dist/native/lib/breakpoints.js +17 -0
- package/dist/native/lib/chart-palette.d.ts +8 -0
- package/dist/native/lib/chart-palette.js +20 -0
- package/dist/native/lib/confirm.d.ts +49 -0
- package/dist/native/lib/confirm.js +70 -0
- package/dist/native/lib/control.d.ts +24 -0
- package/dist/native/lib/control.js +0 -0
- package/dist/native/lib/external-store.d.ts +17 -0
- package/dist/native/lib/external-store.js +30 -0
- package/dist/native/lib/keyboard.d.ts +15 -0
- package/dist/native/lib/keyboard.js +24 -0
- package/dist/native/lib/labels.d.ts +15 -0
- package/dist/native/lib/labels.js +17 -0
- package/dist/native/lib/loading.d.ts +10 -0
- package/dist/native/lib/loading.js +15 -0
- package/dist/native/lib/network.d.ts +12 -0
- package/dist/native/lib/network.js +21 -0
- package/dist/native/lib/scheme.d.ts +18 -0
- package/dist/native/lib/scheme.js +22 -0
- package/dist/native/lib/snackbar.d.ts +46 -0
- package/dist/native/lib/snackbar.js +60 -0
- package/dist/native/lib/theme.d.ts +34 -0
- package/dist/native/lib/theme.js +53 -0
- package/dist/native/lib/timezone.d.ts +26 -0
- package/dist/native/lib/timezone.js +95 -0
- package/dist/native/lib/token-color.d.ts +5 -0
- package/dist/native/lib/token-color.js +13 -0
- package/dist/native/lib/token-color.web.d.ts +4 -0
- package/dist/native/lib/token-color.web.js +7 -0
- package/dist/native/lib/tuning-context.d.ts +9 -0
- package/dist/native/lib/tuning-context.js +7 -0
- package/dist/native/lib/tuning.d.ts +24 -0
- package/dist/native/lib/tuning.js +42 -0
- package/dist/native/lib/typography.d.ts +10 -0
- package/dist/native/lib/typography.js +11 -0
- package/dist/native/lib/utils.d.ts +11 -0
- package/dist/native/lib/utils.js +21 -0
- package/dist/native/once/confirm.d.ts +7 -0
- package/dist/native/once/confirm.js +60 -0
- package/dist/native/once/error-boundary.d.ts +28 -0
- package/dist/native/once/error-boundary.js +55 -0
- package/dist/native/once/loading-lock.d.ts +8 -0
- package/dist/native/once/loading-lock.js +28 -0
- package/dist/native/once/offline-banner.d.ts +15 -0
- package/dist/native/once/offline-banner.js +35 -0
- package/dist/native/once/snackbar.d.ts +17 -0
- package/dist/native/once/snackbar.js +106 -0
- package/dist/native/styles.css +46 -0
- package/dist/shared/analytics.d.ts +71 -0
- package/dist/shared/analytics.js +0 -0
- package/dist/shared/button.d.ts +7 -0
- package/dist/shared/button.js +23 -0
- package/dist/shared/form.d.ts +45 -0
- package/dist/shared/form.js +46 -0
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +3 -0
- package/dist/tokens/contrast-pairs.d.ts +30 -0
- package/dist/tokens/contrast-pairs.js +404 -0
- package/dist/tokens/index.d.ts +6 -0
- package/dist/tokens/index.js +10 -0
- package/dist/tokens/styles.gen.css +250 -0
- package/dist/tokens/tokens.gen.d.ts +141 -0
- package/dist/tokens/tokens.gen.js +206 -0
- package/dist/tuning/catalog.d.ts +27 -0
- package/dist/tuning/catalog.gen.d.ts +4 -0
- package/dist/tuning/catalog.gen.js +397 -0
- package/dist/tuning/catalog.js +128 -0
- package/dist/tuning/color.d.ts +20 -0
- package/dist/tuning/color.js +80 -0
- package/dist/tuning/css.d.ts +35 -0
- package/dist/tuning/css.js +147 -0
- package/dist/tuning/index.d.ts +8 -0
- package/dist/tuning/index.js +9 -0
- package/dist/tuning/model.d.ts +33 -0
- package/dist/tuning/model.js +154 -0
- package/dist/tuning/native.d.ts +35 -0
- package/dist/tuning/native.js +80 -0
- package/dist/tuning/schema.d.ts +69 -0
- package/dist/tuning/schema.js +247 -0
- package/dist/tuning/types.d.ts +75 -0
- package/dist/tuning/types.js +39 -0
- package/dist/web/analytics/line-chart.d.ts +20 -0
- package/dist/web/analytics/line-chart.js +28 -0
- package/dist/web/analytics/metric-card.d.ts +18 -0
- package/dist/web/analytics/metric-card.js +40 -0
- package/dist/web/analytics/multi-line-chart.d.ts +39 -0
- package/dist/web/analytics/multi-line-chart.js +345 -0
- package/dist/web/atom/avatar.d.ts +11 -0
- package/dist/web/atom/avatar.js +24 -0
- package/dist/web/atom/badge.d.ts +2 -0
- package/dist/web/atom/badge.js +3 -0
- package/dist/web/atom/button.d.ts +23 -0
- package/dist/web/atom/button.js +35 -0
- package/dist/web/atom/checkbox.d.ts +19 -0
- package/dist/web/atom/checkbox.js +33 -0
- package/dist/web/atom/colored-value.d.ts +13 -0
- package/dist/web/atom/colored-value.js +24 -0
- package/dist/web/atom/help.d.ts +10 -0
- package/dist/web/atom/help.js +18 -0
- package/dist/web/atom/label.d.ts +2 -0
- package/dist/web/atom/label.js +3 -0
- package/dist/web/atom/segmented-control.d.ts +40 -0
- package/dist/web/atom/segmented-control.js +43 -0
- package/dist/web/atom/select.d.ts +43 -0
- package/dist/web/atom/select.js +93 -0
- package/dist/web/atom/separator.d.ts +2 -0
- package/dist/web/atom/separator.js +3 -0
- package/dist/web/atom/skeleton.d.ts +2 -0
- package/dist/web/atom/skeleton.js +3 -0
- package/dist/web/atom/slider.d.ts +33 -0
- package/dist/web/atom/slider.js +70 -0
- package/dist/web/atom/spinner.d.ts +8 -0
- package/dist/web/atom/spinner.js +14 -0
- package/dist/web/atom/switch.d.ts +11 -0
- package/dist/web/atom/switch.js +10 -0
- package/dist/web/atom/text-area.d.ts +22 -0
- package/dist/web/atom/text-area.js +26 -0
- package/dist/web/atom/text-field.d.ts +17 -0
- package/dist/web/atom/text-field.js +21 -0
- package/dist/web/atom/toggle-chip.d.ts +22 -0
- package/dist/web/atom/toggle-chip.js +28 -0
- package/dist/web/atom/tooltip.d.ts +23 -0
- package/dist/web/atom/tooltip.js +26 -0
- package/dist/web/atom/typography.d.ts +6 -0
- package/dist/web/atom/typography.js +23 -0
- package/dist/web/base/accordion.d.ts +9 -0
- package/dist/web/base/accordion.js +44 -0
- package/dist/web/base/alert.d.ts +11 -0
- package/dist/web/base/alert.js +37 -0
- package/dist/web/base/avatar.d.ts +8 -0
- package/dist/web/base/avatar.js +29 -0
- package/dist/web/base/badge.d.ts +12 -0
- package/dist/web/base/badge.js +26 -0
- package/dist/web/base/breadcrumb.d.ts +16 -0
- package/dist/web/base/breadcrumb.js +72 -0
- package/dist/web/base/button.d.ts +12 -0
- package/dist/web/base/button.js +47 -0
- package/dist/web/base/card.d.ts +19 -0
- package/dist/web/base/card.js +74 -0
- package/dist/web/base/checkbox.d.ts +6 -0
- package/dist/web/base/checkbox.js +21 -0
- package/dist/web/base/dialog.d.ts +20 -0
- package/dist/web/base/dialog.js +92 -0
- package/dist/web/base/dropdown-menu.d.ts +29 -0
- package/dist/web/base/dropdown-menu.js +125 -0
- package/dist/web/base/field.d.ts +27 -0
- package/dist/web/base/field.js +118 -0
- package/dist/web/base/form.d.ts +38 -0
- package/dist/web/base/form.js +110 -0
- package/dist/web/base/input.d.ts +9 -0
- package/dist/web/base/input.js +23 -0
- package/dist/web/base/label.d.ts +6 -0
- package/dist/web/base/label.js +15 -0
- package/dist/web/base/pagination.d.ts +34 -0
- package/dist/web/base/pagination.js +90 -0
- package/dist/web/base/popover.d.ts +11 -0
- package/dist/web/base/popover.js +37 -0
- package/dist/web/base/radio-group.d.ts +7 -0
- package/dist/web/base/radio-group.js +27 -0
- package/dist/web/base/scroll-area.d.ts +10 -0
- package/dist/web/base/scroll-area.js +41 -0
- package/dist/web/base/select.d.ts +17 -0
- package/dist/web/base/select.js +96 -0
- package/dist/web/base/separator.d.ts +6 -0
- package/dist/web/base/separator.js +17 -0
- package/dist/web/base/sheet.d.ts +19 -0
- package/dist/web/base/sheet.js +88 -0
- package/dist/web/base/skeleton.d.ts +5 -0
- package/dist/web/base/skeleton.js +14 -0
- package/dist/web/base/slider.d.ts +9 -0
- package/dist/web/base/slider.js +39 -0
- package/dist/web/base/sonner.d.ts +9 -0
- package/dist/web/base/sonner.js +25 -0
- package/dist/web/base/spinner.d.ts +5 -0
- package/dist/web/base/spinner.js +17 -0
- package/dist/web/base/switch.d.ts +6 -0
- package/dist/web/base/switch.js +19 -0
- package/dist/web/base/table.d.ts +14 -0
- package/dist/web/base/table.js +68 -0
- package/dist/web/base/tabs.d.ts +9 -0
- package/dist/web/base/tabs.js +36 -0
- package/dist/web/base/textarea.d.ts +9 -0
- package/dist/web/base/textarea.js +23 -0
- package/dist/web/base/tooltip.d.ts +9 -0
- package/dist/web/base/tooltip.js +36 -0
- package/dist/web/form/checkbox.d.ts +35 -0
- package/dist/web/form/checkbox.js +91 -0
- package/dist/web/form/radio-group.d.ts +24 -0
- package/dist/web/form/radio-group.js +75 -0
- package/dist/web/form/select.d.ts +16 -0
- package/dist/web/form/select.js +44 -0
- package/dist/web/form/slider.d.ts +19 -0
- package/dist/web/form/slider.js +51 -0
- package/dist/web/form/switch.d.ts +11 -0
- package/dist/web/form/switch.js +50 -0
- package/dist/web/form/text-area.d.ts +10 -0
- package/dist/web/form/text-area.js +31 -0
- package/dist/web/form/text-input.d.ts +10 -0
- package/dist/web/form/text-input.js +42 -0
- package/dist/web/index.d.ts +1 -0
- package/dist/web/index.js +0 -0
- package/dist/web/layout/accordion.d.ts +26 -0
- package/dist/web/layout/accordion.js +29 -0
- package/dist/web/layout/alert-modal.d.ts +41 -0
- package/dist/web/layout/alert-modal.js +71 -0
- package/dist/web/layout/card.d.ts +17 -0
- package/dist/web/layout/card.js +35 -0
- package/dist/web/layout/description.d.ts +12 -0
- package/dist/web/layout/description.js +20 -0
- package/dist/web/layout/information.d.ts +36 -0
- package/dist/web/layout/information.js +53 -0
- package/dist/web/layout/modal.d.ts +16 -0
- package/dist/web/layout/modal.js +37 -0
- package/dist/web/layout/page-frame.d.ts +37 -0
- package/dist/web/layout/page-frame.js +79 -0
- package/dist/web/layout/scroll-area.d.ts +10 -0
- package/dist/web/layout/scroll-area.js +13 -0
- package/dist/web/layout/sheet.d.ts +32 -0
- package/dist/web/layout/sheet.js +68 -0
- package/dist/web/layout/simple-dialog.d.ts +11 -0
- package/dist/web/layout/simple-dialog.js +24 -0
- package/dist/web/layout/simple-table.d.ts +49 -0
- package/dist/web/layout/simple-table.js +58 -0
- package/dist/web/layout/tabs.d.ts +26 -0
- package/dist/web/layout/tabs.js +42 -0
- package/dist/web/layout/titled-modal.d.ts +33 -0
- package/dist/web/layout/titled-modal.js +59 -0
- package/dist/web/lib/css-vars.d.ts +14 -0
- package/dist/web/lib/css-vars.js +18 -0
- package/dist/web/lib/dialog.d.ts +13 -0
- package/dist/web/lib/dialog.js +0 -0
- package/dist/web/lib/form.d.ts +2 -0
- package/dist/web/lib/form.js +3 -0
- package/dist/web/lib/labels.d.ts +27 -0
- package/dist/web/lib/labels.js +29 -0
- package/dist/web/lib/modal-open-focus.d.ts +17 -0
- package/dist/web/lib/modal-open-focus.js +44 -0
- package/dist/web/lib/restore-focus.d.ts +12 -0
- package/dist/web/lib/restore-focus.js +32 -0
- package/dist/web/lib/utils.d.ts +4 -0
- package/dist/web/lib/utils.js +10 -0
- package/dist/web/styles.css +162 -0
- package/package.json +205 -4
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { findColor } from "./catalog.js";
|
|
2
|
+
import { parseTokenColor } from "./color.js";
|
|
3
|
+
import { BODY_SIZE_MAX, BODY_SIZE_MIN, FONT_KEYS, MODES, RADIUS_MAX, RADIUS_ROLES, emptyTuning } from "./types.js";
|
|
4
|
+
import { normalize } from "./model.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
//#region src/tuning/schema.ts
|
|
8
|
+
/** エクスポート・保存の形式の名前とバージョン。形を変えたら `VERSION` を上げる。 */
|
|
9
|
+
const FORMAT = "love3-tuning";
|
|
10
|
+
const VERSION = 1;
|
|
11
|
+
/** 保存先(localStorage)のキー。 */
|
|
12
|
+
const STORAGE_KEY = "love3-preview:tuning";
|
|
13
|
+
const colorMap = z.record(z.string(), z.string());
|
|
14
|
+
const radiusPx = z.number().min(0, { error: "角丸は 0 以上です" }).max(64, { error: `角丸は ${64}px 以下です` });
|
|
15
|
+
const fontValue = z.string();
|
|
16
|
+
/**
|
|
17
|
+
* 形式の骨組み。**知らない項目は断る**(`strictObject`)。トークン名・色の中身など、DS に依存する検査は `parseTuning` が足す。
|
|
18
|
+
* 全部省略できる(既定との差分だけを書く)。
|
|
19
|
+
*/
|
|
20
|
+
const envelope = z.strictObject({
|
|
21
|
+
format: z.literal(FORMAT, { error: (issue) => `format は "${FORMAT}" にしてください(実際: ${show(issue.input)})` }),
|
|
22
|
+
version: z.literal(1, { error: (issue) => `version は ${1} だけに対応しています(実際: ${show(issue.input)})` }),
|
|
23
|
+
tokens: z.strictObject({
|
|
24
|
+
light: colorMap.optional(),
|
|
25
|
+
dark: colorMap.optional()
|
|
26
|
+
}).optional(),
|
|
27
|
+
radius: z.strictObject({
|
|
28
|
+
control: radiusPx.optional(),
|
|
29
|
+
surface: radiusPx.optional()
|
|
30
|
+
}).optional(),
|
|
31
|
+
semantic: z.record(z.string(), z.string()).optional(),
|
|
32
|
+
font: z.strictObject({
|
|
33
|
+
geist: fontValue.optional(),
|
|
34
|
+
geistMono: fontValue.optional(),
|
|
35
|
+
jp: fontValue.optional(),
|
|
36
|
+
bodySize: z.number().min(8, { error: `本文サイズは ${8}px 以上です` }).max(32, { error: `本文サイズは ${32}px 以下です` }).optional()
|
|
37
|
+
}).optional(),
|
|
38
|
+
shadow: z.enum([
|
|
39
|
+
"default",
|
|
40
|
+
"none",
|
|
41
|
+
"preset"
|
|
42
|
+
], { error: (issue) => `shadow は "default"・"none"・"preset" のどれかです(実際: ${show(issue.input)})` }).optional()
|
|
43
|
+
});
|
|
44
|
+
function show(value) {
|
|
45
|
+
return typeof value === "string" ? `"${value}"` : JSON.stringify(value) ?? String(value);
|
|
46
|
+
}
|
|
47
|
+
/** 取り込む JSON の大きさの上限(バイト)。貼り付け・ファイルとも。 */
|
|
48
|
+
const MAX_IMPORT_BYTES = 262144;
|
|
49
|
+
/** 1 つの表(`tokens.light` / `tokens.dark` / `semantic`)の項目数の上限。調整できる名前は 50 に満たない。 */
|
|
50
|
+
const MAX_ENTRIES = 200;
|
|
51
|
+
/** 断る理由を、ここまで並べる(残りは「ほか N 件」)。 */
|
|
52
|
+
const MAX_REASONS = 20;
|
|
53
|
+
const BYTE_ENCODER = new TextEncoder();
|
|
54
|
+
/** 取り込もうとしている文字が、上限(`MAX_IMPORT_BYTES`)を超えるか。 */
|
|
55
|
+
function tooLarge(text) {
|
|
56
|
+
return text.length > 262144 || BYTE_ENCODER.encode(text).length > 262144;
|
|
57
|
+
}
|
|
58
|
+
const TOO_LARGE_REASON = `大きすぎます(${MAX_IMPORT_BYTES / 1024} KB まで)`;
|
|
59
|
+
/**
|
|
60
|
+
* 書体の値(CSS の font-family)の**許可リスト**。次の部品を、カンマ区切りに並べたものだけを通す:
|
|
61
|
+
* - 閉じたクォート付きの名前(`"Geist Variable"` / `'Yu Gothic'`)。中に `\` `;` `{` `}` `<` `>` と改行は置かない
|
|
62
|
+
* - 素の名前(文字・数字・`-`・`_`・空白。非 ASCII も可。`sans-serif` `ui-monospace` など)
|
|
63
|
+
* - `var(--font-…)` の参照(`--font-mono: var(--font-geist-mono)` のような既定の形)
|
|
64
|
+
*
|
|
65
|
+
* **クォートの外の括弧(`(` `)` `[` `]`)と閉じていないクォートは断る**。通すと、`;` や `}` が括弧・引用符に飲まれて、
|
|
66
|
+
* 後ろの宣言や `.dark {}` のブロック全体が消える(`Arial(` の再現)。理由を返す(安全なら undefined)。
|
|
67
|
+
*/
|
|
68
|
+
function unreadable(value, index) {
|
|
69
|
+
return `書体の「${value.slice(index, index + 12).trim()}」から先を読めません。使えるのは、閉じたクォート付きの名前・素の名前(英数字・-・_・空白)・カンマ・var(--font-…) だけです(括弧 ( ) [ ] ; や閉じていない引用符は使えません)`;
|
|
70
|
+
}
|
|
71
|
+
function fontProblem(value) {
|
|
72
|
+
if (value.trim() === "") return "書体が空です。既定に戻すときは、その項目を空にするか「既定へ」を押してください";
|
|
73
|
+
if (value.length > 400) return "書体が長すぎます(400 文字まで)";
|
|
74
|
+
if (/url\s*\(|@import|expression\s*\(/i.test(value)) return "書体に url()・@import は使えません";
|
|
75
|
+
const part = /\s*(?:"[^"\\\n\r;{}<>]*"|'[^'\\\n\r;{}<>]*'|var\(\s*--font-[a-z0-9-]+\s*\)|[\p{L}\p{N}_ -]+)\s*/uy;
|
|
76
|
+
let index = 0;
|
|
77
|
+
for (;;) {
|
|
78
|
+
part.lastIndex = index;
|
|
79
|
+
const match = part.exec(value);
|
|
80
|
+
if (match === null || match[0].trim() === "") return unreadable(value, index);
|
|
81
|
+
index = part.lastIndex;
|
|
82
|
+
if (index >= value.length) return void 0;
|
|
83
|
+
if (value[index] !== ",") return unreadable(value, index);
|
|
84
|
+
index += 1;
|
|
85
|
+
if (index >= value.length) return "書体の最後がカンマで終わっています";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* エクスポートの JSON(文字列)を読んで検証する。**1 つでも不正なら全体を断る(部分適用しない)**。
|
|
90
|
+
* 断る理由は、見つけたものを全部、項目の場所つきの日本語で返す。
|
|
91
|
+
*
|
|
92
|
+
* 断るもの: JSON として読めない・`format` / `version` が違う・知らない項目・未知のトークン名(`--名前`)・
|
|
93
|
+
* 不正な色・ライト・ダーク共通のトークン(ブランド色)のダークの指定・未知のセマンティックトークン・選べない参照先・
|
|
94
|
+
* 範囲外の角丸と本文サイズ・差し込めない書体。成功したら、既定と同じ項目を落とした形(`normalize`)で返す。
|
|
95
|
+
*/
|
|
96
|
+
function parseTuning(text, catalog) {
|
|
97
|
+
if (tooLarge(text)) return {
|
|
98
|
+
ok: false,
|
|
99
|
+
reasons: [TOO_LARGE_REASON]
|
|
100
|
+
};
|
|
101
|
+
let json;
|
|
102
|
+
try {
|
|
103
|
+
json = JSON.parse(text);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
reasons: [`JSON として読めません(${error instanceof Error ? error.message : String(error)})`]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const result = envelope.safeParse(json);
|
|
111
|
+
if (!result.success) {
|
|
112
|
+
const reasons = result.error.issues.map((issue) => describe(issue.path, describeIssue(issue)));
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
reasons: reasons.length > 20 ? [...reasons.slice(0, 20), `ほか ${reasons.length - 20} 件`] : reasons
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const data = result.data;
|
|
119
|
+
const reasons = [];
|
|
120
|
+
const tuning = emptyTuning();
|
|
121
|
+
for (const [where, table, message] of [
|
|
122
|
+
[
|
|
123
|
+
["tokens", "light"],
|
|
124
|
+
rawTable(json, ["tokens", "light"]),
|
|
125
|
+
"未知のトークン名です(--__proto__)"
|
|
126
|
+
],
|
|
127
|
+
[
|
|
128
|
+
["tokens", "dark"],
|
|
129
|
+
rawTable(json, ["tokens", "dark"]),
|
|
130
|
+
"未知のトークン名です(--__proto__)"
|
|
131
|
+
],
|
|
132
|
+
[
|
|
133
|
+
["semantic"],
|
|
134
|
+
rawTable(json, ["semantic"]),
|
|
135
|
+
"未知のセマンティックトークンです(--__proto__)"
|
|
136
|
+
]
|
|
137
|
+
]) if (table !== void 0 && Object.hasOwn(table, "__proto__")) reasons.push(describe([...where, "__proto__"], message));
|
|
138
|
+
for (const mode of MODES) {
|
|
139
|
+
const entries = Object.entries(data.tokens?.[mode] ?? {});
|
|
140
|
+
if (entries.length > 200) {
|
|
141
|
+
reasons.push(describe(["tokens", mode], `項目が多すぎます(${entries.length} 個。${200} 個まで)`));
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
for (const [name, value] of entries) {
|
|
145
|
+
const where = [
|
|
146
|
+
"tokens",
|
|
147
|
+
mode,
|
|
148
|
+
name
|
|
149
|
+
];
|
|
150
|
+
const color = findColor(catalog, name);
|
|
151
|
+
if (color === void 0) reasons.push(describe(where, `未知のトークン名です(--${name})。調整できる色は ${catalog.colors.map((c) => c.name).join("・")}`));
|
|
152
|
+
else if (mode === "dark" && !color.hasDark) reasons.push(describe(where, `--${name} はライト・ダーク共通です。light に書いてください`));
|
|
153
|
+
else {
|
|
154
|
+
const parsed = parseTokenColor(value, { alpha: color.alpha });
|
|
155
|
+
if (parsed.ok) tuning.tokens[mode][name] = parsed.value;
|
|
156
|
+
else reasons.push(describe(where, parsed.reason));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const semanticEntries = Object.entries(data.semantic ?? {});
|
|
161
|
+
if (semanticEntries.length > 200) {
|
|
162
|
+
reasons.push(describe(["semantic"], `項目が多すぎます(${semanticEntries.length} 個。${200} 個まで)`));
|
|
163
|
+
semanticEntries.length = 0;
|
|
164
|
+
}
|
|
165
|
+
for (const [name, ref] of semanticEntries) {
|
|
166
|
+
const where = ["semantic", name];
|
|
167
|
+
if (!Object.hasOwn(catalog.semantic, name)) reasons.push(describe(where, `未知のセマンティックトークンです(--${name})`));
|
|
168
|
+
else if (!catalog.refOptions.includes(ref)) reasons.push(describe(where, `参照先「${ref}」は選べません。選べるのは ${catalog.refOptions.join("・")}`));
|
|
169
|
+
else tuning.semantic[name] = ref;
|
|
170
|
+
}
|
|
171
|
+
for (const role of RADIUS_ROLES) {
|
|
172
|
+
const px = data.radius?.[role];
|
|
173
|
+
if (px !== void 0) tuning.radius[role] = px;
|
|
174
|
+
}
|
|
175
|
+
for (const key of FONT_KEYS) {
|
|
176
|
+
const value = data.font?.[key];
|
|
177
|
+
if (value === void 0) continue;
|
|
178
|
+
const problem = fontProblem(value);
|
|
179
|
+
if (problem === void 0) tuning.font[key] = value;
|
|
180
|
+
else reasons.push(describe(["font", key], problem));
|
|
181
|
+
}
|
|
182
|
+
if (data.font?.bodySize !== void 0) tuning.font.bodySize = data.font.bodySize;
|
|
183
|
+
tuning.shadow = data.shadow ?? "default";
|
|
184
|
+
if (reasons.length > 20) return {
|
|
185
|
+
ok: false,
|
|
186
|
+
reasons: [...reasons.slice(0, 20), `ほか ${reasons.length - 20} 件`]
|
|
187
|
+
};
|
|
188
|
+
if (reasons.length > 0) return {
|
|
189
|
+
ok: false,
|
|
190
|
+
reasons
|
|
191
|
+
};
|
|
192
|
+
return {
|
|
193
|
+
ok: true,
|
|
194
|
+
tuning: normalize(tuning, catalog)
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/** 差分だけのエクスポート用オブジェクト(空の節は省く)。 */
|
|
198
|
+
function toExportObject(tuning, catalog) {
|
|
199
|
+
const n = normalize(tuning, catalog);
|
|
200
|
+
const out = {
|
|
201
|
+
format: FORMAT,
|
|
202
|
+
version: 1
|
|
203
|
+
};
|
|
204
|
+
if (Object.keys(n.tokens.light).length > 0 || Object.keys(n.tokens.dark).length > 0) out.tokens = {
|
|
205
|
+
...Object.keys(n.tokens.light).length > 0 ? { light: n.tokens.light } : {},
|
|
206
|
+
...Object.keys(n.tokens.dark).length > 0 ? { dark: n.tokens.dark } : {}
|
|
207
|
+
};
|
|
208
|
+
if (Object.keys(n.radius).length > 0) out.radius = n.radius;
|
|
209
|
+
if (Object.keys(n.semantic).length > 0) out.semantic = n.semantic;
|
|
210
|
+
if (Object.keys(n.font).length > 0) out.font = n.font;
|
|
211
|
+
if (n.shadow !== "default") out.shadow = n.shadow;
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
/** エクスポート・保存の JSON(既定との差分だけ。整形つき)。 */
|
|
215
|
+
function toJson(tuning, catalog) {
|
|
216
|
+
return JSON.stringify(toExportObject(tuning, catalog), null, 2);
|
|
217
|
+
}
|
|
218
|
+
/** 読んだ JSON の、決まった道筋にある表(オブジェクト)。無ければ undefined。 */
|
|
219
|
+
function rawTable(json, path) {
|
|
220
|
+
let current = json;
|
|
221
|
+
for (const key of path) {
|
|
222
|
+
if (typeof current !== "object" || current === null || !Object.hasOwn(current, key)) return void 0;
|
|
223
|
+
current = Reflect.get(current, key);
|
|
224
|
+
}
|
|
225
|
+
return typeof current === "object" && current !== null ? current : void 0;
|
|
226
|
+
}
|
|
227
|
+
function describe(path, message) {
|
|
228
|
+
return path.length === 0 ? message : `${path.map(String).join(".")}: ${message}`;
|
|
229
|
+
}
|
|
230
|
+
function describeIssue(issue) {
|
|
231
|
+
switch (issue.code) {
|
|
232
|
+
case "unrecognized_keys": return `知らない項目があります(${issue.keys.join("・")})`;
|
|
233
|
+
case "invalid_type": return issue.path.length === 0 ? "最上位が JSON のオブジェクトではありません" : `型が違います(期待: ${expectedType(issue.expected)})`;
|
|
234
|
+
default: return issue.message;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function expectedType(expected) {
|
|
238
|
+
return {
|
|
239
|
+
string: "文字",
|
|
240
|
+
number: "数",
|
|
241
|
+
object: "オブジェクト",
|
|
242
|
+
boolean: "真偽値"
|
|
243
|
+
}[expected] ?? expected;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
//#endregion
|
|
247
|
+
export { FORMAT, MAX_ENTRIES, MAX_IMPORT_BYTES, MAX_REASONS, STORAGE_KEY, TOO_LARGE_REASON, VERSION, fontProblem, parseTuning, toExportObject, toJson, tooLarge };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
//#region src/tuning/types.d.ts
|
|
2
|
+
/** ライト・ダーク。 */
|
|
3
|
+
export type Mode = "light" | "dark";
|
|
4
|
+
export declare const MODES: readonly Mode[];
|
|
5
|
+
/**
|
|
6
|
+
* 角丸の役割(`--radius-control` / `--radius-surface`。部品は `rounded-control` / `rounded-surface` を使う)。
|
|
7
|
+
* - `control`: 操作する小さな要素(Button・入力・Select のトリガー・Checkbox・Badge・メニューの項目ほか)
|
|
8
|
+
* - `surface`: 面・入れ物(Card・Dialog・Sheet・Popover・Alert・Tooltip・メニューの入れ物ほか)
|
|
9
|
+
*/
|
|
10
|
+
export declare const RADIUS_ROLES: readonly ["control", "surface"];
|
|
11
|
+
export type RadiusRole = (typeof RADIUS_ROLES)[number];
|
|
12
|
+
/** 角丸(px)の上限。スライダも数値の入力も、ここまで。 */
|
|
13
|
+
export declare const RADIUS_MAX = 64;
|
|
14
|
+
/** 書体のキー(`--font-geist` / `--font-geist-mono` / `--font-jp`)。 */
|
|
15
|
+
export declare const FONT_KEYS: readonly ["geist", "geistMono", "jp"];
|
|
16
|
+
export type FontKey = (typeof FONT_KEYS)[number];
|
|
17
|
+
export declare const FONT_TOKEN: Readonly<Record<FontKey, string>>;
|
|
18
|
+
/** 本文サイズ(px)の範囲。 */
|
|
19
|
+
export declare const BODY_SIZE_MIN = 8;
|
|
20
|
+
export declare const BODY_SIZE_MAX = 32;
|
|
21
|
+
/**
|
|
22
|
+
* 影の preset。`default` は love3 の既定(`--shadow-color` を使う淡い影)。`none` は全ての影を消す。
|
|
23
|
+
* `preset` は Tailwind の既定の影に差し替える。
|
|
24
|
+
*/
|
|
25
|
+
export type ShadowPreset = "default" | "none" | "preset";
|
|
26
|
+
/**
|
|
27
|
+
* 調整の内容。**既定との差分だけ**を持つ(空 = 既定どおり)。保存・エクスポートの正本と同じ形。
|
|
28
|
+
*
|
|
29
|
+
* - `tokens`: 色のトークンの上書き。ライト・ダーク別。ブランド色(`lt-three*`)は共通なので `light` にだけ持つ
|
|
30
|
+
* - `radius`: 役割ごとの角丸(px)。`control`(操作する小さな要素)と `surface`(面・入れ物)。無ければ既定(`TuningCatalog.radius`)
|
|
31
|
+
* - `semantic`: セマンティックトークン(`background` など)の参照先(ランプ・状態・ブランドの名前)
|
|
32
|
+
* - `font`: 書体(CSS の font-family の値)と本文サイズ(px)
|
|
33
|
+
* - `shadow`: 影の preset(既定は `default` = love3 の淡い影)
|
|
34
|
+
*/
|
|
35
|
+
export interface Tuning {
|
|
36
|
+
tokens: Record<Mode, Record<string, string>>;
|
|
37
|
+
radius: Partial<Record<RadiusRole, number>>;
|
|
38
|
+
semantic: Record<string, string>;
|
|
39
|
+
font: Partial<Record<FontKey, string>> & {
|
|
40
|
+
bodySize?: number;
|
|
41
|
+
};
|
|
42
|
+
shadow: ShadowPreset;
|
|
43
|
+
}
|
|
44
|
+
export declare function emptyTuning(): Tuning;
|
|
45
|
+
/** 色のトークンの区分。 */
|
|
46
|
+
export type ColorGroup = "ramp" | "action" | "brand" | "state" | "chart";
|
|
47
|
+
export interface ColorToken {
|
|
48
|
+
name: string;
|
|
49
|
+
group: ColorGroup;
|
|
50
|
+
/** ダークで別の値を持てるか。ブランド色は共通なので持たない。 */
|
|
51
|
+
hasDark: boolean;
|
|
52
|
+
/** 半透明を許すか(scrim)。それ以外は不透明な `#rrggbb` だけ。 */
|
|
53
|
+
alpha: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 調整の対象と既定値。DS から作る(`buildCatalog`)。ロジックはこの形だけを見るので、DS に依存せず Node でテストできる。
|
|
57
|
+
*/
|
|
58
|
+
export interface TuningCatalog {
|
|
59
|
+
/** 既定の実値(`var()` を解決したもの。love3 の `tokens`)。 */
|
|
60
|
+
defaults: Readonly<Record<Mode, Readonly<Record<string, string>>>>;
|
|
61
|
+
/** 色のトークン(生のランプ・主操作・ブランド・状態・チャート)。定義の順。 */
|
|
62
|
+
colors: readonly ColorToken[];
|
|
63
|
+
/** セマンティックトークン名 → 既定の参照先(`background` → `paper`)。定義の順。 */
|
|
64
|
+
semantic: Readonly<Record<string, string>>;
|
|
65
|
+
/** セマンティックの参照先に選べる名前(不透明な色のトークン)。 */
|
|
66
|
+
refOptions: readonly string[];
|
|
67
|
+
/** 役割ごとの角丸の既定(px)。CSS の `@theme`(`--radius-control` / `--radius-surface`)から読む。 */
|
|
68
|
+
radius: Readonly<Record<RadiusRole, number>>;
|
|
69
|
+
/** 本文サイズの既定(px)と行の高さ(px)。 */
|
|
70
|
+
body: {
|
|
71
|
+
size: number;
|
|
72
|
+
lineHeight: number;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/tuning/types.ts
|
|
2
|
+
const MODES = ["light", "dark"];
|
|
3
|
+
/**
|
|
4
|
+
* 角丸の役割(`--radius-control` / `--radius-surface`。部品は `rounded-control` / `rounded-surface` を使う)。
|
|
5
|
+
* - `control`: 操作する小さな要素(Button・入力・Select のトリガー・Checkbox・Badge・メニューの項目ほか)
|
|
6
|
+
* - `surface`: 面・入れ物(Card・Dialog・Sheet・Popover・Alert・Tooltip・メニューの入れ物ほか)
|
|
7
|
+
*/
|
|
8
|
+
const RADIUS_ROLES = ["control", "surface"];
|
|
9
|
+
/** 角丸(px)の上限。スライダも数値の入力も、ここまで。 */
|
|
10
|
+
const RADIUS_MAX = 64;
|
|
11
|
+
/** 書体のキー(`--font-geist` / `--font-geist-mono` / `--font-jp`)。 */
|
|
12
|
+
const FONT_KEYS = [
|
|
13
|
+
"geist",
|
|
14
|
+
"geistMono",
|
|
15
|
+
"jp"
|
|
16
|
+
];
|
|
17
|
+
const FONT_TOKEN = {
|
|
18
|
+
geist: "font-geist",
|
|
19
|
+
geistMono: "font-geist-mono",
|
|
20
|
+
jp: "font-jp"
|
|
21
|
+
};
|
|
22
|
+
/** 本文サイズ(px)の範囲。 */
|
|
23
|
+
const BODY_SIZE_MIN = 8;
|
|
24
|
+
const BODY_SIZE_MAX = 32;
|
|
25
|
+
function emptyTuning() {
|
|
26
|
+
return {
|
|
27
|
+
tokens: {
|
|
28
|
+
light: {},
|
|
29
|
+
dark: {}
|
|
30
|
+
},
|
|
31
|
+
radius: {},
|
|
32
|
+
semantic: {},
|
|
33
|
+
font: {},
|
|
34
|
+
shadow: "default"
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { BODY_SIZE_MAX, BODY_SIZE_MIN, FONT_KEYS, FONT_TOKEN, MODES, RADIUS_MAX, RADIUS_ROLES, emptyTuning };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ChartCommonProps, LineChartSharedProps } from "../../shared/analytics.js";
|
|
2
|
+
import { CSSProperties } from "react";
|
|
3
|
+
//#region src/web/analytics/line-chart.d.ts
|
|
4
|
+
export type LineChartProps = ChartCommonProps & LineChartSharedProps & {
|
|
5
|
+
/** 外側の要素に渡す style(native の `style: ViewStyle` に当たる)。 */
|
|
6
|
+
style?: CSSProperties;
|
|
7
|
+
className?: string;
|
|
8
|
+
/** 縦軸の目盛りとツールチップの値の整形(web の追加。native の `LineChart` は OS のチャートが整形する)。 */
|
|
9
|
+
formatValue?: (value: number) => string;
|
|
10
|
+
/** データが空のときの文言(web の追加)。 */
|
|
11
|
+
emptyText?: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* 単一系列の折れ線。native の `analytics/line-chart` と同じ API(`shared/analytics.ts`)。**`data` と `formatValue` は参照を安定させて渡す**(`MultiLineChart` と同じ)。
|
|
15
|
+
* 中身は 1 本の系列の `MultiLineChart`(凡例は出さない)。複数の系列を重ねるなら `MultiLineChart`。
|
|
16
|
+
*
|
|
17
|
+
* native の `LineChart` は iOS の SwiftUI Charts で、iOS 以外では `fallbackText` を出す。web は常に描くので `fallbackText` を持たない。
|
|
18
|
+
*/
|
|
19
|
+
export declare function LineChart({ data, color, ...rest }: LineChartProps): import("react").JSX.Element;
|
|
20
|
+
//#endregion
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { LINE_CHART_SINGLE_SERIES_LABEL } from "../lib/labels.js";
|
|
2
|
+
import { MultiLineChart } from "./multi-line-chart.js";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
|
|
6
|
+
//#region src/web/analytics/line-chart.tsx
|
|
7
|
+
/**
|
|
8
|
+
* 単一系列の折れ線。native の `analytics/line-chart` と同じ API(`shared/analytics.ts`)。**`data` と `formatValue` は参照を安定させて渡す**(`MultiLineChart` と同じ)。
|
|
9
|
+
* 中身は 1 本の系列の `MultiLineChart`(凡例は出さない)。複数の系列を重ねるなら `MultiLineChart`。
|
|
10
|
+
*
|
|
11
|
+
* native の `LineChart` は iOS の SwiftUI Charts で、iOS 以外では `fallbackText` を出す。web は常に描くので `fallbackText` を持たない。
|
|
12
|
+
*/
|
|
13
|
+
function LineChart({ data, color = "var(--chart-1)", ...rest }) {
|
|
14
|
+
const series = useMemo(() => [{
|
|
15
|
+
key: "line",
|
|
16
|
+
label: "値",
|
|
17
|
+
points: data,
|
|
18
|
+
color
|
|
19
|
+
}], [data, color]);
|
|
20
|
+
return /* @__PURE__ */ jsx(MultiLineChart, {
|
|
21
|
+
legend: false,
|
|
22
|
+
series,
|
|
23
|
+
...rest
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { LineChart };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { MetricCardSharedProps } from "../../shared/analytics.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
//#region src/web/analytics/metric-card.d.ts
|
|
4
|
+
export type MetricCardProps = Omit<MetricCardSharedProps, "label"> & {
|
|
5
|
+
/** 指標の名前。文字列のほか、ヘルプのトリガー付きの見出しなど要素も渡せる(native は文字列)。 */
|
|
6
|
+
label: ReactNode;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* 面付きの単一指標(ラベル + 大型の等幅数値 + 補助)。native の `analytics/metric-card` と同じ API(`shared/analytics.ts`)。
|
|
10
|
+
* raised 面 + 1px の薄い枠 + surface 角丸で、影は付けない。
|
|
11
|
+
*
|
|
12
|
+
* 中身は `<dl>`(ラベルが dt・値と補助が dd)で、それ自体で成り立つ(グリッドの親は `<div>` でよい)。
|
|
13
|
+
* 値は 1 行に収め、入りきらないときは省略する(折り返さない)。
|
|
14
|
+
*
|
|
15
|
+
* `trend` が正なら値を `state-success`、負なら `state-error` で塗る。**色だけで伝えない**ので、増減は `value` に符号を含める。
|
|
16
|
+
*/
|
|
17
|
+
export declare function MetricCard({ label, value, subValue, trend, className }: MetricCardProps): import("react").JSX.Element;
|
|
18
|
+
//#endregion
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/web/analytics/metric-card.tsx
|
|
5
|
+
/**
|
|
6
|
+
* 面付きの単一指標(ラベル + 大型の等幅数値 + 補助)。native の `analytics/metric-card` と同じ API(`shared/analytics.ts`)。
|
|
7
|
+
* raised 面 + 1px の薄い枠 + surface 角丸で、影は付けない。
|
|
8
|
+
*
|
|
9
|
+
* 中身は `<dl>`(ラベルが dt・値と補助が dd)で、それ自体で成り立つ(グリッドの親は `<div>` でよい)。
|
|
10
|
+
* 値は 1 行に収め、入りきらないときは省略する(折り返さない)。
|
|
11
|
+
*
|
|
12
|
+
* `trend` が正なら値を `state-success`、負なら `state-error` で塗る。**色だけで伝えない**ので、増減は `value` に符号を含める。
|
|
13
|
+
*/
|
|
14
|
+
function MetricCard({ label, value, subValue, trend, className }) {
|
|
15
|
+
const tone = trend === void 0 || trend === 0 ? void 0 : trend > 0 ? "up" : "down";
|
|
16
|
+
return /* @__PURE__ */ jsxs("dl", {
|
|
17
|
+
"data-slot": "metric-card",
|
|
18
|
+
className: cn("bg-card border-border rounded-surface flex min-w-0 flex-col gap-1 border p-4", className),
|
|
19
|
+
children: [
|
|
20
|
+
/* @__PURE__ */ jsx("dt", {
|
|
21
|
+
className: "text-muted-foreground flex items-center gap-1 text-xs font-medium tracking-[0.06em] uppercase",
|
|
22
|
+
children: label
|
|
23
|
+
}),
|
|
24
|
+
/* @__PURE__ */ jsx("dd", {
|
|
25
|
+
"data-slot": "metric-card-value",
|
|
26
|
+
"data-trend": tone,
|
|
27
|
+
title: value,
|
|
28
|
+
className: cn("min-w-0 truncate font-mono text-xl font-bold tabular-nums sm:text-2xl", tone === "up" && "text-state-success", tone === "down" && "text-state-error"),
|
|
29
|
+
children: value
|
|
30
|
+
}),
|
|
31
|
+
subValue ? /* @__PURE__ */ jsx("dd", {
|
|
32
|
+
className: "text-muted-foreground min-w-0 truncate font-mono text-xs tabular-nums",
|
|
33
|
+
children: subValue
|
|
34
|
+
}) : null
|
|
35
|
+
]
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
export { MetricCard };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ChartCommonProps, MultiLineChartSharedProps } from "../../shared/analytics.js";
|
|
2
|
+
import { CSSProperties } from "react";
|
|
3
|
+
//#region src/web/analytics/multi-line-chart.d.ts
|
|
4
|
+
export type MultiLineChartProps = ChartCommonProps & MultiLineChartSharedProps & {
|
|
5
|
+
/** 外側の要素に渡す style(native の `style: ViewStyle` に当たる)。 */
|
|
6
|
+
style?: CSSProperties;
|
|
7
|
+
className?: string;
|
|
8
|
+
/** 凡例を出すか(既定は出す。native の `MultiLineChart` は系列が 1 本でも出す)。 */
|
|
9
|
+
legend?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* 読み上げ用の隠した表(系列・見出し・値)を出すか(既定は出す)。
|
|
12
|
+
* 図形の名前(`accessibilityLabel`)だけでは正確な値が読めないので、同じ値を HTML の表でも持つ。
|
|
13
|
+
* 値は `formatValue` と同じ整形を通す。**1 系列が 60 点を超えるときは、DOM が膨らまないよう等間隔に 60 点へ抜き出し**、
|
|
14
|
+
* その旨を表の前に一文で添える(全点の正確な値が要るなら、使う側の表や CSV で出す)。
|
|
15
|
+
*/
|
|
16
|
+
table?: boolean;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* 複数系列の折れ線。native の `analytics/multi-line-chart` と同じ API(`shared/analytics.ts`)。
|
|
20
|
+
* 描画は `@tanstack/charts`(SVG)。目盛りの罫線は 1px の細い線で、色は `--chart-1〜5`・ツールチップは overlay 面 + 影。
|
|
21
|
+
*
|
|
22
|
+
* - **数でない点は除く**: `y`(と `t`)が有限でない点(NaN・Infinity)は、組む前に取り除く(読み上げ用の表にも出さず、
|
|
23
|
+
* 除いた件数を表に添える)。全部除かれたら `emptyText`
|
|
24
|
+
* - 全系列が `t` を持つなら **時間で横位置を決める**。持たないものが 1 つでもあれば、系列ごとに並び順で等間隔に置く
|
|
25
|
+
* (単一系列ならそれでよい。刻みの違う系列を重ねるなら `t` を入れる)。1 点だけの系列は点(dot)で描く
|
|
26
|
+
* - 横軸の目盛りは先頭・中・末の位置の 3 つまで(点が少なければ重ならない数)。**見出しは、先頭の系列の最も近い点のもの**
|
|
27
|
+
* (時間で置くとき、2 本目以降の系列にしかない時刻は、見出しにならない)。点の位置に置く
|
|
28
|
+
* - 縦軸の目盛りは上・中・下の 3 本。全点が同じ値でも線が潰れないよう、幅ゼロのときだけ上下に逃がす
|
|
29
|
+
* - `formatValue` の既定は、**データの幅に応じた桁数**(0〜1 の値でも目盛りが区別できる)。目盛り・ツールチップ・読み上げの表が
|
|
30
|
+
* 同じ整形を通る。渡すときは **関数の参照を安定させる**(毎回新しい関数だと、描画のたびにチャートを組み直し、ツールチップが閉じる)。
|
|
31
|
+
* `series` も同じ(親が作り直さない参照を渡す)
|
|
32
|
+
* - ホバー・フォーカスで、その位置の各系列の点の値をツールチップに出す(キーボードでも矢印で移れる)。系列名と色を添える。
|
|
33
|
+
* **系列の点がある範囲(端は点の間隔の半分まで)の外では、その系列の行を出さない**(遠い点の値を、その位置の値のように見せない)。
|
|
34
|
+
* 点の見出しがタイトルと違うときは、行に添える
|
|
35
|
+
* - 読み上げ: 図形は `role="img"` で、`accessibilityLabel`(要約)が名前。正確な値は隠した表でも持つ(`table`)
|
|
36
|
+
* - **色だけで系列を見分けさせない**: 凡例に系列名があり、副系列は破線にできる
|
|
37
|
+
*/
|
|
38
|
+
export declare function MultiLineChart({ series, height, formatValue, emptyText, accessibilityLabel, style, className, legend, table }: MultiLineChartProps): import("react").JSX.Element;
|
|
39
|
+
//#endregion
|