@endfield-ui/react 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.
Files changed (228) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +27 -0
  3. package/README.md +768 -2
  4. package/dist/index.d.ts +3243 -0
  5. package/dist/index.js +8182 -0
  6. package/dist/styles.css +2 -0
  7. package/package.json +83 -3
  8. package/src/components/README.md +172 -0
  9. package/src/components/accordion/Accordion.tsx +168 -0
  10. package/src/components/accordion/index.ts +8 -0
  11. package/src/components/alert/Alert.tsx +106 -0
  12. package/src/components/alert/index.ts +1 -0
  13. package/src/components/avatar/Avatar.tsx +89 -0
  14. package/src/components/avatar/AvatarSwitcher.tsx +261 -0
  15. package/src/components/avatar/index.ts +8 -0
  16. package/src/components/back-to-top/BackToTop.tsx +124 -0
  17. package/src/components/back-to-top/index.ts +5 -0
  18. package/src/components/badge/Badge.tsx +75 -0
  19. package/src/components/badge/index.ts +1 -0
  20. package/src/components/bracket-title/BracketTitle.tsx +40 -0
  21. package/src/components/bracket-title/index.ts +1 -0
  22. package/src/components/breadcrumb/Breadcrumb.tsx +161 -0
  23. package/src/components/breadcrumb/index.ts +6 -0
  24. package/src/components/button/Button.tsx +357 -0
  25. package/src/components/button/ButtonGroup.tsx +55 -0
  26. package/src/components/button/index.ts +13 -0
  27. package/src/components/calendar/Calendar.tsx +443 -0
  28. package/src/components/calendar/index.ts +7 -0
  29. package/src/components/carousel/Carousel.tsx +292 -0
  30. package/src/components/carousel/index.ts +7 -0
  31. package/src/components/checkbox/Checkbox.tsx +94 -0
  32. package/src/components/checkbox/choice-style.ts +30 -0
  33. package/src/components/checkbox/index.ts +1 -0
  34. package/src/components/chip/FilterChip.tsx +68 -0
  35. package/src/components/chip/ResourceChip.tsx +63 -0
  36. package/src/components/chip/capsule-style.ts +22 -0
  37. package/src/components/chip/index.ts +6 -0
  38. package/src/components/combobox/Combobox.tsx +450 -0
  39. package/src/components/combobox/index.ts +10 -0
  40. package/src/components/completion-banner/CompletionBanner.tsx +72 -0
  41. package/src/components/completion-banner/index.ts +4 -0
  42. package/src/components/context-menu/ContextMenu.tsx +70 -0
  43. package/src/components/context-menu/index.ts +5 -0
  44. package/src/components/corner-brackets/CornerBrackets.tsx +43 -0
  45. package/src/components/corner-brackets/index.ts +5 -0
  46. package/src/components/countdown/Countdown.tsx +145 -0
  47. package/src/components/countdown/index.ts +6 -0
  48. package/src/components/dash-indicator/DashIndicator.tsx +44 -0
  49. package/src/components/dash-indicator/index.ts +1 -0
  50. package/src/components/data-row/DataRow.tsx +282 -0
  51. package/src/components/data-row/index.ts +8 -0
  52. package/src/components/date-picker/DateField.tsx +159 -0
  53. package/src/components/date-picker/DatePicker.tsx +182 -0
  54. package/src/components/date-picker/DateRangePicker.tsx +203 -0
  55. package/src/components/date-picker/index.ts +7 -0
  56. package/src/components/dialog/Dialog.tsx +203 -0
  57. package/src/components/dialog/index.ts +7 -0
  58. package/src/components/dialog/initial-focus.ts +19 -0
  59. package/src/components/dialog/overlay-style.ts +28 -0
  60. package/src/components/drawer/Drawer.tsx +213 -0
  61. package/src/components/drawer/index.ts +6 -0
  62. package/src/components/dropdown-menu/DropdownMenu.tsx +427 -0
  63. package/src/components/dropdown-menu/MenuCheck.tsx +44 -0
  64. package/src/components/dropdown-menu/index.ts +22 -0
  65. package/src/components/dropdown-menu/menu-style.ts +47 -0
  66. package/src/components/empty-state/EmptyState.tsx +79 -0
  67. package/src/components/empty-state/index.ts +1 -0
  68. package/src/components/field/Field.tsx +203 -0
  69. package/src/components/field/index.ts +6 -0
  70. package/src/components/file-upload/FileItem.tsx +201 -0
  71. package/src/components/file-upload/FileUpload.tsx +425 -0
  72. package/src/components/file-upload/index.ts +9 -0
  73. package/src/components/flyout-bar/FlyoutBar.tsx +158 -0
  74. package/src/components/flyout-bar/index.ts +7 -0
  75. package/src/components/ghost-text/GhostText.tsx +47 -0
  76. package/src/components/ghost-text/index.ts +5 -0
  77. package/src/components/hatch/Hatch.tsx +35 -0
  78. package/src/components/hatch/index.ts +1 -0
  79. package/src/components/hazard-stripe/HazardStripe.tsx +35 -0
  80. package/src/components/hazard-stripe/index.ts +5 -0
  81. package/src/components/icon-button/IconButton.tsx +95 -0
  82. package/src/components/icon-button/index.ts +6 -0
  83. package/src/components/input/Input.tsx +109 -0
  84. package/src/components/input/control-box.ts +71 -0
  85. package/src/components/input/index.ts +6 -0
  86. package/src/components/input/value-chip.ts +34 -0
  87. package/src/components/item-slot/ItemGrid.tsx +207 -0
  88. package/src/components/item-slot/ItemSlot.tsx +283 -0
  89. package/src/components/item-slot/index.ts +7 -0
  90. package/src/components/kbd/Kbd.tsx +33 -0
  91. package/src/components/kbd/index.ts +1 -0
  92. package/src/components/link-element/LinkElement.tsx +25 -0
  93. package/src/components/list/List.tsx +229 -0
  94. package/src/components/list/band-style.ts +18 -0
  95. package/src/components/list/index.ts +8 -0
  96. package/src/components/loader/Loader.tsx +154 -0
  97. package/src/components/loader/index.ts +1 -0
  98. package/src/components/marquee/Marquee.tsx +96 -0
  99. package/src/components/marquee/index.ts +1 -0
  100. package/src/components/media-card/MediaCard.tsx +139 -0
  101. package/src/components/media-card/index.ts +5 -0
  102. package/src/components/nav-action/NavAction.tsx +142 -0
  103. package/src/components/nav-action/index.ts +5 -0
  104. package/src/components/nav-menu/NavMenu.tsx +288 -0
  105. package/src/components/nav-menu/index.ts +6 -0
  106. package/src/components/navigator/Navigator.tsx +136 -0
  107. package/src/components/navigator/index.ts +5 -0
  108. package/src/components/pagination/Pagination.tsx +181 -0
  109. package/src/components/pagination/index.ts +5 -0
  110. package/src/components/panel/Panel.tsx +114 -0
  111. package/src/components/panel/index.ts +13 -0
  112. package/src/components/play-button/PlayButton.tsx +92 -0
  113. package/src/components/play-button/index.ts +7 -0
  114. package/src/components/popover/Popover.tsx +105 -0
  115. package/src/components/popover/index.ts +8 -0
  116. package/src/components/progress/Progress.tsx +133 -0
  117. package/src/components/progress/ProgressRing.tsx +123 -0
  118. package/src/components/progress/index.ts +2 -0
  119. package/src/components/radio/Radio.tsx +182 -0
  120. package/src/components/radio/index.ts +7 -0
  121. package/src/components/rec-indicator/RecIndicator.tsx +47 -0
  122. package/src/components/rec-indicator/index.ts +1 -0
  123. package/src/components/registration-strip/RegistrationStrip.tsx +51 -0
  124. package/src/components/registration-strip/index.ts +5 -0
  125. package/src/components/schedule/Schedule.tsx +457 -0
  126. package/src/components/schedule/index.ts +9 -0
  127. package/src/components/scroll-hint/ScrollHint.tsx +78 -0
  128. package/src/components/scroll-hint/index.ts +5 -0
  129. package/src/components/section-title/SectionTitle.tsx +202 -0
  130. package/src/components/section-title/index.ts +5 -0
  131. package/src/components/segmented-control/SegmentedControl.tsx +220 -0
  132. package/src/components/segmented-control/index.ts +8 -0
  133. package/src/components/select/Select.tsx +390 -0
  134. package/src/components/select/index.ts +15 -0
  135. package/src/components/side-rail/SideRail.tsx +317 -0
  136. package/src/components/side-rail/SideRailSub.tsx +298 -0
  137. package/src/components/side-rail/index.ts +14 -0
  138. package/src/components/skeleton/Skeleton.tsx +70 -0
  139. package/src/components/skeleton/index.ts +1 -0
  140. package/src/components/slider/Slider.tsx +219 -0
  141. package/src/components/slider/index.ts +8 -0
  142. package/src/components/sparkline/Sparkline.tsx +122 -0
  143. package/src/components/sparkline/index.ts +6 -0
  144. package/src/components/spinner/Spinner.tsx +64 -0
  145. package/src/components/spinner/index.ts +1 -0
  146. package/src/components/stat/Stat.tsx +79 -0
  147. package/src/components/stat/index.ts +1 -0
  148. package/src/components/stepper/Stepper.tsx +316 -0
  149. package/src/components/stepper/index.ts +1 -0
  150. package/src/components/steps/Steps.tsx +302 -0
  151. package/src/components/steps/index.ts +10 -0
  152. package/src/components/switch/Switch.tsx +120 -0
  153. package/src/components/switch/index.ts +1 -0
  154. package/src/components/table/Table.tsx +559 -0
  155. package/src/components/table/index.ts +20 -0
  156. package/src/components/tabs/Tabs.tsx +313 -0
  157. package/src/components/tabs/index.ts +12 -0
  158. package/src/components/tag/Tag.tsx +66 -0
  159. package/src/components/tag/TagPair.tsx +34 -0
  160. package/src/components/tag/index.ts +2 -0
  161. package/src/components/tag-input/TagInput.tsx +501 -0
  162. package/src/components/tag-input/index.ts +8 -0
  163. package/src/components/term/Term.tsx +53 -0
  164. package/src/components/term/index.ts +1 -0
  165. package/src/components/textarea/Textarea.tsx +99 -0
  166. package/src/components/textarea/index.ts +1 -0
  167. package/src/components/texture/Texture.tsx +45 -0
  168. package/src/components/texture/index.ts +1 -0
  169. package/src/components/tick-ring/TickRing.tsx +77 -0
  170. package/src/components/tick-ring/index.ts +1 -0
  171. package/src/components/timeline/Timeline.tsx +84 -0
  172. package/src/components/timeline/index.ts +7 -0
  173. package/src/components/toast/Toast.tsx +257 -0
  174. package/src/components/toast/index.ts +10 -0
  175. package/src/components/toc/Toc.tsx +262 -0
  176. package/src/components/toc/index.ts +7 -0
  177. package/src/components/toolbar/Toolbar.tsx +389 -0
  178. package/src/components/toolbar/index.ts +17 -0
  179. package/src/components/tooltip/Tooltip.tsx +175 -0
  180. package/src/components/tooltip/index.ts +8 -0
  181. package/src/components/top-bar/TopBar.tsx +47 -0
  182. package/src/components/top-bar/index.ts +1 -0
  183. package/src/components/viewfinder/Viewfinder.tsx +97 -0
  184. package/src/components/viewfinder/index.ts +6 -0
  185. package/src/hooks/README.md +14 -0
  186. package/src/hooks/index.ts +4 -0
  187. package/src/hooks/useControllableState.ts +30 -0
  188. package/src/hooks/useInView.ts +44 -0
  189. package/src/hooks/useOverflowing.ts +28 -0
  190. package/src/hooks/usePortalScope.ts +68 -0
  191. package/src/hooks/useScrollPosition.ts +57 -0
  192. package/src/hooks/useTheme.ts +102 -0
  193. package/src/icons/ArrowCorner.tsx +7 -0
  194. package/src/icons/ArrowLeft.tsx +6 -0
  195. package/src/icons/ArrowRight.tsx +6 -0
  196. package/src/icons/CalendarIcon.tsx +10 -0
  197. package/src/icons/Check.tsx +4 -0
  198. package/src/icons/ChevronDown.tsx +3 -0
  199. package/src/icons/ChevronLeft.tsx +6 -0
  200. package/src/icons/ChevronRight.tsx +6 -0
  201. package/src/icons/Close.tsx +3 -0
  202. package/src/icons/Crosshair.tsx +7 -0
  203. package/src/icons/Lock.tsx +6 -0
  204. package/src/icons/Menu.tsx +4 -0
  205. package/src/icons/Minus.tsx +4 -0
  206. package/src/icons/Plus.tsx +3 -0
  207. package/src/icons/README.md +31 -0
  208. package/src/icons/StatusDanger.tsx +7 -0
  209. package/src/icons/StatusInfo.tsx +7 -0
  210. package/src/icons/StatusSuccess.tsx +10 -0
  211. package/src/icons/StatusWarning.tsx +7 -0
  212. package/src/icons/TriangleRight.tsx +7 -0
  213. package/src/icons/Upload.tsx +7 -0
  214. package/src/icons/createIcon.tsx +34 -0
  215. package/src/icons/index.ts +21 -0
  216. package/src/index.ts +82 -0
  217. package/src/lib/README.md +17 -0
  218. package/src/lib/cn.ts +34 -0
  219. package/src/lib/date.ts +125 -0
  220. package/src/lib/decor.ts +6 -0
  221. package/src/lib/defined.ts +9 -0
  222. package/src/lib/file.ts +61 -0
  223. package/src/lib/focus-ring.ts +11 -0
  224. package/src/lib/merge-refs.ts +11 -0
  225. package/src/styles/README.md +70 -0
  226. package/src/styles/index.css +25 -0
  227. package/src/styles/theme.css +568 -0
  228. package/src/styles/utilities.css +241 -0
