@ptengine/design-components 0.3.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 (197) hide show
  1. package/README.md +309 -0
  2. package/components.json +21 -0
  3. package/dist/components/ui/accordion.d.ts +8 -0
  4. package/dist/components/ui/accordion.d.ts.map +1 -0
  5. package/dist/components/ui/alert-dialog.d.ts +21 -0
  6. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  7. package/dist/components/ui/alert.d.ts +9 -0
  8. package/dist/components/ui/alert.d.ts.map +1 -0
  9. package/dist/components/ui/aspect-ratio.d.ts +4 -0
  10. package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
  11. package/dist/components/ui/attachment.d.ts +26 -0
  12. package/dist/components/ui/attachment.d.ts.map +1 -0
  13. package/dist/components/ui/avatar.d.ts +10 -0
  14. package/dist/components/ui/avatar.d.ts.map +1 -0
  15. package/dist/components/ui/badge.d.ts +10 -0
  16. package/dist/components/ui/badge.d.ts.map +1 -0
  17. package/dist/components/ui/breadcrumb.d.ts +22 -0
  18. package/dist/components/ui/breadcrumb.d.ts.map +1 -0
  19. package/dist/components/ui/bubble.d.ts +19 -0
  20. package/dist/components/ui/bubble.d.ts.map +1 -0
  21. package/dist/components/ui/button-group.d.ts +13 -0
  22. package/dist/components/ui/button-group.d.ts.map +1 -0
  23. package/dist/components/ui/button.d.ts +13 -0
  24. package/dist/components/ui/button.d.ts.map +1 -0
  25. package/dist/components/ui/calendar.d.ts +9 -0
  26. package/dist/components/ui/calendar.d.ts.map +1 -0
  27. package/dist/components/ui/card.d.ts +9 -0
  28. package/dist/components/ui/card.d.ts.map +1 -0
  29. package/dist/components/ui/carousel.d.ts +23 -0
  30. package/dist/components/ui/carousel.d.ts.map +1 -0
  31. package/dist/components/ui/chart.d.ts +63 -0
  32. package/dist/components/ui/chart.d.ts.map +1 -0
  33. package/dist/components/ui/checkbox.d.ts +5 -0
  34. package/dist/components/ui/checkbox.d.ts.map +1 -0
  35. package/dist/components/ui/collapsible.d.ts +6 -0
  36. package/dist/components/ui/collapsible.d.ts.map +1 -0
  37. package/dist/components/ui/command.d.ts +81 -0
  38. package/dist/components/ui/command.d.ts.map +1 -0
  39. package/dist/components/ui/context-menu.d.ts +28 -0
  40. package/dist/components/ui/context-menu.d.ts.map +1 -0
  41. package/dist/components/ui/dialog.d.ts +22 -0
  42. package/dist/components/ui/dialog.d.ts.map +1 -0
  43. package/dist/components/ui/direction.d.ts +8 -0
  44. package/dist/components/ui/direction.d.ts.map +1 -0
  45. package/dist/components/ui/drawer.d.ts +23 -0
  46. package/dist/components/ui/drawer.d.ts.map +1 -0
  47. package/dist/components/ui/dropdown-menu.d.ts +28 -0
  48. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  49. package/dist/components/ui/empty.d.ts +13 -0
  50. package/dist/components/ui/empty.d.ts.map +1 -0
  51. package/dist/components/ui/field.d.ts +25 -0
  52. package/dist/components/ui/field.d.ts.map +1 -0
  53. package/dist/components/ui/hover-card.d.ts +7 -0
  54. package/dist/components/ui/hover-card.d.ts.map +1 -0
  55. package/dist/components/ui/input-group.d.ts +13 -0
  56. package/dist/components/ui/input-group.d.ts.map +1 -0
  57. package/dist/components/ui/input-otp.d.ts +35 -0
  58. package/dist/components/ui/input-otp.d.ts.map +1 -0
  59. package/dist/components/ui/input.d.ts +5 -0
  60. package/dist/components/ui/input.d.ts.map +1 -0
  61. package/dist/components/ui/item.d.ts +24 -0
  62. package/dist/components/ui/item.d.ts.map +1 -0
  63. package/dist/components/ui/kbd.d.ts +5 -0
  64. package/dist/components/ui/kbd.d.ts.map +1 -0
  65. package/dist/components/ui/label.d.ts +6 -0
  66. package/dist/components/ui/label.d.ts.map +1 -0
  67. package/dist/components/ui/marker.d.ts +13 -0
  68. package/dist/components/ui/marker.d.ts.map +1 -0
  69. package/dist/components/ui/menubar.d.ts +29 -0
  70. package/dist/components/ui/menubar.d.ts.map +1 -0
  71. package/dist/components/ui/message-scroller.d.ts +26 -0
  72. package/dist/components/ui/message-scroller.d.ts.map +1 -0
  73. package/dist/components/ui/message.d.ts +11 -0
  74. package/dist/components/ui/message.d.ts.map +1 -0
  75. package/dist/components/ui/native-select.d.ts +4 -0
  76. package/dist/components/ui/native-select.d.ts.map +1 -0
  77. package/dist/components/ui/navigation-menu.d.ts +13 -0
  78. package/dist/components/ui/navigation-menu.d.ts.map +1 -0
  79. package/dist/components/ui/pagination.d.ts +35 -0
  80. package/dist/components/ui/pagination.d.ts.map +1 -0
  81. package/dist/components/ui/popover.d.ts +8 -0
  82. package/dist/components/ui/popover.d.ts.map +1 -0
  83. package/dist/components/ui/progress.d.ts +5 -0
  84. package/dist/components/ui/progress.d.ts.map +1 -0
  85. package/dist/components/ui/radio-group.d.ts +6 -0
  86. package/dist/components/ui/radio-group.d.ts.map +1 -0
  87. package/dist/components/ui/resizable.d.ts +24 -0
  88. package/dist/components/ui/resizable.d.ts.map +1 -0
  89. package/dist/components/ui/scroll-area.d.ts +6 -0
  90. package/dist/components/ui/scroll-area.d.ts.map +1 -0
  91. package/dist/components/ui/search-input.d.ts +23 -0
  92. package/dist/components/ui/search-input.d.ts.map +1 -0
  93. package/dist/components/ui/select.d.ts +14 -0
  94. package/dist/components/ui/select.d.ts.map +1 -0
  95. package/dist/components/ui/separator.d.ts +5 -0
  96. package/dist/components/ui/separator.d.ts.map +1 -0
  97. package/dist/components/ui/sheet.d.ts +28 -0
  98. package/dist/components/ui/sheet.d.ts.map +1 -0
  99. package/dist/components/ui/sidebar.d.ts +70 -0
  100. package/dist/components/ui/sidebar.d.ts.map +1 -0
  101. package/dist/components/ui/skeleton.d.ts +4 -0
  102. package/dist/components/ui/skeleton.d.ts.map +1 -0
  103. package/dist/components/ui/slider.d.ts +5 -0
  104. package/dist/components/ui/slider.d.ts.map +1 -0
  105. package/dist/components/ui/sonner.d.ts +6 -0
  106. package/dist/components/ui/sonner.d.ts.map +1 -0
  107. package/dist/components/ui/spinner.d.ts +7 -0
  108. package/dist/components/ui/spinner.d.ts.map +1 -0
  109. package/dist/components/ui/switch.d.ts +5 -0
  110. package/dist/components/ui/switch.d.ts.map +1 -0
  111. package/dist/components/ui/table.d.ts +11 -0
  112. package/dist/components/ui/table.d.ts.map +1 -0
  113. package/dist/components/ui/tabs.d.ts +13 -0
  114. package/dist/components/ui/tabs.d.ts.map +1 -0
  115. package/dist/components/ui/textarea.d.ts +5 -0
  116. package/dist/components/ui/textarea.d.ts.map +1 -0
  117. package/dist/components/ui/toggle-group.d.ts +13 -0
  118. package/dist/components/ui/toggle-group.d.ts.map +1 -0
  119. package/dist/components/ui/toggle.d.ts +13 -0
  120. package/dist/components/ui/toggle.d.ts.map +1 -0
  121. package/dist/components/ui/tooltip.d.ts +8 -0
  122. package/dist/components/ui/tooltip.d.ts.map +1 -0
  123. package/dist/hooks/use-mobile.d.ts +2 -0
  124. package/dist/hooks/use-mobile.d.ts.map +1 -0
  125. package/dist/index.cjs +82 -0
  126. package/dist/index.d.ts +71 -0
  127. package/dist/index.d.ts.map +1 -0
  128. package/dist/index.js +20969 -0
  129. package/dist/lib/utils.d.ts +4 -0
  130. package/dist/lib/utils.d.ts.map +1 -0
  131. package/llms.txt +173 -0
  132. package/package.json +125 -0
  133. package/src/components/ui/accordion.tsx +51 -0
  134. package/src/components/ui/alert-dialog.tsx +115 -0
  135. package/src/components/ui/alert.tsx +48 -0
  136. package/src/components/ui/aspect-ratio.tsx +5 -0
  137. package/src/components/ui/attachment.tsx +186 -0
  138. package/src/components/ui/avatar.tsx +57 -0
  139. package/src/components/ui/badge.tsx +40 -0
  140. package/src/components/ui/breadcrumb.tsx +101 -0
  141. package/src/components/ui/bubble.tsx +123 -0
  142. package/src/components/ui/button-group.tsx +74 -0
  143. package/src/components/ui/button.tsx +64 -0
  144. package/src/components/ui/calendar.tsx +63 -0
  145. package/src/components/ui/card.tsx +55 -0
  146. package/src/components/ui/carousel.tsx +252 -0
  147. package/src/components/ui/chart.tsx +323 -0
  148. package/src/components/ui/checkbox.tsx +26 -0
  149. package/src/components/ui/collapsible.tsx +9 -0
  150. package/src/components/ui/command.tsx +136 -0
  151. package/src/components/ui/context-menu.tsx +187 -0
  152. package/src/components/ui/dialog.tsx +103 -0
  153. package/src/components/ui/direction.tsx +15 -0
  154. package/src/components/ui/drawer.tsx +98 -0
  155. package/src/components/ui/dropdown-menu.tsx +186 -0
  156. package/src/components/ui/empty.tsx +75 -0
  157. package/src/components/ui/field.tsx +175 -0
  158. package/src/components/ui/hover-card.tsx +27 -0
  159. package/src/components/ui/input-group.tsx +80 -0
  160. package/src/components/ui/input-otp.tsx +67 -0
  161. package/src/components/ui/input.tsx +24 -0
  162. package/src/components/ui/item.tsx +118 -0
  163. package/src/components/ui/kbd.tsx +21 -0
  164. package/src/components/ui/label.tsx +19 -0
  165. package/src/components/ui/marker.tsx +67 -0
  166. package/src/components/ui/menubar.tsx +218 -0
  167. package/src/components/ui/message-scroller.tsx +206 -0
  168. package/src/components/ui/message.tsx +85 -0
  169. package/src/components/ui/native-select.tsx +29 -0
  170. package/src/components/ui/navigation-menu.tsx +120 -0
  171. package/src/components/ui/pagination.tsx +108 -0
  172. package/src/components/ui/popover.tsx +31 -0
  173. package/src/components/ui/progress.tsx +23 -0
  174. package/src/components/ui/radio-group.tsx +36 -0
  175. package/src/components/ui/resizable.tsx +40 -0
  176. package/src/components/ui/scroll-area.tsx +44 -0
  177. package/src/components/ui/search-input.tsx +97 -0
  178. package/src/components/ui/select.tsx +151 -0
  179. package/src/components/ui/separator.tsx +24 -0
  180. package/src/components/ui/sheet.tsx +124 -0
  181. package/src/components/ui/sidebar.tsx +704 -0
  182. package/src/components/ui/skeleton.tsx +9 -0
  183. package/src/components/ui/slider.tsx +41 -0
  184. package/src/components/ui/sonner.tsx +45 -0
  185. package/src/components/ui/spinner.tsx +21 -0
  186. package/src/components/ui/switch.tsx +27 -0
  187. package/src/components/ui/table.tsx +95 -0
  188. package/src/components/ui/tabs.tsx +119 -0
  189. package/src/components/ui/textarea.tsx +23 -0
  190. package/src/components/ui/toggle-group.tsx +55 -0
  191. package/src/components/ui/toggle.tsx +40 -0
  192. package/src/components/ui/tooltip.tsx +30 -0
  193. package/src/hooks/use-mobile.ts +19 -0
  194. package/src/index.ts +414 -0
  195. package/src/lib/utils.ts +7 -0
  196. package/src/styles/tokens.css +360 -0
  197. package/tailwind.preset.js +235 -0
