@gouno/ui 0.3.2

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 (231) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/LICENSE +21 -0
  3. package/README.md +144 -0
  4. package/dist/base.css +239 -0
  5. package/dist/bootstrap.js +22 -0
  6. package/dist/components/primitives/alert-dialog.d.ts +18 -0
  7. package/dist/components/primitives/alert-dialog.js +43 -0
  8. package/dist/components/primitives/alert.d.ts +9 -0
  9. package/dist/components/primitives/alert.js +25 -0
  10. package/dist/components/primitives/avatar.d.ts +14 -0
  11. package/dist/components/primitives/avatar.js +42 -0
  12. package/dist/components/primitives/badge.d.ts +9 -0
  13. package/dist/components/primitives/badge.js +25 -0
  14. package/dist/components/primitives/button.d.ts +10 -0
  15. package/dist/components/primitives/button.js +36 -0
  16. package/dist/components/primitives/checkbox.d.ts +4 -0
  17. package/dist/components/primitives/checkbox.js +10 -0
  18. package/dist/components/primitives/dialog.d.ts +20 -0
  19. package/dist/components/primitives/dialog.js +37 -0
  20. package/dist/components/primitives/dropdown-menu.d.ts +25 -0
  21. package/dist/components/primitives/dropdown-menu.js +51 -0
  22. package/dist/components/primitives/empty.d.ts +11 -0
  23. package/dist/components/primitives/empty.js +33 -0
  24. package/dist/components/primitives/field.d.ts +24 -0
  25. package/dist/components/primitives/field.js +75 -0
  26. package/dist/components/primitives/input.d.ts +3 -0
  27. package/dist/components/primitives/input.js +7 -0
  28. package/dist/components/primitives/label.d.ts +4 -0
  29. package/dist/components/primitives/label.js +8 -0
  30. package/dist/components/primitives/popover.d.ts +16 -0
  31. package/dist/components/primitives/popover.js +31 -0
  32. package/dist/components/primitives/radio-group.d.ts +5 -0
  33. package/dist/components/primitives/radio-group.js +13 -0
  34. package/dist/components/primitives/select.d.ts +15 -0
  35. package/dist/components/primitives/select.js +38 -0
  36. package/dist/components/primitives/separator.d.ts +4 -0
  37. package/dist/components/primitives/separator.js +8 -0
  38. package/dist/components/primitives/sheet.d.ts +17 -0
  39. package/dist/components/primitives/sheet.js +41 -0
  40. package/dist/components/primitives/skeleton.d.ts +2 -0
  41. package/dist/components/primitives/skeleton.js +6 -0
  42. package/dist/components/primitives/switch.d.ts +6 -0
  43. package/dist/components/primitives/switch.js +8 -0
  44. package/dist/components/primitives/table.d.ts +28 -0
  45. package/dist/components/primitives/table.js +44 -0
  46. package/dist/components/primitives/tabs.d.ts +11 -0
  47. package/dist/components/primitives/tabs.js +30 -0
  48. package/dist/components/primitives/textarea.d.ts +3 -0
  49. package/dist/components/primitives/textarea.js +7 -0
  50. package/dist/components/primitives/tooltip.d.ts +13 -0
  51. package/dist/components/primitives/tooltip.js +24 -0
  52. package/dist/core/affix.d.ts +14 -0
  53. package/dist/core/affix.js +29 -0
  54. package/dist/core/alert.d.ts +54 -0
  55. package/dist/core/alert.js +56 -0
  56. package/dist/core/anchor.d.ts +16 -0
  57. package/dist/core/anchor.js +31 -0
  58. package/dist/core/app.d.ts +5 -0
  59. package/dist/core/app.js +5 -0
  60. package/dist/core/aspect-ratio.d.ts +6 -0
  61. package/dist/core/aspect-ratio.js +6 -0
  62. package/dist/core/autocomplete.d.ts +24 -0
  63. package/dist/core/autocomplete.js +139 -0
  64. package/dist/core/avatar.d.ts +26 -0
  65. package/dist/core/avatar.js +20 -0
  66. package/dist/core/badge.d.ts +16 -0
  67. package/dist/core/badge.js +22 -0
  68. package/dist/core/breadcrumb.d.ts +55 -0
  69. package/dist/core/breadcrumb.js +69 -0
  70. package/dist/core/button.d.ts +49 -0
  71. package/dist/core/button.js +66 -0
  72. package/dist/core/calendar.d.ts +48 -0
  73. package/dist/core/calendar.js +179 -0
  74. package/dist/core/card.d.ts +30 -0
  75. package/dist/core/card.js +12 -0
  76. package/dist/core/carousel.d.ts +49 -0
  77. package/dist/core/carousel.js +166 -0
  78. package/dist/core/cascader.d.ts +19 -0
  79. package/dist/core/cascader.js +45 -0
  80. package/dist/core/code-block.d.ts +16 -0
  81. package/dist/core/code-block.js +47 -0
  82. package/dist/core/collapse.d.ts +49 -0
  83. package/dist/core/collapse.js +96 -0
  84. package/dist/core/color-picker.d.ts +9 -0
  85. package/dist/core/color-picker.js +8 -0
  86. package/dist/core/config-provider.d.ts +10 -0
  87. package/dist/core/config-provider.js +14 -0
  88. package/dist/core/control-types.d.ts +2 -0
  89. package/dist/core/control-types.js +7 -0
  90. package/dist/core/date-picker.d.ts +13 -0
  91. package/dist/core/date-picker.js +14 -0
  92. package/dist/core/date-range-picker.d.ts +26 -0
  93. package/dist/core/date-range-picker.js +10 -0
  94. package/dist/core/descriptions.d.ts +41 -0
  95. package/dist/core/descriptions.js +133 -0
  96. package/dist/core/drawer.d.ts +9 -0
  97. package/dist/core/drawer.js +58 -0
  98. package/dist/core/empty.d.ts +8 -0
  99. package/dist/core/empty.js +5 -0
  100. package/dist/core/feedback.d.ts +5 -0
  101. package/dist/core/feedback.js +5 -0
  102. package/dist/core/field.d.ts +15 -0
  103. package/dist/core/field.js +19 -0
  104. package/dist/core/float-button.d.ts +13 -0
  105. package/dist/core/float-button.js +22 -0
  106. package/dist/core/form.d.ts +19 -0
  107. package/dist/core/form.js +56 -0
  108. package/dist/core/icon-button.d.ts +7 -0
  109. package/dist/core/icon-button.js +4 -0
  110. package/dist/core/icon.d.ts +17 -0
  111. package/dist/core/icon.js +35 -0
  112. package/dist/core/image.d.ts +76 -0
  113. package/dist/core/image.js +189 -0
  114. package/dist/core/index.d.ts +79 -0
  115. package/dist/core/index.js +77 -0
  116. package/dist/core/input-number.d.ts +24 -0
  117. package/dist/core/input-number.js +70 -0
  118. package/dist/core/input-otp.d.ts +14 -0
  119. package/dist/core/input-otp.js +76 -0
  120. package/dist/core/input.d.ts +14 -0
  121. package/dist/core/input.js +51 -0
  122. package/dist/core/kbd.d.ts +8 -0
  123. package/dist/core/kbd.js +11 -0
  124. package/dist/core/layout-primitives.d.ts +11 -0
  125. package/dist/core/layout-primitives.js +8 -0
  126. package/dist/core/layout.d.ts +71 -0
  127. package/dist/core/layout.js +119 -0
  128. package/dist/core/list.d.ts +37 -0
  129. package/dist/core/list.js +35 -0
  130. package/dist/core/locale.d.ts +48 -0
  131. package/dist/core/locale.js +32 -0
  132. package/dist/core/mentions.d.ts +10 -0
  133. package/dist/core/mentions.js +112 -0
  134. package/dist/core/menu.d.ts +79 -0
  135. package/dist/core/menu.js +265 -0
  136. package/dist/core/message.d.ts +16 -0
  137. package/dist/core/message.js +36 -0
  138. package/dist/core/modal.d.ts +40 -0
  139. package/dist/core/modal.js +67 -0
  140. package/dist/core/notification.d.ts +15 -0
  141. package/dist/core/notification.js +39 -0
  142. package/dist/core/page-layout.d.ts +6 -0
  143. package/dist/core/page-layout.js +7 -0
  144. package/dist/core/pagination.d.ts +28 -0
  145. package/dist/core/pagination.js +69 -0
  146. package/dist/core/popconfirm.d.ts +13 -0
  147. package/dist/core/popconfirm.js +42 -0
  148. package/dist/core/progress.d.ts +6 -0
  149. package/dist/core/progress.js +8 -0
  150. package/dist/core/public-props.d.ts +82 -0
  151. package/dist/core/public-props.js +1 -0
  152. package/dist/core/qrcode.d.ts +14 -0
  153. package/dist/core/qrcode.js +17 -0
  154. package/dist/core/rate.d.ts +11 -0
  155. package/dist/core/rate.js +48 -0
  156. package/dist/core/result.d.ts +12 -0
  157. package/dist/core/result.js +19 -0
  158. package/dist/core/segmented.d.ts +29 -0
  159. package/dist/core/segmented.js +38 -0
  160. package/dist/core/select.d.ts +27 -0
  161. package/dist/core/select.js +143 -0
  162. package/dist/core/selection-controls.d.ts +12 -0
  163. package/dist/core/selection-controls.js +23 -0
  164. package/dist/core/separator.d.ts +24 -0
  165. package/dist/core/separator.js +17 -0
  166. package/dist/core/slider.d.ts +2 -0
  167. package/dist/core/slider.js +6 -0
  168. package/dist/core/spin.d.ts +7 -0
  169. package/dist/core/spin.js +7 -0
  170. package/dist/core/spinner.d.ts +3 -0
  171. package/dist/core/spinner.js +6 -0
  172. package/dist/core/splitter.d.ts +44 -0
  173. package/dist/core/splitter.js +208 -0
  174. package/dist/core/statistic.d.ts +12 -0
  175. package/dist/core/statistic.js +6 -0
  176. package/dist/core/steps.d.ts +54 -0
  177. package/dist/core/steps.js +106 -0
  178. package/dist/core/tabs.d.ts +40 -0
  179. package/dist/core/tabs.js +80 -0
  180. package/dist/core/tag.d.ts +19 -0
  181. package/dist/core/tag.js +29 -0
  182. package/dist/core/textarea.d.ts +8 -0
  183. package/dist/core/textarea.js +14 -0
  184. package/dist/core/time-picker.d.ts +9 -0
  185. package/dist/core/time-picker.js +8 -0
  186. package/dist/core/timeline.d.ts +37 -0
  187. package/dist/core/timeline.js +48 -0
  188. package/dist/core/tour.d.ts +18 -0
  189. package/dist/core/tour.js +51 -0
  190. package/dist/core/transfer.d.ts +16 -0
  191. package/dist/core/transfer.js +46 -0
  192. package/dist/core/tree-select.d.ts +19 -0
  193. package/dist/core/tree-select.js +19 -0
  194. package/dist/core/tree.d.ts +89 -0
  195. package/dist/core/tree.js +386 -0
  196. package/dist/core/typography.d.ts +20 -0
  197. package/dist/core/typography.js +12 -0
  198. package/dist/core/upload.d.ts +20 -0
  199. package/dist/core/upload.js +72 -0
  200. package/dist/core/watermark.d.ts +10 -0
  201. package/dist/core/watermark.js +21 -0
  202. package/dist/fonts/inter-LICENSE +93 -0
  203. package/dist/fonts/inter-latin-wght-normal.woff2 +0 -0
  204. package/dist/fonts/jetbrains-mono-LICENSE +93 -0
  205. package/dist/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  206. package/dist/gouno/app-shell.d.ts +22 -0
  207. package/dist/gouno/app-shell.js +27 -0
  208. package/dist/gouno/index.d.ts +4 -0
  209. package/dist/gouno/index.js +4 -0
  210. package/dist/gouno/page-container.d.ts +4 -0
  211. package/dist/gouno/page-container.js +6 -0
  212. package/dist/gouno/page-header.d.ts +8 -0
  213. package/dist/gouno/page-header.js +6 -0
  214. package/dist/hooks/use-overlay-body.d.ts +6 -0
  215. package/dist/hooks/use-overlay-body.js +18 -0
  216. package/dist/index.d.ts +5 -0
  217. package/dist/index.js +5 -0
  218. package/dist/lib/utils.d.ts +2 -0
  219. package/dist/lib/utils.js +5 -0
  220. package/dist/patterns/bulk-action-bar.d.ts +8 -0
  221. package/dist/patterns/bulk-action-bar.js +7 -0
  222. package/dist/patterns/index.d.ts +5 -0
  223. package/dist/patterns/index.js +5 -0
  224. package/dist/theme/index.d.ts +2 -0
  225. package/dist/theme/index.js +2 -0
  226. package/dist/theme/provider.d.ts +22 -0
  227. package/dist/theme/provider.js +76 -0
  228. package/dist/theme/theme-toggle.d.ts +6 -0
  229. package/dist/theme/theme-toggle.js +10 -0
  230. package/dist/tokens.css +164 -0
  231. package/package.json +111 -0
