@qijenchen/design-system 0.1.0-beta.3
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/package.json +93 -0
- package/src/README.md +32 -0
- package/src/components/Accordion/accordion.tsx +104 -0
- package/src/components/Alert/alert.tsx +188 -0
- package/src/components/AppShell/_demo-helpers.tsx +198 -0
- package/src/components/AppShell/app-shell.tsx +364 -0
- package/src/components/AspectRatio/aspect-ratio.tsx +58 -0
- package/src/components/Avatar/avatar.tsx +368 -0
- package/src/components/Badge/badge.tsx +104 -0
- package/src/components/Breadcrumb/breadcrumb.tsx +609 -0
- package/src/components/BulkActionBar/bulk-action-bar.tsx +156 -0
- package/src/components/Button/button-group.tsx +96 -0
- package/src/components/Button/button.tsx +539 -0
- package/src/components/Calendar/calendar.tsx +411 -0
- package/src/components/Carousel/carousel.tsx +371 -0
- package/src/components/Chart/chart.tsx +376 -0
- package/src/components/Checkbox/checkbox-group.tsx +94 -0
- package/src/components/Checkbox/checkbox.tsx +237 -0
- package/src/components/Chip/chip.tsx +359 -0
- package/src/components/CircularProgress/circular-progress.tsx +204 -0
- package/src/components/Coachmark/coachmark.tsx +255 -0
- package/src/components/Combobox/combobox.tsx +826 -0
- package/src/components/Command/command.tsx +187 -0
- package/src/components/DataTable/active-editor-controller.ts +72 -0
- package/src/components/DataTable/cell-registry.tsx +520 -0
- package/src/components/DataTable/column-types.ts +180 -0
- package/src/components/DataTable/data-table-column-visibility-panel.tsx +261 -0
- package/src/components/DataTable/data-table-filter-panel.tsx +813 -0
- package/src/components/DataTable/data-table-interaction-layer.tsx +483 -0
- package/src/components/DataTable/data-table-sort-manager.tsx +210 -0
- package/src/components/DataTable/data-table.css +165 -0
- package/src/components/DataTable/data-table.tsx +2924 -0
- package/src/components/DataTable/filter-operators.ts +225 -0
- package/src/components/DataTable/filter-tree.ts +313 -0
- package/src/components/DataTable/lib/column-meta.ts +79 -0
- package/src/components/DateGrid/date-grid.tsx +209 -0
- package/src/components/DatePicker/date-picker.tsx +1114 -0
- package/src/components/DescriptionList/description-list.tsx +141 -0
- package/src/components/Dialog/dialog.tsx +267 -0
- package/src/components/DropdownMenu/dropdown-menu.tsx +475 -0
- package/src/components/Empty/empty.tsx +108 -0
- package/src/components/Field/field-context.ts +136 -0
- package/src/components/Field/field-types.ts +52 -0
- package/src/components/Field/field-wrapper.tsx +348 -0
- package/src/components/Field/field.tsx +535 -0
- package/src/components/FieldControlGroup/field-control-group.tsx +136 -0
- package/src/components/FileItem/file-item.tsx +322 -0
- package/src/components/FileUpload/file-upload.tsx +326 -0
- package/src/components/FileViewer/file-viewer-types.ts +76 -0
- package/src/components/FileViewer/file-viewer.tsx +1065 -0
- package/src/components/FileViewer/image-renderer.tsx +256 -0
- package/src/components/HoverCard/hover-card.tsx +79 -0
- package/src/components/Input/input.tsx +233 -0
- package/src/components/LinkInput/link-input.tsx +304 -0
- package/src/components/Menu/menu-item.tsx +334 -0
- package/src/components/NameCard/name-card.tsx +319 -0
- package/src/components/Notice/notice.tsx +196 -0
- package/src/components/NumberInput/number-input.tsx +203 -0
- package/src/components/OverflowIndicator/overflow-indicator.tsx +156 -0
- package/src/components/PeoplePicker/avatar-stack-overflow.ts +100 -0
- package/src/components/PeoplePicker/people-picker-helpers.ts +76 -0
- package/src/components/PeoplePicker/people-picker.tsx +455 -0
- package/src/components/PeoplePicker/person-display.tsx +358 -0
- package/src/components/Popover/popover.tsx +183 -0
- package/src/components/ProgressBar/progress-bar.tsx +157 -0
- package/src/components/README.md +58 -0
- package/src/components/RadioGroup/radio-group.tsx +261 -0
- package/src/components/Rating/rating.tsx +295 -0
- package/src/components/ScrollArea/scroll-area.tsx +110 -0
- package/src/components/SegmentedControl/segmented-control.tsx +304 -0
- package/src/components/Select/select.tsx +658 -0
- package/src/components/SelectMenu/select-menu.tsx +430 -0
- package/src/components/SelectionControl/selection-item.tsx +261 -0
- package/src/components/Separator/separator.tsx +48 -0
- package/src/components/Sheet/sheet.tsx +240 -0
- package/src/components/Sidebar/sidebar.tsx +1280 -0
- package/src/components/Skeleton/skeleton.tsx +35 -0
- package/src/components/Slider/slider.tsx +158 -0
- package/src/components/Steps/steps.tsx +850 -0
- package/src/components/Switch/switch.tsx +285 -0
- package/src/components/Tabs/tabs.tsx +515 -0
- package/src/components/Tag/tag.tsx +246 -0
- package/src/components/Textarea/textarea.tsx +280 -0
- package/src/components/TimePicker/time-columns.tsx +260 -0
- package/src/components/TimePicker/time-picker.tsx +419 -0
- package/src/components/Toast/toast.tsx +129 -0
- package/src/components/Tooltip/tooltip.tsx +68 -0
- package/src/components/TreeView/tree-view.tsx +1031 -0
- package/src/hooks/use-controllable.ts +40 -0
- package/src/hooks/use-is-narrow-viewport.ts +19 -0
- package/src/hooks/use-is-touch-device.ts +21 -0
- package/src/hooks/use-overflow-items.ts +256 -0
- package/src/index.ts +85 -0
- package/src/lib/README.md +82 -0
- package/src/lib/drag-visual.ts +272 -0
- package/src/lib/i18n/README.md +60 -0
- package/src/lib/i18n/i18n-context.tsx +129 -0
- package/src/lib/multi-select-ordering.ts +61 -0
- package/src/lib/utils.ts +93 -0
- package/src/patterns/README.md +67 -0
- package/src/patterns/element-anatomy/item-anatomy.tsx +744 -0
- package/src/patterns/header-canonical/chrome-header.tsx +175 -0
- package/src/patterns/header-canonical/header-canonical.css +27 -0
- package/src/patterns/horizontal-overflow/horizontal-overflow.tsx +217 -0
- package/src/patterns/overlay-surface/overlay-surface.tsx +191 -0
- package/src/patterns/resize-handle/resize-handle.tsx +188 -0
- package/src/stories-helpers/anatomy/anatomy-utils.tsx +64 -0
- package/src/tokens/README.md +53 -0
- package/src/tokens/color/primitives.css +429 -0
- package/src/tokens/color/semantic.css +539 -0
- package/src/tokens/elevation/overlay-geometry.ts +13 -0
- package/src/tokens/layoutSpace/layoutSpace.css +36 -0
- package/src/tokens/motion/motion.css +30 -0
- package/src/tokens/motion/motion.ts +17 -0
- package/src/tokens/opacity/opacity.css +23 -0
- package/src/tokens/radius/radius.css +19 -0
- package/src/tokens/typography/typography.css +118 -0
- package/src/tokens/uiSize/icon-size.ts +52 -0
- package/src/tokens/uiSize/uiSize.css +125 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "@/lib/utils"
|
|
3
|
+
|
|
4
|
+
const Skeleton = React.forwardRef<
|
|
5
|
+
HTMLDivElement,
|
|
6
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
7
|
+
>(({ className, ...props }, ref) => (
|
|
8
|
+
<div
|
|
9
|
+
ref={ref}
|
|
10
|
+
className={cn("animate-pulse rounded-md bg-muted motion-reduce:animate-none", className)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
))
|
|
14
|
+
Skeleton.displayName = "Skeleton"
|
|
15
|
+
|
|
16
|
+
// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)
|
|
17
|
+
// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
|
|
18
|
+
export const skeletonMeta = {
|
|
19
|
+
component: 'Skeleton',
|
|
20
|
+
family: null, // non-family composite / overlay / layout
|
|
21
|
+
variants: {
|
|
22
|
+
|
|
23
|
+
},
|
|
24
|
+
sizes: {
|
|
25
|
+
|
|
26
|
+
},
|
|
27
|
+
states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
|
|
28
|
+
tokens: {
|
|
29
|
+
bg: ['bg-muted'],
|
|
30
|
+
fg: [],
|
|
31
|
+
ring: [],
|
|
32
|
+
},
|
|
33
|
+
} as const
|
|
34
|
+
|
|
35
|
+
export { Skeleton }
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as SliderPrimitive from '@radix-ui/react-slider'
|
|
3
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Slider — 數值範圍選取器
|
|
8
|
+
*
|
|
9
|
+
* 基於 Radix Slider primitive,橋接設計系統 token。詳細設計原則見 `slider.spec.md`。
|
|
10
|
+
*
|
|
11
|
+
* ── 核心設計 ──
|
|
12
|
+
* 1. **視覺單一**:track 厚度、thumb 直徑、ring 尺寸都是固定值,不隨 `size` 變
|
|
13
|
+
* 2. **`size` 只控容器外高**:對齊 Field family 的 `h-field-*` tier,讓 Slider 能跟
|
|
14
|
+
* Input / NumberInput / Select 在 Field 內並排對齊
|
|
15
|
+
* 3. **Range mode 免費**:Radix 原生支援 `value: number[]`,傳多值自動多 thumb
|
|
16
|
+
* 4. **Hover / active 用 elevation 陰影**:不用色變,避免暗示「這是 button」
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const sliderRootVariants = cva(
|
|
20
|
+
// 容器外層:水平置中 + relative(Radix 會絕對定位內部元素)
|
|
21
|
+
// flex items-center 讓 track+thumb 在任何 field-height 下都垂直置中
|
|
22
|
+
//
|
|
23
|
+
// ── Disabled 策略:灰階 token swap(對齊 Button / Checkbox)──
|
|
24
|
+
// Slider 的藍色 range 是美學視覺,不是 semantic state——使用者從 disabled
|
|
25
|
+
// slider 需要辨識的是 thumb 位置 + range 長度,這兩者不依賴顏色。失去藍色
|
|
26
|
+
// 沒有資訊損失。
|
|
27
|
+
//
|
|
28
|
+
// 跟 Switch 的差別:Switch 的 on/off 是純顏色差異(沒有形狀差異),所以必須
|
|
29
|
+
// 靠 opacity 保留色彩身分。Slider 的位置/長度是形狀差異,不需要保留顏色身分,
|
|
30
|
+
// 跟 Checkbox(checkmark 形狀 = semantic 載體)同類,應該走灰階。
|
|
31
|
+
//
|
|
32
|
+
// 詳細判準見 `slider.spec.md` 的「Disabled 策略」章節。
|
|
33
|
+
[
|
|
34
|
+
'relative flex items-center w-full min-w-0 touch-none select-none',
|
|
35
|
+
'data-[disabled]:cursor-not-allowed',
|
|
36
|
+
],
|
|
37
|
+
{
|
|
38
|
+
variants: {
|
|
39
|
+
size: {
|
|
40
|
+
sm: 'h-field-sm',
|
|
41
|
+
md: 'h-field-md',
|
|
42
|
+
lg: 'h-field-lg',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
defaultVariants: {
|
|
46
|
+
size: 'md',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
export interface SliderProps
|
|
52
|
+
extends Omit<React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>, 'children'>,
|
|
53
|
+
VariantProps<typeof sliderRootVariants> {}
|
|
54
|
+
|
|
55
|
+
const Slider = React.forwardRef<
|
|
56
|
+
React.ElementRef<typeof SliderPrimitive.Root>,
|
|
57
|
+
SliderProps
|
|
58
|
+
>(({ className, size, value, defaultValue, 'aria-label': ariaLabel, ...props }, ref) => {
|
|
59
|
+
// 推導要渲染幾個 thumb:controlled 用 value,uncontrolled 用 defaultValue,
|
|
60
|
+
// 都沒有時 fallback 單 thumb(Radix 預設行為)
|
|
61
|
+
const thumbCount =
|
|
62
|
+
(Array.isArray(value) && value.length) ||
|
|
63
|
+
(Array.isArray(defaultValue) && defaultValue.length) ||
|
|
64
|
+
1
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<SliderPrimitive.Root
|
|
68
|
+
ref={ref}
|
|
69
|
+
value={value}
|
|
70
|
+
defaultValue={defaultValue}
|
|
71
|
+
className={cn(sliderRootVariants({ size }), className)}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
{/*
|
|
75
|
+
Track — rest 用 bg-secondary(n-3,「微淡可辨」),disabled 用 bg-muted(n-2,退化)。
|
|
76
|
+
跟 Tag neutral / Badge low 同家族。
|
|
77
|
+
*/}
|
|
78
|
+
<SliderPrimitive.Track className={cn(
|
|
79
|
+
'relative grow overflow-hidden rounded-full h-1',
|
|
80
|
+
// Rest:bg-secondary(n-3,「微淡可辨」的 subtle fill,跟 Tag neutral / Badge low 同級)
|
|
81
|
+
// Disabled:bg-muted(n-2,「disabled-like 退化」底色)
|
|
82
|
+
'bg-secondary data-[disabled]:bg-muted',
|
|
83
|
+
)}>
|
|
84
|
+
{/*
|
|
85
|
+
Range — 填滿段。
|
|
86
|
+
|
|
87
|
+
── Range 色 = Thumb border 色(語意一致) ──
|
|
88
|
+
Rest 兩者都是 `primary`,disabled 兩者都是 `border`(n-5)。為什麼要一致?
|
|
89
|
+
Range 是「填充視覺」,Thumb border 是「thumb 的輪廓線」——視覺上 thumb
|
|
90
|
+
的 border 剛好是 range 的延續(thumb 坐落在 range 的端點上,border 跟
|
|
91
|
+
range 在色彩上融為一體,看起來像「range 包住 thumb」而不是「thumb 浮在
|
|
92
|
+
range 上」)。兩個 token 綁在一起,不論什麼 state 都一致。
|
|
93
|
+
*/}
|
|
94
|
+
<SliderPrimitive.Range
|
|
95
|
+
className={cn(
|
|
96
|
+
'absolute h-full bg-primary',
|
|
97
|
+
'data-[disabled]:bg-border',
|
|
98
|
+
)}
|
|
99
|
+
/>
|
|
100
|
+
</SliderPrimitive.Track>
|
|
101
|
+
|
|
102
|
+
{/*
|
|
103
|
+
Thumb — N 個(由 thumbCount 決定)。
|
|
104
|
+
白底 + 2px 邊框,邊框色 = Range 色(**一致綁定**):
|
|
105
|
+
- Rest: `border-primary` ↔ Range `bg-primary`
|
|
106
|
+
- Disabled: `border-border` ↔ Range `bg-border`
|
|
107
|
+
這個一致性讓 thumb border 跟 range 融為一體,看起來像「range 包住 thumb」
|
|
108
|
+
的連續視覺。thumb 的白底則是「被 range 圍住的空心洞」,讓 thumb 的位置
|
|
109
|
+
清楚浮出。不論 state,thumb border 跟 range 永遠同色。
|
|
110
|
+
|
|
111
|
+
**為什麼 thumb bg 不能改**:`bg-surface`(白)必須在 rest / disabled 都維持,
|
|
112
|
+
否則會融入 track 的 `bg-muted` 裡消失。這是之前踩過的同色融色 bug
|
|
113
|
+
(曾經寫成 `data-[disabled]:bg-muted` 讓 thumb 跟 track 完全融合)。
|
|
114
|
+
*/}
|
|
115
|
+
{Array.from({ length: thumbCount }).map((_, i) => (
|
|
116
|
+
<SliderPrimitive.Thumb
|
|
117
|
+
key={i}
|
|
118
|
+
className={cn(
|
|
119
|
+
'block h-4 w-4 shrink-0 rounded-full cursor-grab',
|
|
120
|
+
'bg-surface border-2 border-primary',
|
|
121
|
+
'transition-all duration-150',
|
|
122
|
+
// Hover:border 加深到 primary-hover + elevation 陰影
|
|
123
|
+
'hover:border-primary-hover hover:[box-shadow:var(--elevation-100)]',
|
|
124
|
+
'active:cursor-grabbing active:border-primary-hover active:[box-shadow:var(--elevation-200)]',
|
|
125
|
+
// Focus:border 加深(跟 hover 同視覺),不加 ring 或 halo
|
|
126
|
+
'outline-none focus-visible:border-primary-hover',
|
|
127
|
+
// Disabled:border 跟 Range 一起退成 border(n-5),bg 保留 bg-surface
|
|
128
|
+
'data-[disabled]:cursor-not-allowed data-[disabled]:border-border',
|
|
129
|
+
'data-[disabled]:hover:[box-shadow:none]',
|
|
130
|
+
)}
|
|
131
|
+
aria-label={ariaLabel ? (thumbCount > 1 ? `${ariaLabel} (${i + 1})` : ariaLabel) : (thumbCount > 1 ? `Thumb ${i + 1}` : undefined)}
|
|
132
|
+
/>
|
|
133
|
+
))}
|
|
134
|
+
</SliderPrimitive.Root>
|
|
135
|
+
)
|
|
136
|
+
})
|
|
137
|
+
Slider.displayName = 'Slider'
|
|
138
|
+
|
|
139
|
+
// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)
|
|
140
|
+
// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
|
|
141
|
+
export const sliderMeta = {
|
|
142
|
+
component: 'Slider',
|
|
143
|
+
family: 4,
|
|
144
|
+
variants: {},
|
|
145
|
+
sizes: {
|
|
146
|
+
sm: { px: 28, when: 'Toolbar / inline 編輯' },
|
|
147
|
+
md: { px: 36, when: '預設 — Form / cell inline edit' },
|
|
148
|
+
lg: { px: 44, when: 'Marketing / 高 touch 區' },
|
|
149
|
+
},
|
|
150
|
+
states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
|
|
151
|
+
tokens: {
|
|
152
|
+
bg: ['bg-muted', 'bg-primary', 'bg-secondary', 'bg-surface'],
|
|
153
|
+
fg: [],
|
|
154
|
+
ring: [],
|
|
155
|
+
},
|
|
156
|
+
} as const
|
|
157
|
+
|
|
158
|
+
export { Slider, sliderRootVariants }
|