@@ -0,0 +1,9 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '../../lib/utils';
4
+
5
+ function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
6
+ return <div className={cn('animate-pulse rounded-[var(--pt-radius-md)] bg-muted', className)} {...props} />;
7
+ }
8
+
9
+ export { Skeleton };
@@ -0,0 +1,41 @@
1
+ import * as React from 'react';
2
+ import * as SliderPrimitive from '@radix-ui/react-slider';
3
+
4
+ import { cn } from '../../lib/utils';
5
+
6
+ const Slider = React.forwardRef<
7
+ React.ElementRef<typeof SliderPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
9
+ >(({ className, defaultValue, value, min = 0, ...props }, ref) => {
10
+ const values = React.useMemo(
11
+ () => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min]),
12
+ [value, defaultValue, min]
13
+ );
14
+
15
+ return (
16
+ <SliderPrimitive.Root
17
+ ref={ref}
18
+ defaultValue={defaultValue}
19
+ value={value}
20
+ min={min}
21
+ className={cn(
22
+ 'relative flex w-full touch-none select-none items-center data-[disabled]:cursor-not-allowed',
23
+ className
24
+ )}
25
+ {...props}
26
+ >
27
+ <SliderPrimitive.Track className="relative h-[6px] w-full grow overflow-hidden rounded-full bg-secondary">
28
+ <SliderPrimitive.Range className="absolute h-full bg-primary" />
29
+ </SliderPrimitive.Track>
30
+ {values.map((_, index) => (
31
+ <SliderPrimitive.Thumb
32
+ key={index}
33
+ className="block h-[16px] w-[16px] shrink-0 rounded-full border border-neutral bg-background transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring data-[disabled]:opacity-50"
34
+ />
35
+ ))}
36
+ </SliderPrimitive.Root>
37
+ );
38
+ });
39
+ Slider.displayName = SliderPrimitive.Root.displayName;
40
+
41
+ export { Slider };
@@ -0,0 +1,45 @@
1
+ import * as React from 'react';
2
+ import { Toaster as Sonner, toast } from 'sonner';
3
+
4
+ type ToasterProps = React.ComponentProps<typeof Sonner>;
5
+
6
+ /** Reads the current theme from the `dark` class on <html> (no next-themes dependency). */
7
+ function useDocumentTheme(): 'light' | 'dark' {
8
+ const [theme, setTheme] = React.useState<'light' | 'dark'>(() =>
9
+ typeof document !== 'undefined' && document.documentElement.classList.contains('dark')
10
+ ? 'dark'
11
+ : 'light'
12
+ );
13
+ React.useEffect(() => {
14
+ const observer = new MutationObserver(() =>
15
+ setTheme(document.documentElement.classList.contains('dark') ? 'dark' : 'light')
16
+ );
17
+ observer.observe(document.documentElement, {
18
+ attributes: true,
19
+ attributeFilter: ['class']
20
+ });
21
+ return () => observer.disconnect();
22
+ }, []);
23
+ return theme;
24
+ }
25
+
26
+ const Toaster = ({ ...props }: ToasterProps) => {
27
+ const theme = useDocumentTheme();
28
+ return (
29
+ <Sonner
30
+ theme={theme}
31
+ className="toaster group"
32
+ toastOptions={{
33
+ classNames: {
34
+ toast: 'group toast group-[.toaster]:bg-popover group-[.toaster]:text-popover-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
35
+ description: 'group-[.toast]:text-muted-foreground',
36
+ actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
37
+ cancelButton: 'group-[.toast]:bg-secondary group-[.toast]:text-secondary-foreground'
38
+ }
39
+ }}
40
+ {...props}
41
+ />
42
+ );
43
+ };
44
+
45
+ export { Toaster, toast };
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+ import { Loader2 } from 'lucide-react';
3
+
4
+ import { cn } from '../../lib/utils';
5
+
6
+ function Spinner({
7
+ className,
8
+ label = 'Loading',
9
+ ...props
10
+ }: React.ComponentProps<typeof Loader2> & { label?: string }) {
11
+ return (
12
+ <Loader2
13
+ role="status"
14
+ aria-label={label}
15
+ className={cn('size-[16px] animate-spin text-foreground', className)}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ export { Spinner };
@@ -0,0 +1,27 @@
1
+ import * as React from 'react';
2
+ import * as SwitchPrimitives from '@radix-ui/react-switch';
3
+
4
+ import { cn } from '../../lib/utils';
5
+
6
+ const Switch = React.forwardRef<
7
+ React.ElementRef<typeof SwitchPrimitives.Root>,
8
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
9
+ >(({ className, ...props }, ref) => (
10
+ <SwitchPrimitives.Root
11
+ className={cn(
12
+ 'peer state-layer inline-flex h-[20px] w-[36px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input [--pt-state-layer-border:2px]',
13
+ className
14
+ )}
15
+ {...props}
16
+ ref={ref}
17
+ >
18
+ <SwitchPrimitives.Thumb
19
+ className={cn(
20
+ 'pointer-events-none block h-[16px] w-[16px] rounded-full border border-neutral bg-background ring-0 transition-transform data-[state=checked]:translate-x-[16px] data-[state=unchecked]:translate-x-0'
21
+ )}
22
+ />
23
+ </SwitchPrimitives.Root>
24
+ ));
25
+ Switch.displayName = SwitchPrimitives.Root.displayName;
26
+
27
+ export { Switch };
@@ -0,0 +1,95 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '../../lib/utils';
4
+
5
+ const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
6
+ ({ className, ...props }, ref) => (
7
+ <div className="relative w-full overflow-auto">
8
+ <table
9
+ ref={ref}
10
+ className={cn('w-full caption-bottom text-[14px] leading-[20px] text-foreground', className)}
11
+ {...props}
12
+ />
13
+ </div>
14
+ )
15
+ );
16
+ Table.displayName = 'Table';
17
+
18
+ const TableHeader = React.forwardRef<
19
+ HTMLTableSectionElement,
20
+ React.HTMLAttributes<HTMLTableSectionElement>
21
+ >(({ className, ...props }, ref) => (
22
+ <thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
23
+ ));
24
+ TableHeader.displayName = 'TableHeader';
25
+
26
+ const TableBody = React.forwardRef<
27
+ HTMLTableSectionElement,
28
+ React.HTMLAttributes<HTMLTableSectionElement>
29
+ >(({ className, ...props }, ref) => (
30
+ <tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
31
+ ));
32
+ TableBody.displayName = 'TableBody';
33
+
34
+ const TableFooter = React.forwardRef<
35
+ HTMLTableSectionElement,
36
+ React.HTMLAttributes<HTMLTableSectionElement>
37
+ >(({ className, ...props }, ref) => (
38
+ <tfoot
39
+ ref={ref}
40
+ className={cn('border-t bg-secondary font-medium [&>tr]:last:border-b-0', className)}
41
+ {...props}
42
+ />
43
+ ));
44
+ TableFooter.displayName = 'TableFooter';
45
+
46
+ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
47
+ ({ className, ...props }, ref) => (
48
+ <tr
49
+ ref={ref}
50
+ className={cn(
51
+ 'state-layer border-b transition-colors data-[state=selected]:bg-selected',
52
+ className
53
+ )}
54
+ {...props}
55
+ />
56
+ )
57
+ );
58
+ TableRow.displayName = 'TableRow';
59
+
60
+ const TableHead = React.forwardRef<
61
+ HTMLTableCellElement,
62
+ React.ThHTMLAttributes<HTMLTableCellElement>
63
+ >(({ className, ...props }, ref) => (
64
+ <th
65
+ ref={ref}
66
+ className={cn(
67
+ 'h-[40px] px-[12px] text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
68
+ className
69
+ )}
70
+ {...props}
71
+ />
72
+ ));
73
+ TableHead.displayName = 'TableHead';
74
+
75
+ const TableCell = React.forwardRef<
76
+ HTMLTableCellElement,
77
+ React.TdHTMLAttributes<HTMLTableCellElement>
78
+ >(({ className, ...props }, ref) => (
79
+ <td
80
+ ref={ref}
81
+ className={cn('p-[12px] align-middle [&:has([role=checkbox])]:pr-0', className)}
82
+ {...props}
83
+ />
84
+ ));
85
+ TableCell.displayName = 'TableCell';
86
+
87
+ const TableCaption = React.forwardRef<
88
+ HTMLTableCaptionElement,
89
+ React.HTMLAttributes<HTMLTableCaptionElement>
90
+ >(({ className, ...props }, ref) => (
91
+ <caption ref={ref} className={cn('mt-[16px] text-[14px] leading-[20px] text-muted-foreground', className)} {...props} />
92
+ ));
93
+ TableCaption.displayName = 'TableCaption';
94
+
95
+ export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
@@ -0,0 +1,119 @@
1
+ import * as React from 'react';
2
+ import * as TabsPrimitive from '@radix-ui/react-tabs';
3
+
4
+ import { cn } from '../../lib/utils';
5
+
6
+ /* Tabs 两种形态,用来表达层级:
7
+ * line —— 下划线,分量重,给「页面级」主标签(概览 / 报表 / 设置)
8
+ * segmented —— 灰底胶囊,分量轻,给「区块内」切换视图(日 / 周 / 月)
9
+ * 嵌套时外层用 line、内层用 segmented,层级一眼可辨。
10
+ *
11
+ * 尺寸接三档刻度:sm 24(字12)· default 32(字13)· lg 40(字14)。
12
+ * variant / size 写在 <Tabs> 上一次,List 与 Trigger 通过 context 取。 */
13
+ type TabsVariant = 'segmented' | 'line';
14
+ type TabsSize = 'sm' | 'default' | 'lg';
15
+
16
+ const TabsContext = React.createContext<{ variant: TabsVariant; size: TabsSize }>({
17
+ variant: 'segmented',
18
+ size: 'default'
19
+ });
20
+
21
+ const LIST_SIZE: Record<TabsVariant, Record<TabsSize, string>> = {
22
+ segmented: {
23
+ sm: 'h-[24px] p-[2px]',
24
+ default: 'h-[32px] p-[4px]',
25
+ lg: 'h-[40px] p-[4px]'
26
+ },
27
+ line: {
28
+ sm: 'h-[24px] gap-[12px]',
29
+ default: 'h-[32px] gap-[16px]',
30
+ lg: 'h-[40px] gap-[20px]'
31
+ }
32
+ };
33
+
34
+ const TRIGGER_TEXT: Record<TabsSize, string> = {
35
+ sm: 'text-[12px] leading-[16px]',
36
+ default: 'text-[13px] leading-[20px]',
37
+ lg: 'text-[14px] leading-[20px]'
38
+ };
39
+
40
+ const Tabs = React.forwardRef<
41
+ React.ElementRef<typeof TabsPrimitive.Root>,
42
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root> & {
43
+ variant?: TabsVariant;
44
+ size?: TabsSize;
45
+ }
46
+ >(({ variant = 'segmented', size = 'default', ...props }, ref) => {
47
+ const ctx = React.useMemo(() => ({ variant, size }), [variant, size]);
48
+ return (
49
+ <TabsContext.Provider value={ctx}>
50
+ <TabsPrimitive.Root ref={ref} {...props} />
51
+ </TabsContext.Provider>
52
+ );
53
+ });
54
+ Tabs.displayName = TabsPrimitive.Root.displayName;
55
+
56
+ const TabsList = React.forwardRef<
57
+ React.ElementRef<typeof TabsPrimitive.List>,
58
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
59
+ >(({ className, ...props }, ref) => {
60
+ const { variant, size } = React.useContext(TabsContext);
61
+ return (
62
+ <TabsPrimitive.List
63
+ ref={ref}
64
+ className={cn(
65
+ 'inline-flex items-center text-muted-foreground',
66
+ variant === 'segmented' && 'justify-center rounded-[var(--pt-radius)] bg-muted',
67
+ /* line:整宽 + 底部分隔线,标签左对齐 */
68
+ variant === 'line' && 'w-full justify-start border-b border-border',
69
+ LIST_SIZE[variant][size],
70
+ className
71
+ )}
72
+ {...props}
73
+ />
74
+ );
75
+ });
76
+ TabsList.displayName = TabsPrimitive.List.displayName;
77
+
78
+ const TabsTrigger = React.forwardRef<
79
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
80
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
81
+ >(({ className, ...props }, ref) => {
82
+ const { variant, size } = React.useContext(TabsContext);
83
+ return (
84
+ <TabsPrimitive.Trigger
85
+ ref={ref}
86
+ className={cn(
87
+ 'inline-flex items-center justify-center whitespace-nowrap font-medium ring-offset-background transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
88
+ TRIGGER_TEXT[size],
89
+ /* segmented:选中项白底浮起。按设计铁律不加阴影——白底与灰底容器的
90
+ 明度差已足够表达选中,阴影在暗色模式下还会失效。 */
91
+ variant === 'segmented' &&
92
+ 'h-full rounded-[var(--pt-radius-sm)] px-[12px] data-[state=active]:bg-background data-[state=active]:text-foreground',
93
+ /* line:选中项底部 2px 实线,-mb-px 让它压住 List 的分隔线 */
94
+ variant === 'line' &&
95
+ '-mb-px h-full border-b-2 border-transparent px-[2px] data-[state=active]:border-foreground data-[state=active]:text-foreground',
96
+ className
97
+ )}
98
+ {...props}
99
+ />
100
+ );
101
+ });
102
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
103
+
104
+ const TabsContent = React.forwardRef<
105
+ React.ElementRef<typeof TabsPrimitive.Content>,
106
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
107
+ >(({ className, ...props }, ref) => (
108
+ <TabsPrimitive.Content
109
+ ref={ref}
110
+ className={cn(
111
+ 'mt-[8px] ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
112
+ className
113
+ )}
114
+ {...props}
115
+ />
116
+ ));
117
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
118
+
119
+ export { Tabs, TabsList, TabsTrigger, TabsContent };
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from '../../lib/utils';
4
+
5
+ export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
6
+
7
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
8
+ ({ className, ...props }, ref) => {
9
+ return (
10
+ <textarea
11
+ className={cn(
12
+ 'flex min-h-[60px] w-full rounded-[var(--pt-radius-md)] border border-input bg-transparent px-[12px] py-[8px] text-[14px] leading-[20px] placeholder:text-muted-foreground transition-colors enabled:hover:border-neutral focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 aria-[invalid=true]:border-destructive aria-[invalid=true]:enabled:hover:border-destructive aria-[invalid=true]:focus-visible:ring-destructive/30',
13
+ className
14
+ )}
15
+ ref={ref}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+ );
21
+ Textarea.displayName = 'Textarea';
22
+
23
+ export { Textarea };
@@ -0,0 +1,55 @@
1
+ import * as React from 'react';
2
+ import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
3
+ import { type VariantProps } from 'class-variance-authority';
4
+
5
+ import { cn } from '../../lib/utils';
6
+ import { toggleVariants } from './toggle';
7
+
8
+ const ToggleGroupContext = React.createContext<VariantProps<typeof toggleVariants>>({
9
+ size: 'default',
10
+ variant: 'default'
11
+ });
12
+
13
+ const ToggleGroup = React.forwardRef<
14
+ React.ElementRef<typeof ToggleGroupPrimitive.Root>,
15
+ React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
16
+ VariantProps<typeof toggleVariants>
17
+ >(({ className, variant, size, children, ...props }, ref) => (
18
+ <ToggleGroupPrimitive.Root
19
+ ref={ref}
20
+ className={cn('flex items-center justify-center gap-[4px]', className)}
21
+ {...props}
22
+ >
23
+ <ToggleGroupContext.Provider value={{ variant, size }}>
24
+ {children}
25
+ </ToggleGroupContext.Provider>
26
+ </ToggleGroupPrimitive.Root>
27
+ ));
28
+ ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
29
+
30
+ const ToggleGroupItem = React.forwardRef<
31
+ React.ElementRef<typeof ToggleGroupPrimitive.Item>,
32
+ React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
33
+ VariantProps<typeof toggleVariants>
34
+ >(({ className, children, variant, size, ...props }, ref) => {
35
+ const context = React.useContext(ToggleGroupContext);
36
+
37
+ return (
38
+ <ToggleGroupPrimitive.Item
39
+ ref={ref}
40
+ className={cn(
41
+ toggleVariants({
42
+ variant: context.variant || variant,
43
+ size: context.size || size
44
+ }),
45
+ className
46
+ )}
47
+ {...props}
48
+ >
49
+ {children}
50
+ </ToggleGroupPrimitive.Item>
51
+ );
52
+ });
53
+ ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
54
+
55
+ export { ToggleGroup, ToggleGroupItem };
@@ -0,0 +1,40 @@
1
+ import * as React from 'react';
2
+ import * as TogglePrimitive from '@radix-ui/react-toggle';
3
+ import { cva, type VariantProps } from 'class-variance-authority';
4
+
5
+ import { cn } from '../../lib/utils';
6
+
7
+ const toggleVariants = cva(
8
+ 'state-layer inline-flex items-center justify-center gap-[8px] rounded-[var(--pt-radius-md)] text-[14px] leading-[20px] font-medium text-foreground transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=on]:bg-selected data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-[16px] [&_svg]:shrink-0',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-transparent',
13
+ outline: 'border border-input bg-transparent [--pt-state-layer-border:1px]'
14
+ },
15
+ size: {
16
+ default: 'h-[32px] min-w-[36px] px-[8px] text-[13px] leading-[20px]',
17
+ sm: 'h-[24px] min-w-[32px] rounded-[var(--pt-radius-sm)] px-[6px] text-[12px] leading-[16px]',
18
+ lg: 'h-[40px] min-w-[40px] px-[10px] text-[14px] leading-[20px] [&_svg]:size-[20px]'
19
+ }
20
+ },
21
+ defaultVariants: {
22
+ variant: 'default',
23
+ size: 'default'
24
+ }
25
+ }
26
+ );
27
+
28
+ const Toggle = React.forwardRef<
29
+ React.ElementRef<typeof TogglePrimitive.Root>,
30
+ React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>
31
+ >(({ className, variant, size, ...props }, ref) => (
32
+ <TogglePrimitive.Root
33
+ ref={ref}
34
+ className={cn(toggleVariants({ variant, size, className }))}
35
+ {...props}
36
+ />
37
+ ));
38
+ Toggle.displayName = TogglePrimitive.Root.displayName;
39
+
40
+ export { Toggle, toggleVariants };
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip';
3
+
4
+ import { cn } from '../../lib/utils';
5
+
6
+ const TooltipProvider = TooltipPrimitive.Provider;
7
+
8
+ const Tooltip = TooltipPrimitive.Root;
9
+
10
+ const TooltipTrigger = TooltipPrimitive.Trigger;
11
+
12
+ const TooltipContent = React.forwardRef<
13
+ React.ElementRef<typeof TooltipPrimitive.Content>,
14
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
15
+ >(({ className, sideOffset = 4, ...props }, ref) => (
16
+ <TooltipPrimitive.Portal>
17
+ <TooltipPrimitive.Content
18
+ ref={ref}
19
+ sideOffset={sideOffset}
20
+ className={cn(
21
+ 'z-[var(--pt-z-tooltip,50)] overflow-hidden rounded-[var(--pt-radius-md)] bg-primary px-[12px] py-[6px] text-[13px] leading-[20px] text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
22
+ className
23
+ )}
24
+ {...props}
25
+ />
26
+ </TooltipPrimitive.Portal>
27
+ ));
28
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
29
+
30
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+
3
+ const MOBILE_BREAKPOINT = 768;
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);
7
+
8
+ React.useEffect(() => {
9
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
10
+ const onChange = () => {
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
12
+ };
13
+ mql.addEventListener('change', onChange);
14
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
15
+ return () => mql.removeEventListener('change', onChange);
16
+ }, []);
17
+
18
+ return !!isMobile;
19
+ }