@@ -0,0 +1,313 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useId,
5
+ useMemo,
6
+ type ComponentProps,
7
+ type KeyboardEvent,
8
+ type MouseEvent,
9
+ } from "react";
10
+ import { useControllableState } from "../../hooks/useControllableState";
11
+ import { TriangleRight } from "../../icons/TriangleRight";
12
+ import { cn } from "../../lib/cn";
13
+ import { focusRing, focusRingInset } from "../../lib/focus-ring";
14
+ import { capsuleBase, capsuleSize, capsuleState } from "../chip/capsule-style";
15
+
16
+ export type TabsVariant = "block" | "capsule" | "wedge";
17
+ export type TabsSize = "sm" | "md";
18
+
19
+ type TabsContextValue = {
20
+ value: string;
21
+ select: (value: string) => void;
22
+ baseId: string;
23
+ variant: TabsVariant;
24
+ size: TabsSize;
25
+ };
26
+
27
+ const TabsContext = createContext<TabsContextValue | null>(null);
28
+
29
+ function useTabsContext(component: string): TabsContextValue {
30
+ const context = useContext(TabsContext);
31
+ if (!context) {
32
+ throw new Error(`<${component}> 必须放在 <Tabs> 里`);
33
+ }
34
+ return context;
35
+ }
36
+
37
+ /** 页签值可以是任意字符串,拼进 id 之前先换掉不能出现在 IDREF 里的字符 */
38
+ function toId(baseId: string, kind: "tab" | "panel", value: string) {
39
+ return `${baseId}-${kind}-${value.replace(/[^\w-]/g, "_")}`;
40
+ }
41
+
42
+ type TabsOwnProps = {
43
+ /**
44
+ * - `block` 直角的格子,之间是不通顶的竖线;激活时文字让位给箭头。默认;
45
+ * - `capsule` 横向胶囊,选中反转为墨底。筛选、类目切换;
46
+ * - `wedge` 选中项是黄底墨字、右侧斜切的楔形。游戏风格界面的一级页签。
47
+ */
48
+ variant?: TabsVariant;
49
+ size?: TabsSize;
50
+ onValueChange?: (value: string) => void;
51
+ } & (
52
+ | { /** 受控 */ value: string; defaultValue?: never }
53
+ | { /** 非受控时的初始页签 */ defaultValue: string; value?: never }
54
+ );
55
+
56
+ export type TabsProps = Omit<ComponentProps<"div">, keyof TabsOwnProps> &
57
+ TabsOwnProps;
58
+
59
+ export function Tabs({
60
+ value,
61
+ defaultValue,
62
+ onValueChange,
63
+ variant = "block",
64
+ size = "md",
65
+ ...props
66
+ }: TabsProps) {
67
+ const baseId = useId();
68
+ const [current, select] = useControllableState({
69
+ value,
70
+ defaultValue: defaultValue ?? "",
71
+ onChange: onValueChange,
72
+ });
73
+
74
+ const context = useMemo(
75
+ () => ({ value: current, select, baseId, variant, size }),
76
+ [current, select, baseId, variant, size],
77
+ );
78
+
79
+ return (
80
+ <TabsContext value={context}>
81
+ <div {...props} data-variant={variant} />
82
+ </TabsContext>
83
+ );
84
+ }
85
+
86
+ export type TabListProps = ComponentProps<"div">;
87
+
88
+ export function TabList({ className, onKeyDown, ...props }: TabListProps) {
89
+ const { variant } = useTabsContext("TabList");
90
+
91
+ // 漫游 tabindex:方向键在页签之间移动焦点并直接切换
92
+ const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
93
+ onKeyDown?.(event);
94
+ if (event.defaultPrevented) return;
95
+
96
+ const tabs = Array.from(
97
+ event.currentTarget.querySelectorAll<HTMLButtonElement>(
98
+ '[role="tab"]:not(:disabled)',
99
+ ),
100
+ );
101
+ const index = tabs.indexOf(document.activeElement as HTMLButtonElement);
102
+ if (index === -1) return;
103
+
104
+ let next: number;
105
+ switch (event.key) {
106
+ case "ArrowRight":
107
+ next = (index + 1) % tabs.length;
108
+ break;
109
+ case "ArrowLeft":
110
+ next = (index - 1 + tabs.length) % tabs.length;
111
+ break;
112
+ case "Home":
113
+ next = 0;
114
+ break;
115
+ case "End":
116
+ next = tabs.length - 1;
117
+ break;
118
+ default:
119
+ return;
120
+ }
121
+
122
+ event.preventDefault();
123
+ const target = tabs[next];
124
+ target?.focus();
125
+ target?.click();
126
+ };
127
+
128
+ return (
129
+ <div
130
+ {...props}
131
+ role="tablist"
132
+ aria-orientation="horizontal"
133
+ onKeyDown={handleKeyDown}
134
+ className={cn(
135
+ // 页签很多时横向滚动,不换行
136
+ "flex overflow-x-auto [scrollbar-width:thin]",
137
+ variant === "capsule" && "gap-2 p-1",
138
+ // 楔形坐在一条墨线上:亮色页面上黄色对白底的明度差很小,靠这条线托住
139
+ variant === "wedge" && "border-b-2 border-ink",
140
+ className,
141
+ )}
142
+ />
143
+ );
144
+ }
145
+
146
+ export type TabProps = Omit<ComponentProps<"button">, "value"> & {
147
+ value: string;
148
+ };
149
+
150
+ const blockSize: Record<TabsSize, string> = {
151
+ sm: "h-9 px-7 text-base",
152
+ md: "h-10 px-8 text-lg",
153
+ };
154
+
155
+ const blockLabelShift: Record<TabsSize, string> = {
156
+ sm: "-translate-x-2.5",
157
+ md: "-translate-x-3",
158
+ };
159
+
160
+ const blockArrow: Record<TabsSize, string> = {
161
+ sm: "right-1.5 size-5 [&_svg]:size-2.5",
162
+ md: "right-2 size-6 [&_svg]:size-3",
163
+ };
164
+
165
+ /* 右侧多留一段:斜边占掉了 10px */
166
+ const wedgeSize: Record<TabsSize, string> = {
167
+ sm: "h-9 pr-8 pl-5 text-base",
168
+ md: "h-10 pr-9 pl-6 text-lg",
169
+ };
170
+
171
+ export function Tab({
172
+ value,
173
+ disabled = false,
174
+ className,
175
+ children,
176
+ onClick,
177
+ ...props
178
+ }: TabProps) {
179
+ const context = useTabsContext("Tab");
180
+ const selected = context.value === value;
181
+ const { variant, size } = context;
182
+
183
+ const shared = {
184
+ ...props,
185
+ type: "button" as const,
186
+ role: "tab",
187
+ id: toId(context.baseId, "tab", value),
188
+ "aria-selected": selected,
189
+ "aria-controls": toId(context.baseId, "panel", value),
190
+ tabIndex: selected ? 0 : -1,
191
+ disabled,
192
+ "data-selected": selected ? "" : undefined,
193
+ onClick: (event: MouseEvent<HTMLButtonElement>) => {
194
+ onClick?.(event);
195
+ if (!event.defaultPrevented) context.select(value);
196
+ },
197
+ };
198
+
199
+ if (variant === "capsule") {
200
+ return (
201
+ <button
202
+ {...shared}
203
+ className={cn(
204
+ capsuleBase,
205
+ focusRing,
206
+ capsuleSize[size],
207
+ selected
208
+ ? capsuleState.selected
209
+ : disabled
210
+ ? capsuleState.disabled
211
+ : capsuleState.rest,
212
+ className,
213
+ )}
214
+ >
215
+ {children}
216
+ </button>
217
+ );
218
+ }
219
+
220
+ if (variant === "wedge") {
221
+ return (
222
+ <button
223
+ {...shared}
224
+ className={cn(
225
+ "relative isolate inline-flex shrink-0 items-center justify-center leading-none font-medium whitespace-nowrap",
226
+ "transition-colors duration-(--duration-fast) ease-standard",
227
+ // 楔形画在伪元素的底上:按钮本身不裁切,焦点环才是完整的
228
+ "before:absolute before:inset-0 before:-z-10 before:wedge-r before:transition-colors before:duration-(--duration-fast) before:ease-standard before:content-['']",
229
+ focusRingInset,
230
+ wedgeSize[size],
231
+ selected
232
+ ? "text-on-action before:bg-action"
233
+ : disabled
234
+ ? "cursor-not-allowed text-ink-disabled"
235
+ : "text-ink hover:before:bg-ink/5",
236
+ className,
237
+ )}
238
+ >
239
+ {children}
240
+ </button>
241
+ );
242
+ }
243
+
244
+ return (
245
+ <button
246
+ {...shared}
247
+ className={cn(
248
+ "relative inline-flex shrink-0 items-center justify-center leading-none font-medium whitespace-nowrap",
249
+ "transition-colors duration-(--duration-fast) ease-standard",
250
+ // 页签之间的竖线:2px、三分之二高、不通顶。紧挨激活项的两条让给它的底色
251
+ "before:absolute before:top-1/6 before:left-0 before:h-2/3 before:w-0.5 before:bg-line before:content-[''] first:before:hidden [[data-selected]+&]:before:hidden",
252
+ focusRingInset,
253
+ blockSize[size],
254
+ selected
255
+ ? "bg-surface-muted text-ink before:hidden"
256
+ : disabled
257
+ ? "cursor-not-allowed text-ink-disabled"
258
+ : "text-ink hover:bg-ink/5",
259
+ className,
260
+ )}
261
+ >
262
+ {/* 激活:文字左移,右侧的箭头块淡入——新元素出现时旧元素让位 */}
263
+ <span
264
+ className={cn(
265
+ "transition-[translate] duration-(--duration-base) ease-standard",
266
+ selected && blockLabelShift[size],
267
+ )}
268
+ >
269
+ {children}
270
+ </span>
271
+ <span
272
+ aria-hidden="true"
273
+ className={cn(
274
+ "pointer-events-none absolute top-1/2 flex -translate-y-1/2 items-center justify-center bg-surface-raised text-ink",
275
+ "transition-opacity duration-(--duration-base) ease-standard",
276
+ blockArrow[size],
277
+ selected ? "opacity-100" : "opacity-0",
278
+ )}
279
+ >
280
+ <TriangleRight />
281
+ </span>
282
+ </button>
283
+ );
284
+ }
285
+
286
+ export type TabPanelProps = ComponentProps<"div"> & {
287
+ value: string;
288
+ };
289
+
290
+ /** 未选中的面板保留一个隐藏的空容器,让页签的 `aria-controls` 始终有所指。 */
291
+ export function TabPanel({
292
+ value,
293
+ className,
294
+ children,
295
+ ...props
296
+ }: TabPanelProps) {
297
+ const context = useTabsContext("TabPanel");
298
+ const selected = context.value === value;
299
+
300
+ return (
301
+ <div
302
+ {...props}
303
+ role="tabpanel"
304
+ id={toId(context.baseId, "panel", value)}
305
+ aria-labelledby={toId(context.baseId, "tab", value)}
306
+ hidden={!selected}
307
+ tabIndex={0}
308
+ className={cn(focusRing, className)}
309
+ >
310
+ {selected ? children : null}
311
+ </div>
312
+ );
313
+ }
@@ -0,0 +1,12 @@
1
+ export {
2
+ Tab,
3
+ TabList,
4
+ TabPanel,
5
+ Tabs,
6
+ type TabListProps,
7
+ type TabPanelProps,
8
+ type TabProps,
9
+ type TabsProps,
10
+ type TabsSize,
11
+ type TabsVariant,
12
+ } from "./Tabs";
@@ -0,0 +1,66 @@
1
+ import type { ComponentProps } from "react";
2
+ import { cn } from "../../lib/cn";
3
+
4
+ export type TagVariant =
5
+ "solid" | "outline" | "inverse" | "muted" | "accent" | "gain";
6
+ export type TagSize = "sm" | "md";
7
+
8
+ export type TagProps = ComponentProps<"span"> & {
9
+ /**
10
+ * 直角标签:归档性的、不变的信息。
11
+ *
12
+ * - `solid` 深灰底白字、2px 微圆角,媒体类型(`PV`、`OST`),默认;
13
+ * - `outline` 白底细边,活动类型、分类;
14
+ * - `inverse` 墨底白字,日期块、名值对里的"名";
15
+ * - `muted` 浅灰底墨字,名值对里的"值";
16
+ * - `accent` 黄底墨字,状态签(`NEW`、推荐);
17
+ * - `gain` 荧光黄绿底墨字,增益签(`+6%`)。
18
+ */
19
+ variant?: TagVariant;
20
+ size?: TagSize;
21
+ /** 等宽数字,用于日期、编号 */
22
+ numeric?: boolean;
23
+ /** 左上角加一个小红角,标出一组日期块里需要留意的那一个 */
24
+ marked?: boolean;
25
+ };
26
+
27
+ const sizeClass: Record<TagSize, string> = {
28
+ sm: "h-5 px-1.5 text-xs",
29
+ md: "h-7 px-2.5 text-sm",
30
+ };
31
+
32
+ const variantClass: Record<TagVariant, string> = {
33
+ solid: "rounded-xs bg-control text-on-control",
34
+ outline: "border border-line-strong bg-surface text-ink",
35
+ inverse: "bg-surface-inverse text-ink-inverse",
36
+ muted: "bg-surface-sunken text-ink",
37
+ accent: "bg-action text-on-action",
38
+ // 有意不随主题翻转:增益色是按"填充 + 墨字"取的角色色
39
+ gain: "bg-gain text-neutral-900",
40
+ };
41
+
42
+ export function Tag({
43
+ variant = "solid",
44
+ size = "md",
45
+ numeric = false,
46
+ marked = false,
47
+ className,
48
+ ...props
49
+ }: TagProps) {
50
+ return (
51
+ <span
52
+ {...props}
53
+ data-variant={variant}
54
+ data-marked={marked ? "" : undefined}
55
+ className={cn(
56
+ "inline-flex shrink-0 items-center font-medium leading-none whitespace-nowrap",
57
+ sizeClass[size],
58
+ variantClass[variant],
59
+ numeric && "font-tech font-bold tabular-nums",
60
+ marked &&
61
+ "relative before:absolute before:top-0 before:left-0 before:size-1.5 before:bg-alert before:content-[''] before:[clip-path:polygon(0_0,100%_0,0_100%)]",
62
+ className,
63
+ )}
64
+ />
65
+ );
66
+ }
@@ -0,0 +1,34 @@
1
+ import type { ComponentProps, ReactNode } from "react";
2
+ import { cn } from "../../lib/cn";
3
+ import { Tag, type TagSize } from "./Tag";
4
+
5
+ export type TagPairProps = Omit<ComponentProps<"span">, "children"> & {
6
+ /** 属性名,如"阵营" */
7
+ name: ReactNode;
8
+ /** 属性值 */
9
+ value: ReactNode;
10
+ /** 一组里需要突出的那一项:"名"换成黄底 */
11
+ emphasis?: boolean;
12
+ size?: TagSize;
13
+ };
14
+
15
+ /** 名值对:两半紧贴,中间没有缝也没有分隔线。多组之间由使用方留 8px。 */
16
+ export function TagPair({
17
+ name,
18
+ value,
19
+ emphasis = false,
20
+ size = "md",
21
+ className,
22
+ ...props
23
+ }: TagPairProps) {
24
+ return (
25
+ <span {...props} className={cn("inline-flex shrink-0", className)}>
26
+ <Tag variant={emphasis ? "accent" : "inverse"} size={size}>
27
+ {name}
28
+ </Tag>
29
+ <Tag variant="muted" size={size}>
30
+ {value}
31
+ </Tag>
32
+ </span>
33
+ );
34
+ }
@@ -0,0 +1,2 @@
1
+ export { Tag, type TagProps, type TagSize, type TagVariant } from "./Tag";
2
+ export { TagPair, type TagPairProps } from "./TagPair";