@@ -0,0 +1,179 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState, } from "react";
3
+ import { ChevronLeft, ChevronRight } from "lucide-react";
4
+ import { cn } from "../lib/utils.js";
5
+ import { Button } from "./button.js";
6
+ import { IconButton } from "./icon-button.js";
7
+ function startOfDay(date) {
8
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
9
+ }
10
+ function sameDay(a, b) {
11
+ return startOfDay(a).getTime() === startOfDay(b).getTime();
12
+ }
13
+ function sameMonth(a, b) {
14
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
15
+ }
16
+ function addMonths(date, amount) {
17
+ return new Date(date.getFullYear(), date.getMonth() + amount, 1);
18
+ }
19
+ function addYears(date, amount) {
20
+ return new Date(date.getFullYear() + amount, date.getMonth(), 1);
21
+ }
22
+ function rangeState(date, range) {
23
+ if (!range)
24
+ return undefined;
25
+ if (sameDay(date, range[0]))
26
+ return "start";
27
+ if (sameDay(date, range[1]))
28
+ return "end";
29
+ return undefined;
30
+ }
31
+ function isOutsideRange(date, range) {
32
+ if (!range)
33
+ return false;
34
+ const time = startOfDay(date).getTime();
35
+ return time < startOfDay(range[0]).getTime() || time > startOfDay(range[1]).getTime();
36
+ }
37
+ function getWeekNumber(date) {
38
+ const target = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
39
+ const day = target.getUTCDay() || 7;
40
+ target.setUTCDate(target.getUTCDate() + 4 - day);
41
+ const yearStart = new Date(Date.UTC(target.getUTCFullYear(), 0, 1));
42
+ return Math.ceil(((target.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
43
+ }
44
+ function defaultLocale() {
45
+ return typeof document !== "undefined" && document.documentElement.lang
46
+ ? document.documentElement.lang
47
+ : undefined;
48
+ }
49
+ function localizedLabel(en, zh) {
50
+ return typeof document !== "undefined" &&
51
+ document.documentElement.lang.startsWith("en")
52
+ ? en
53
+ : zh;
54
+ }
55
+ export function Calendar({ value, defaultValue, mode, defaultMode = "month", fullscreen = true, showWeek = false, disabledDate, validRange, locale = defaultLocale(), cellRender, fullCellRender, headerRender, onChange, onPanelChange, onSelect, classNames, styles, className, style, ref, ...props }) {
56
+ const today = useMemo(() => startOfDay(new Date()), []);
57
+ const [internalValue, setInternalValue] = useState(() => startOfDay(defaultValue ?? value ?? today));
58
+ const selectedValue = value ? startOfDay(value) : internalValue;
59
+ const [panelDate, setPanelDate] = useState(() => selectedValue);
60
+ const [internalMode, setInternalMode] = useState(defaultMode);
61
+ const activeMode = mode ?? internalMode;
62
+ useEffect(() => {
63
+ if (value)
64
+ setPanelDate(startOfDay(value));
65
+ }, [value]);
66
+ const semanticInfo = {
67
+ props: { mode: activeMode, fullscreen, showWeek },
68
+ };
69
+ const semanticClassNames = typeof classNames === "function" ? classNames(semanticInfo) : (classNames ?? {});
70
+ const semanticStyles = typeof styles === "function" ? styles(semanticInfo) : (styles ?? {});
71
+ const format = (date, options) => new Intl.DateTimeFormat(locale || undefined, options).format(date);
72
+ const isDisabled = (date) => isOutsideRange(date, validRange) || Boolean(disabledDate?.(date));
73
+ const changePanel = (nextDate, nextMode = activeMode) => {
74
+ setPanelDate(nextDate);
75
+ if (mode === undefined)
76
+ setInternalMode(nextMode);
77
+ onPanelChange?.(nextDate, nextMode);
78
+ };
79
+ const selectDate = (date, source) => {
80
+ if (isDisabled(date))
81
+ return;
82
+ const normalized = startOfDay(date);
83
+ if (value === undefined)
84
+ setInternalValue(normalized);
85
+ setPanelDate(normalized);
86
+ onSelect?.(normalized, { source });
87
+ if (!sameDay(normalized, selectedValue))
88
+ onChange?.(normalized);
89
+ };
90
+ const monthCells = useMemo(() => {
91
+ const first = new Date(panelDate.getFullYear(), panelDate.getMonth(), 1);
92
+ const mondayOffset = (first.getDay() + 6) % 7;
93
+ const start = new Date(first.getFullYear(), first.getMonth(), 1 - mondayOffset);
94
+ return Array.from({ length: 42 }, (_, index) => new Date(start.getFullYear(), start.getMonth(), start.getDate() + index));
95
+ }, [panelDate]);
96
+ const weekdayLabels = useMemo(() => {
97
+ const monday = new Date(2024, 0, 1);
98
+ return Array.from({ length: 7 }, (_, index) => format(new Date(monday.getFullYear(), monday.getMonth(), monday.getDate() + index), { weekday: "short" }));
99
+ }, [locale]);
100
+ const moveFocus = (event, date) => {
101
+ const amount = event.key === "ArrowLeft"
102
+ ? -1
103
+ : event.key === "ArrowRight"
104
+ ? 1
105
+ : event.key === "ArrowUp"
106
+ ? -7
107
+ : event.key === "ArrowDown"
108
+ ? 7
109
+ : 0;
110
+ if (!amount)
111
+ return;
112
+ event.preventDefault();
113
+ const next = new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount);
114
+ const key = `${next.getFullYear()}-${next.getMonth()}-${next.getDate()}`;
115
+ event.currentTarget
116
+ .closest('[data-slot="calendar"]')
117
+ ?.querySelector(`[data-calendar-date="${key}"]`)
118
+ ?.focus();
119
+ };
120
+ const renderCell = (date, originNode, cellMode) => {
121
+ const info = {
122
+ originNode,
123
+ today,
124
+ mode: cellMode,
125
+ range: rangeState(date, validRange),
126
+ };
127
+ if (fullCellRender)
128
+ return fullCellRender(date, info);
129
+ if (cellRender)
130
+ return cellRender(date, info);
131
+ return originNode;
132
+ };
133
+ const defaultHeader = (_jsxs("div", { className: "flex min-w-0 items-center justify-between gap-2", children: [_jsx(IconButton, { variant: "text", label: format(activeMode === "month" ? addMonths(panelDate, -1) : addYears(panelDate, -1), { year: "numeric", month: activeMode === "month" ? "long" : undefined }), icon: _jsx(ChevronLeft, { "aria-hidden": "true" }), onClick: () => changePanel(activeMode === "month" ? addMonths(panelDate, -1) : addYears(panelDate, -1)) }), _jsx(Button, { variant: "text", onClick: () => changePanel(panelDate, activeMode === "month" ? "year" : "month"), "aria-label": activeMode === "month"
134
+ ? localizedLabel("Switch to year view", "切换到年份视图")
135
+ : localizedLabel("Switch to month view", "切换到月份视图"), children: format(panelDate, activeMode === "month"
136
+ ? { year: "numeric", month: "long" }
137
+ : { year: "numeric" }) }), _jsx(IconButton, { variant: "text", label: format(activeMode === "month" ? addMonths(panelDate, 1) : addYears(panelDate, 1), { year: "numeric", month: activeMode === "month" ? "long" : undefined }), icon: _jsx(ChevronRight, { "aria-hidden": "true" }), onClick: () => changePanel(activeMode === "month" ? addMonths(panelDate, 1) : addYears(panelDate, 1)) })] }));
138
+ return (_jsxs("div", { ...props, ref: ref, "data-slot": "calendar", "data-mode": activeMode, "data-fullscreen": fullscreen || undefined, className: cn("w-full min-w-0 text-sm", !fullscreen && "rounded-lg border bg-background p-3", semanticClassNames.root, className), style: { ...semanticStyles.root, ...style }, children: [_jsx("div", { "data-slot": "calendar-header", className: cn("mb-4", semanticClassNames.header), style: semanticStyles.header, children: headerRender
139
+ ? headerRender({
140
+ value: panelDate,
141
+ mode: activeMode,
142
+ onChange: (next) => changePanel(startOfDay(next)),
143
+ onModeChange: (nextMode) => changePanel(panelDate, nextMode),
144
+ })
145
+ : defaultHeader }), _jsx("div", { "data-slot": "calendar-body", className: cn("min-w-0", semanticClassNames.body), style: semanticStyles.body, children: activeMode === "month" ? (_jsxs("div", { role: "grid", "aria-label": format(panelDate, { year: "numeric", month: "long" }), className: cn("grid gap-px overflow-hidden rounded-lg border bg-border", showWeek ? "grid-cols-[auto_repeat(7,minmax(0,1fr))]" : "grid-cols-7"), children: [showWeek ? (_jsx("div", { role: "columnheader", "data-slot": "calendar-week-header", className: cn("bg-muted/50 p-2 text-center text-xs font-medium text-muted-foreground", semanticClassNames.weekHeader), style: semanticStyles.weekHeader, children: "#" })) : null, weekdayLabels.map((label) => (_jsx("div", { role: "columnheader", className: "bg-muted/50 p-2 text-center text-xs font-medium text-muted-foreground", children: label }, label))), Array.from({ length: 6 }, (_, row) => {
146
+ const week = monthCells.slice(row * 7, row * 7 + 7);
147
+ const weekNumber = getWeekNumber(week[0]);
148
+ return [
149
+ showWeek ? (_jsx("div", { role: "rowheader", "data-slot": "calendar-week-number", className: cn("flex items-center justify-center bg-muted/30 px-2 text-xs text-muted-foreground", semanticClassNames.weekNumber), style: semanticStyles.weekNumber, children: weekNumber }, `week-${weekNumber}`)) : null,
150
+ ...week.map((date) => {
151
+ const outside = !sameMonth(date, panelDate);
152
+ const selected = sameDay(date, selectedValue);
153
+ const current = sameDay(date, today);
154
+ const disabled = isDisabled(date);
155
+ const key = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
156
+ const originNode = (_jsx("button", { type: "button", "data-calendar-date": key, disabled: disabled, "aria-selected": selected, "aria-current": current ? "date" : undefined, "aria-label": format(date, {
157
+ weekday: "long",
158
+ year: "numeric",
159
+ month: "long",
160
+ day: "numeric",
161
+ }), onClick: () => selectDate(date, "date"), onKeyDown: (event) => moveFocus(event, date), className: cn("flex min-h-20 w-full flex-col items-end rounded-none bg-background p-2 text-left outline-none transition-colors hover:bg-accent focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-40", outside && "text-muted-foreground/60", selected && "bg-primary/10"), children: _jsx("span", { className: cn("flex size-7 items-center justify-center rounded-full", selected && "bg-primary text-primary-foreground", current && !selected && "ring-1 ring-primary text-primary"), children: date.getDate() }) }));
162
+ return (_jsx("div", { role: "gridcell", "data-slot": "calendar-cell", "data-outside": outside || undefined, className: cn("min-w-0 bg-background", semanticClassNames.cell), style: semanticStyles.cell, children: _jsx("div", { "data-slot": "calendar-cell-inner", className: cn("h-full", semanticClassNames.cellInner), style: semanticStyles.cellInner, children: renderCell(date, originNode, "month") }) }, key));
163
+ }),
164
+ ];
165
+ })] })) : (_jsx("div", { role: "grid", "aria-label": format(panelDate, { year: "numeric" }), className: "grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-4", children: Array.from({ length: 12 }, (_, month) => {
166
+ const date = new Date(panelDate.getFullYear(), month, 1);
167
+ const disabled = Array.from({ length: new Date(date.getFullYear(), month + 1, 0).getDate() }, (_, day) => new Date(date.getFullYear(), month, day + 1)).every(isDisabled);
168
+ const selected = sameMonth(date, selectedValue);
169
+ const originNode = (_jsx("button", { type: "button", disabled: disabled, "aria-selected": selected, "aria-label": format(date, { month: "long" }), className: cn("min-h-20 w-full rounded-md border bg-background p-3 text-left outline-none transition-colors hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-40", selected && "border-primary bg-primary/10"), onClick: () => {
170
+ const next = new Date(date.getFullYear(), date.getMonth(), selectedValue.getDate());
171
+ const normalized = next.getMonth() === date.getMonth()
172
+ ? next
173
+ : new Date(date.getFullYear(), date.getMonth() + 1, 0);
174
+ selectDate(normalized, "month");
175
+ changePanel(normalized, "month");
176
+ }, children: format(date, { month: "long" }) }));
177
+ return (_jsx("div", { role: "gridcell", "data-slot": "calendar-cell", className: cn("min-w-0", semanticClassNames.cell), style: semanticStyles.cell, children: _jsx("div", { "data-slot": "calendar-cell-inner", className: cn(semanticClassNames.cellInner), style: semanticStyles.cellInner, children: renderCell(date, originNode, "year") }) }, month));
178
+ }) })) })] }));
179
+ }
@@ -0,0 +1,30 @@
1
+ import type { ElementType, HTMLAttributes, ReactNode } from "react";
2
+ export type CardVariant = "default" | "subtle" | "elevated";
3
+ export type CardPadding = "none" | "sm" | "base" | "lg";
4
+ /** A visual grouping surface. `interactive` only adds hover styling; use a
5
+ * semantic button or link as the host when the whole card performs an action. */
6
+ export interface CardProps extends HTMLAttributes<HTMLElement> {
7
+ as?: ElementType;
8
+ variant?: CardVariant;
9
+ padding?: CardPadding;
10
+ interactive?: boolean;
11
+ }
12
+ export interface CardHeaderProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
13
+ title?: ReactNode;
14
+ description?: ReactNode;
15
+ action?: ReactNode;
16
+ children?: ReactNode;
17
+ }
18
+ export type CardTitleProps = HTMLAttributes<HTMLHeadingElement>;
19
+ export type CardDescriptionProps = HTMLAttributes<HTMLParagraphElement>;
20
+ export interface CardContentProps extends HTMLAttributes<HTMLDivElement> {
21
+ /** Retained for compatibility; CardContent does not add padding itself. */
22
+ flush?: boolean;
23
+ }
24
+ export type CardFooterProps = HTMLAttributes<HTMLElement>;
25
+ export declare function Card({ as: Component, variant, padding, interactive, className, ...props }: CardProps): import("react").JSX.Element;
26
+ export declare function CardHeader({ title, description, action, children, className, ...props }: CardHeaderProps): import("react").JSX.Element;
27
+ export declare const CardTitle: ({ className, ...props }: CardTitleProps) => import("react").JSX.Element;
28
+ export declare const CardDescription: ({ className, ...props }: CardDescriptionProps) => import("react").JSX.Element;
29
+ export declare const CardContent: ({ className, flush: _flush, ...props }: CardContentProps) => import("react").JSX.Element;
30
+ export declare const CardFooter: ({ className, ...props }: CardFooterProps) => import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { cn } from "../lib/utils.js";
3
+ export function Card({ as: Component = "div", variant = "default", padding = "base", interactive = false, className, ...props }) {
4
+ return _jsx(Component, { ...props, "data-slot": "card", className: cn("min-w-0 rounded-lg border bg-card text-card-foreground flex flex-col gap-5", padding === "sm" ? "p-4" : padding === "lg" ? "p-8" : padding === "none" ? "p-0" : "p-6", variant === "default" && "shadow-surface", variant === "subtle" && "bg-muted", variant === "elevated" && "bg-raised shadow-raised", interactive && "cursor-pointer transition-[border-color,box-shadow,transform] hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-raised", className) });
5
+ }
6
+ export function CardHeader({ title, description, action, children, className, ...props }) {
7
+ return _jsx("header", { ...props, "data-slot": "card-header", className: cn("flex items-start justify-between gap-3", className), children: children !== undefined ? children : _jsxs(_Fragment, { children: [_jsxs("div", { children: [title !== undefined ? _jsx(CardTitle, { children: title }) : null, description !== undefined ? _jsx(CardDescription, { children: description }) : null] }), action] }) });
8
+ }
9
+ export const CardTitle = ({ className, ...props }) => _jsx("h3", { ...props, "data-slot": "card-title", className: cn("text-base font-semibold", className) });
10
+ export const CardDescription = ({ className, ...props }) => _jsx("p", { ...props, "data-slot": "card-description", className: cn("mt-1 text-sm text-muted-foreground", className) });
11
+ export const CardContent = ({ className, flush: _flush, ...props }) => _jsx("div", { ...props, "data-slot": "card-content", className: cn("min-w-0", className) });
12
+ export const CardFooter = ({ className, ...props }) => _jsx("footer", { ...props, "data-slot": "card-footer", className: cn("flex flex-wrap items-center gap-3", className) });
@@ -0,0 +1,49 @@
1
+ import { type CSSProperties, type HTMLAttributes, type ReactNode, type Ref } from "react";
2
+ export type CarouselEffect = "scrollx" | "fade";
3
+ export type CarouselDotPlacement = "top" | "bottom" | "start" | "end";
4
+ export type CarouselSemantic = "root" | "viewport" | "track" | "slide" | "arrows" | "prevArrow" | "nextArrow" | "dots" | "dot";
5
+ export interface CarouselAutoplayConfig {
6
+ dotDuration?: boolean;
7
+ }
8
+ export interface CarouselRef {
9
+ goTo: (slide: number, dontAnimate?: boolean) => void;
10
+ next: () => void;
11
+ prev: () => void;
12
+ }
13
+ export interface CarouselSemanticInfo {
14
+ props: Readonly<{
15
+ activeIndex: number;
16
+ arrows: boolean;
17
+ dots: boolean;
18
+ effect: CarouselEffect;
19
+ dotPlacement: CarouselDotPlacement;
20
+ }>;
21
+ }
22
+ export type CarouselClassNames = Partial<Record<CarouselSemantic, string>> | ((info: CarouselSemanticInfo) => Partial<Record<CarouselSemantic, string>>);
23
+ export type CarouselStyles = Partial<Record<CarouselSemantic, CSSProperties>> | ((info: CarouselSemanticInfo) => Partial<Record<CarouselSemantic, CSSProperties>>);
24
+ export interface CarouselProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "onChange"> {
25
+ items: readonly ReactNode[];
26
+ activeIndex?: number;
27
+ defaultActiveIndex?: number;
28
+ arrows?: boolean;
29
+ dots?: boolean;
30
+ dotPlacement?: CarouselDotPlacement;
31
+ autoplay?: boolean | CarouselAutoplayConfig;
32
+ autoplaySpeed?: number;
33
+ adaptiveHeight?: boolean;
34
+ draggable?: boolean;
35
+ effect?: CarouselEffect;
36
+ infinite?: boolean;
37
+ speed?: number;
38
+ easing?: string;
39
+ waitForAnimate?: boolean;
40
+ pauseOnHover?: boolean;
41
+ pauseOnFocus?: boolean;
42
+ beforeChange?: (current: number, next: number) => void;
43
+ afterChange?: (current: number) => void;
44
+ onChange?: (current: number) => void;
45
+ classNames?: CarouselClassNames;
46
+ styles?: CarouselStyles;
47
+ ref?: Ref<CarouselRef>;
48
+ }
49
+ export declare function Carousel({ items, activeIndex, defaultActiveIndex, arrows, dots, dotPlacement, autoplay, autoplaySpeed, adaptiveHeight, draggable, effect, infinite, speed, easing, waitForAnimate, pauseOnHover, pauseOnFocus, beforeChange, afterChange, onChange, classNames, styles, className, style, ref, "aria-label": ariaLabel, ...props }: CarouselProps): import("react").JSX.Element | null;
@@ -0,0 +1,166 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useImperativeHandle, useRef, useState, } from "react";
3
+ import { ChevronLeft, ChevronRight } from "lucide-react";
4
+ import { cn } from "../lib/utils.js";
5
+ import { IconButton } from "./icon-button.js";
6
+ function localizedLabel(en, zh) {
7
+ return typeof document !== "undefined" &&
8
+ document.documentElement.lang.startsWith("en")
9
+ ? en
10
+ : zh;
11
+ }
12
+ function normalizeIndex(index, count, infinite) {
13
+ if (!count)
14
+ return 0;
15
+ if (infinite)
16
+ return ((index % count) + count) % count;
17
+ return Math.min(count - 1, Math.max(0, index));
18
+ }
19
+ export function Carousel({ items, activeIndex, defaultActiveIndex = 0, arrows = false, dots = true, dotPlacement = "bottom", autoplay = false, autoplaySpeed = 3000, adaptiveHeight = false, draggable = false, effect = "scrollx", infinite = true, speed = 500, easing = "ease", waitForAnimate = false, pauseOnHover = true, pauseOnFocus = true, beforeChange, afterChange, onChange, classNames, styles, className, style, ref, "aria-label": ariaLabel, ...props }) {
20
+ const count = items.length;
21
+ const controlled = activeIndex !== undefined;
22
+ const [internalIndex, setInternalIndex] = useState(() => normalizeIndex(defaultActiveIndex, count, infinite));
23
+ const current = normalizeIndex(controlled ? activeIndex : internalIndex, count, infinite);
24
+ const [animating, setAnimating] = useState(false);
25
+ const [paused, setPaused] = useState(false);
26
+ const pointerStart = useRef(null);
27
+ const animationTimer = useRef(null);
28
+ useEffect(() => {
29
+ return () => {
30
+ if (animationTimer.current)
31
+ clearTimeout(animationTimer.current);
32
+ };
33
+ }, []);
34
+ const goTo = (target, dontAnimate = false) => {
35
+ if (!count || (waitForAnimate && animating))
36
+ return;
37
+ const nextIndex = normalizeIndex(target, count, infinite);
38
+ if (nextIndex === current)
39
+ return;
40
+ beforeChange?.(current, nextIndex);
41
+ if (!controlled)
42
+ setInternalIndex(nextIndex);
43
+ onChange?.(nextIndex);
44
+ if (dontAnimate || speed <= 0) {
45
+ setAnimating(false);
46
+ afterChange?.(nextIndex);
47
+ return;
48
+ }
49
+ setAnimating(true);
50
+ if (animationTimer.current)
51
+ clearTimeout(animationTimer.current);
52
+ animationTimer.current = setTimeout(() => {
53
+ setAnimating(false);
54
+ afterChange?.(nextIndex);
55
+ }, speed);
56
+ };
57
+ const next = () => {
58
+ if (!infinite && current >= count - 1)
59
+ return;
60
+ goTo(current + 1);
61
+ };
62
+ const prev = () => {
63
+ if (!infinite && current <= 0)
64
+ return;
65
+ goTo(current - 1);
66
+ };
67
+ useImperativeHandle(ref, () => ({ goTo, next, prev }), [current, count, infinite, animating]);
68
+ useEffect(() => {
69
+ if (!autoplay || paused || count < 2)
70
+ return;
71
+ if (typeof window !== "undefined" &&
72
+ window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
73
+ return;
74
+ }
75
+ const timer = window.setInterval(next, Math.max(250, autoplaySpeed));
76
+ return () => window.clearInterval(timer);
77
+ }, [autoplay, autoplaySpeed, paused, current, count, infinite, animating]);
78
+ if (!count)
79
+ return null;
80
+ const semanticInfo = {
81
+ props: { activeIndex: current, arrows, dots, effect, dotPlacement },
82
+ };
83
+ const semanticClassNames = typeof classNames === "function" ? classNames(semanticInfo) : (classNames ?? {});
84
+ const semanticStyles = typeof styles === "function" ? styles(semanticInfo) : (styles ?? {});
85
+ const autoplayConfig = typeof autoplay === "object" ? autoplay : undefined;
86
+ const arrowNodes = arrows ? (_jsxs("div", { "data-slot": "carousel-arrows", className: cn("pointer-events-none absolute inset-0 z-20 flex items-center justify-between px-2", semanticClassNames.arrows), style: semanticStyles.arrows, children: [_jsx(IconButton, { variant: "outline", size: "small", label: localizedLabel("Previous slide", "上一张"), icon: _jsx(ChevronLeft, { "aria-hidden": "true" }), onClick: prev, disabled: !infinite && current === 0, className: cn("pointer-events-auto", semanticClassNames.prevArrow), style: semanticStyles.prevArrow }), _jsx(IconButton, { variant: "outline", size: "small", label: localizedLabel("Next slide", "下一张"), icon: _jsx(ChevronRight, { "aria-hidden": "true" }), onClick: next, disabled: !infinite && current === count - 1, className: cn("pointer-events-auto", semanticClassNames.nextArrow), style: semanticStyles.nextArrow })] })) : null;
87
+ const dotNodes = dots ? (_jsx("div", { "data-slot": "carousel-dots", role: "tablist", "aria-label": localizedLabel("Choose slide", "选择轮播项"), className: cn("z-20 flex items-center justify-center gap-2", (dotPlacement === "top" || dotPlacement === "bottom") && "px-3 py-2", (dotPlacement === "start" || dotPlacement === "end") &&
88
+ "absolute inset-y-0 flex-col px-2", dotPlacement === "top" && "order-first", dotPlacement === "start" && "left-0", dotPlacement === "end" && "right-0", semanticClassNames.dots), style: semanticStyles.dots, children: items.map((_, index) => {
89
+ const selected = index === current;
90
+ return (_jsx("button", { type: "button", role: "tab", "aria-selected": selected, "aria-label": `${localizedLabel("Slide", "第")} ${index + 1}`, onClick: () => goTo(index), "data-slot": "carousel-dot", className: cn("relative h-2 overflow-hidden rounded-full bg-muted-foreground/30 outline-none transition-[width,background-color] focus-visible:ring-2 focus-visible:ring-ring", selected ? "w-8 bg-primary" : "w-2 hover:bg-muted-foreground/60", semanticClassNames.dot), style: semanticStyles.dot, children: selected && autoplay && autoplayConfig?.dotDuration ? (_jsx("span", { "aria-hidden": "true", className: "absolute inset-y-0 left-0 bg-primary-foreground/50", style: {
91
+ width: "100%",
92
+ transformOrigin: "left",
93
+ animation: `gouno-carousel-dot ${Math.max(250, autoplaySpeed)}ms linear`,
94
+ } })) : null }, index));
95
+ }) })) : null;
96
+ return (_jsxs("div", { ...props, "data-slot": "carousel", role: "region", "aria-roledescription": "carousel", "aria-label": ariaLabel ?? localizedLabel("Carousel", "轮播"), "data-effect": effect, "data-dot-placement": dotPlacement, className: cn("relative flex min-w-0 flex-col", semanticClassNames.root, className), style: { ...semanticStyles.root, ...style }, onMouseEnter: (event) => {
97
+ if (pauseOnHover)
98
+ setPaused(true);
99
+ props.onMouseEnter?.(event);
100
+ }, onMouseLeave: (event) => {
101
+ if (pauseOnHover)
102
+ setPaused(false);
103
+ props.onMouseLeave?.(event);
104
+ }, onFocusCapture: (event) => {
105
+ if (pauseOnFocus)
106
+ setPaused(true);
107
+ props.onFocusCapture?.(event);
108
+ }, onBlurCapture: (event) => {
109
+ if (pauseOnFocus &&
110
+ !event.currentTarget.contains(event.relatedTarget)) {
111
+ setPaused(false);
112
+ }
113
+ props.onBlurCapture?.(event);
114
+ }, children: [dotPlacement === "top" ? dotNodes : null, _jsxs("div", { "data-slot": "carousel-viewport", className: cn("relative min-w-0 overflow-hidden rounded-lg", adaptiveHeight && "transition-[height]", semanticClassNames.viewport), style: semanticStyles.viewport, onPointerDown: (event) => {
115
+ if (!draggable)
116
+ return;
117
+ pointerStart.current = {
118
+ id: event.pointerId,
119
+ x: event.clientX,
120
+ y: event.clientY,
121
+ };
122
+ event.currentTarget.setPointerCapture(event.pointerId);
123
+ }, onPointerUp: (event) => {
124
+ const start = pointerStart.current;
125
+ pointerStart.current = null;
126
+ if (!draggable || !start || start.id !== event.pointerId)
127
+ return;
128
+ const dx = event.clientX - start.x;
129
+ const dy = event.clientY - start.y;
130
+ if (Math.abs(dx) < 40 || Math.abs(dx) < Math.abs(dy))
131
+ return;
132
+ if (dx < 0)
133
+ next();
134
+ else
135
+ prev();
136
+ }, onPointerCancel: () => {
137
+ pointerStart.current = null;
138
+ }, children: [_jsx("div", { "data-slot": "carousel-track", className: cn("relative flex min-w-0", effect === "fade" && "grid", semanticClassNames.track), style: {
139
+ ...(effect === "scrollx"
140
+ ? {
141
+ width: `${count * 100}%`,
142
+ transform: `translateX(-${(current * 100) / count}%)`,
143
+ transitionProperty: "transform",
144
+ transitionDuration: `${speed}ms`,
145
+ transitionTimingFunction: easing,
146
+ }
147
+ : undefined),
148
+ ...semanticStyles.track,
149
+ }, children: items.map((item, index) => {
150
+ const selected = index === current;
151
+ return (_jsx("div", { "data-slot": "carousel-slide", role: "group", "aria-roledescription": "slide", "aria-label": `${index + 1} / ${count}`, "aria-hidden": !selected, className: cn("min-w-0", effect === "scrollx" && "shrink-0", effect === "fade" &&
152
+ "col-start-1 row-start-1 transition-opacity", effect === "fade" &&
153
+ (selected
154
+ ? "z-10 opacity-100"
155
+ : "pointer-events-none opacity-0"), semanticClassNames.slide), style: {
156
+ ...(effect === "scrollx" ? { width: `${100 / count}%` } : undefined),
157
+ ...(effect === "fade"
158
+ ? {
159
+ transitionDuration: `${speed}ms`,
160
+ transitionTimingFunction: easing,
161
+ }
162
+ : undefined),
163
+ ...semanticStyles.slide,
164
+ }, children: item }, index));
165
+ }) }), arrowNodes] }), dotPlacement === "bottom" ? dotNodes : null, dotPlacement === "start" || dotPlacement === "end" ? dotNodes : null] }));
166
+ }
@@ -0,0 +1,19 @@
1
+ import { type HTMLAttributes } from "react";
2
+ import { type ControlSize } from "./control-types.js";
3
+ export interface CascaderOption {
4
+ value: string;
5
+ label: string;
6
+ disabled?: boolean;
7
+ children?: readonly CascaderOption[];
8
+ }
9
+ export interface CascaderProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "onChange" | "role" | "aria-disabled"> {
10
+ options: readonly CascaderOption[];
11
+ value?: readonly string[];
12
+ defaultValue?: readonly string[];
13
+ onChange?: (value: string[], selected: CascaderOption[]) => void;
14
+ placeholder?: string;
15
+ disabled?: boolean;
16
+ size?: ControlSize;
17
+ status?: "error" | "warning";
18
+ }
19
+ export declare const Cascader: import("react").ForwardRefExoticComponent<CascaderProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { forwardRef, useMemo, useState, } from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ import { controlSizeClass } from "./control-types.js";
5
+ function resolveSelected(options, path) {
6
+ const selected = [];
7
+ let items = options;
8
+ for (const value of path) {
9
+ const option = items.find((item) => item.value === value);
10
+ if (!option)
11
+ break;
12
+ selected.push(option);
13
+ items = option.children ?? [];
14
+ }
15
+ return selected;
16
+ }
17
+ export const Cascader = forwardRef(function Cascader({ options, value, defaultValue = [], onChange, placeholder, disabled = false, size = "middle", status, className, ...props }, ref) {
18
+ const [inner, setInner] = useState(defaultValue);
19
+ const current = value ?? inner;
20
+ const invalid = status === "error" ? true : props["aria-invalid"];
21
+ const levels = useMemo(() => {
22
+ const result = [options];
23
+ let items = options;
24
+ for (const selectedValue of current) {
25
+ const selected = items.find((item) => item.value === selectedValue);
26
+ if (!selected?.children?.length)
27
+ break;
28
+ items = selected.children;
29
+ result.push(items);
30
+ }
31
+ return result;
32
+ }, [options, current]);
33
+ const update = (level, selectedValue) => {
34
+ const next = selectedValue
35
+ ? [...current.slice(0, level), selectedValue]
36
+ : [...current.slice(0, level)];
37
+ const selected = resolveSelected(options, next);
38
+ if (value === undefined)
39
+ setInner(next);
40
+ onChange?.(next, selected);
41
+ };
42
+ return (_jsx("div", { ...props, ref: ref, role: "group", "aria-disabled": disabled || undefined, "aria-invalid": invalid, "data-slot": "cascader", "data-status": status, className: cn("flex flex-wrap gap-2", className), children: levels.map((items, level) => (_jsxs("select", { "data-slot": "cascader-select", "data-level": level, disabled: disabled, value: current[level] ?? "", "aria-label": String(level + 1), "aria-invalid": invalid, onChange: (event) => update(level, event.currentTarget.value), className: cn("min-w-32 rounded-md border border-border bg-input px-3 outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", controlSizeClass(size), status === "error" &&
43
+ "border-destructive focus-visible:ring-destructive", status === "warning" &&
44
+ "border-warning focus-visible:ring-warning"), children: [_jsx("option", { value: "", children: placeholder ?? "" }), items.map((item) => (_jsx("option", { value: item.value, disabled: item.disabled, children: item.label }, item.value)))] }, level))) }));
45
+ });
@@ -0,0 +1,16 @@
1
+ import { type HTMLAttributes, type ReactNode } from "react";
2
+ export interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
3
+ /** Canonical source text used for both display input and clipboard copy. */
4
+ code: string;
5
+ /** Optional language label. Syntax rendering remains caller-owned through renderCode. */
6
+ language?: string;
7
+ /** Whether to expose the copy action. */
8
+ copyable?: boolean;
9
+ /** Accessible and visible label for the copy action. */
10
+ copyLabel?: string;
11
+ /** Accessible and visible label shown after a successful copy. */
12
+ copiedLabel?: string;
13
+ /** Optional presentation hook for syntax highlighting. Receives the exact canonical code string. */
14
+ renderCode?: (code: string) => ReactNode;
15
+ }
16
+ export declare function CodeBlock({ code, language, copyable, copyLabel, copiedLabel, renderCode, className, ...props }: CodeBlockProps): import("react").JSX.Element;
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Check, Copy } from "lucide-react";
4
+ import { cn } from "../lib/utils.js";
5
+ import { Button } from "./button.js";
6
+ async function copyText(value) {
7
+ if (navigator.clipboard?.writeText) {
8
+ try {
9
+ await navigator.clipboard.writeText(value);
10
+ return;
11
+ }
12
+ catch {
13
+ // Fall back to the document copy path for restricted clipboard contexts.
14
+ }
15
+ }
16
+ const textarea = document.createElement("textarea");
17
+ textarea.value = value;
18
+ textarea.setAttribute("readonly", "");
19
+ textarea.style.position = "fixed";
20
+ textarea.style.opacity = "0";
21
+ document.body.appendChild(textarea);
22
+ textarea.select();
23
+ const copied = document.execCommand("copy");
24
+ textarea.remove();
25
+ if (!copied)
26
+ throw new Error("Unable to copy code");
27
+ }
28
+ export function CodeBlock({ code, language, copyable = true, copyLabel = "复制代码", copiedLabel = "代码已复制", renderCode, className, ...props }) {
29
+ const [copied, setCopied] = useState(false);
30
+ useEffect(() => {
31
+ if (!copied)
32
+ return;
33
+ const timer = window.setTimeout(() => setCopied(false), 1800);
34
+ return () => window.clearTimeout(timer);
35
+ }, [copied]);
36
+ const showToolbar = Boolean(language) || copyable;
37
+ const handleCopy = async () => {
38
+ try {
39
+ await copyText(code);
40
+ setCopied(true);
41
+ }
42
+ catch {
43
+ setCopied(false);
44
+ }
45
+ };
46
+ return (_jsxs("div", { ...props, "data-slot": "code-block", "data-language": language || undefined, className: cn("min-w-0 overflow-hidden rounded-lg border bg-card", className), children: [showToolbar ? (_jsxs("div", { "data-slot": "code-block-toolbar", className: "flex min-h-10 items-center justify-between gap-3 border-b bg-muted/40 px-3", children: [language ? (_jsx("span", { "data-slot": "code-block-language", className: "min-w-0 truncate font-mono text-xs uppercase tracking-wide text-muted-foreground", children: language })) : (_jsx("span", { "aria-hidden": "true" })), copyable ? (_jsx(Button, { type: "button", size: "small", variant: "text", className: "h-8 shrink-0 px-2 text-xs text-muted-foreground", "aria-label": copied ? copiedLabel : copyLabel, icon: copied ? _jsx(Check, { className: "size-3.5" }) : _jsx(Copy, { className: "size-3.5" }), onClick: () => void handleCopy(), children: _jsx("span", { "aria-live": "polite", children: copied ? copiedLabel : copyLabel }) })) : null] })) : null, _jsx("pre", { "data-slot": "code-block-pre", className: "m-0 max-w-full overflow-auto bg-background p-4 font-mono text-sm leading-6 text-foreground [tab-size:2]", children: _jsx("code", { "data-slot": "code-block-code", children: renderCode ? renderCode(code) : code }) })] }));
47
+ }
@@ -0,0 +1,49 @@
1
+ import { type CSSProperties, type HTMLAttributes, type Key, type ReactNode, type Ref } from "react";
2
+ export type CollapseSize = "small" | "middle" | "large";
3
+ export type CollapseCollapsible = "header" | "icon" | "disabled";
4
+ export type CollapseExpandIconPlacement = "start" | "end";
5
+ export type CollapseActiveKey = Key | readonly Key[];
6
+ export interface CollapseItem {
7
+ key: Key;
8
+ label: ReactNode;
9
+ children: ReactNode;
10
+ extra?: ReactNode;
11
+ collapsible?: CollapseCollapsible;
12
+ forceRender?: boolean;
13
+ showArrow?: boolean;
14
+ classNames?: Partial<Record<"header" | "body", string>>;
15
+ styles?: Partial<Record<"header" | "body", CSSProperties>>;
16
+ }
17
+ export interface CollapseExpandIconInfo {
18
+ active: boolean;
19
+ item: CollapseItem;
20
+ }
21
+ export type CollapseSemantic = "root" | "item" | "header" | "body";
22
+ export interface CollapseSemanticInfo {
23
+ props: Readonly<{
24
+ accordion: boolean;
25
+ bordered: boolean;
26
+ ghost: boolean;
27
+ size: CollapseSize;
28
+ }>;
29
+ }
30
+ export type CollapseClassNames = Partial<Record<CollapseSemantic, string>> | ((info: CollapseSemanticInfo) => Partial<Record<CollapseSemantic, string>>);
31
+ export type CollapseStyles = Partial<Record<CollapseSemantic, CSSProperties>> | ((info: CollapseSemanticInfo) => Partial<Record<CollapseSemantic, CSSProperties>>);
32
+ export interface CollapseProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "onChange"> {
33
+ items: readonly CollapseItem[];
34
+ activeKey?: CollapseActiveKey;
35
+ defaultActiveKey?: CollapseActiveKey;
36
+ accordion?: boolean;
37
+ bordered?: boolean;
38
+ collapsible?: CollapseCollapsible;
39
+ destroyOnHidden?: boolean;
40
+ expandIcon?: (info: CollapseExpandIconInfo) => ReactNode;
41
+ expandIconPlacement?: CollapseExpandIconPlacement;
42
+ ghost?: boolean;
43
+ size?: CollapseSize;
44
+ onChange?: (activeKey: Key | Key[]) => void;
45
+ classNames?: CollapseClassNames;
46
+ styles?: CollapseStyles;
47
+ ref?: Ref<HTMLDivElement>;
48
+ }
49
+ export declare function Collapse({ items, activeKey, defaultActiveKey, accordion, bordered, collapsible, destroyOnHidden, expandIcon, expandIconPlacement, ghost, size, onChange, classNames, styles, className, style, ref, ...props }: CollapseProps): import("react").JSX.Element;