@luxfi/ui 6.1.3 → 7.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 (241) hide show
  1. package/dist/accordion.cjs +213 -0
  2. package/dist/accordion.d.cts +74 -0
  3. package/dist/accordion.d.ts +74 -0
  4. package/dist/accordion.js +186 -0
  5. package/dist/alert.cjs +430 -0
  6. package/dist/alert.d.cts +30 -0
  7. package/dist/alert.d.ts +30 -0
  8. package/dist/alert.js +408 -0
  9. package/dist/avatar.cjs +149 -0
  10. package/dist/avatar.d.cts +25 -0
  11. package/dist/avatar.d.ts +25 -0
  12. package/dist/avatar.js +125 -0
  13. package/dist/badge.cjs +452 -0
  14. package/dist/badge.d.cts +19 -0
  15. package/dist/badge.d.ts +19 -0
  16. package/dist/badge.js +431 -0
  17. package/dist/bank.cjs +272 -0
  18. package/dist/bank.d.cts +76 -0
  19. package/dist/bank.d.ts +76 -0
  20. package/dist/bank.js +258 -0
  21. package/dist/button.cjs +578 -0
  22. package/dist/button.d.cts +55 -0
  23. package/dist/button.d.ts +55 -0
  24. package/dist/button.js +553 -0
  25. package/dist/checkbox.cjs +265 -0
  26. package/dist/checkbox.d.cts +31 -0
  27. package/dist/checkbox.d.ts +31 -0
  28. package/dist/checkbox.js +241 -0
  29. package/dist/close-button.cjs +92 -0
  30. package/dist/close-button.d.cts +9 -0
  31. package/dist/close-button.d.ts +9 -0
  32. package/dist/close-button.js +70 -0
  33. package/dist/collapsible.cjs +555 -0
  34. package/dist/collapsible.d.cts +23 -0
  35. package/dist/collapsible.d.ts +23 -0
  36. package/dist/collapsible.js +532 -0
  37. package/dist/color-mode.cjs +96 -0
  38. package/dist/color-mode.d.cts +25 -0
  39. package/dist/color-mode.d.ts +25 -0
  40. package/dist/color-mode.js +72 -0
  41. package/dist/dialog.cjs +295 -0
  42. package/dist/dialog.d.cts +65 -0
  43. package/dist/dialog.d.ts +65 -0
  44. package/dist/dialog.js +263 -0
  45. package/dist/drawer.cjs +231 -0
  46. package/dist/drawer.d.cts +36 -0
  47. package/dist/drawer.d.ts +36 -0
  48. package/dist/drawer.js +199 -0
  49. package/dist/empty-state.cjs +93 -0
  50. package/dist/empty-state.d.cts +13 -0
  51. package/dist/empty-state.d.ts +13 -0
  52. package/dist/empty-state.js +71 -0
  53. package/dist/expiration-selector.cjs +138 -0
  54. package/dist/expiration-selector.d.cts +16 -0
  55. package/dist/expiration-selector.d.ts +16 -0
  56. package/dist/expiration-selector.js +116 -0
  57. package/dist/field.cjs +183 -0
  58. package/dist/field.d.cts +24 -0
  59. package/dist/field.d.ts +24 -0
  60. package/dist/field.js +160 -0
  61. package/dist/greeks-display.cjs +114 -0
  62. package/dist/greeks-display.d.cts +14 -0
  63. package/dist/greeks-display.d.ts +14 -0
  64. package/dist/greeks-display.js +92 -0
  65. package/dist/heading.cjs +41 -0
  66. package/dist/heading.d.cts +10 -0
  67. package/dist/heading.d.ts +10 -0
  68. package/dist/heading.js +35 -0
  69. package/dist/icon-button.cjs +367 -0
  70. package/dist/icon-button.d.cts +33 -0
  71. package/dist/icon-button.d.ts +33 -0
  72. package/dist/icon-button.js +345 -0
  73. package/dist/image.cjs +425 -0
  74. package/dist/image.d.cts +57 -0
  75. package/dist/image.d.ts +57 -0
  76. package/dist/image.js +404 -0
  77. package/dist/index.cjs +7220 -0
  78. package/dist/index.d.cts +55 -0
  79. package/dist/index.d.ts +55 -0
  80. package/dist/index.js +7043 -0
  81. package/dist/input-group.cjs +155 -0
  82. package/dist/input-group.d.cts +17 -0
  83. package/dist/input-group.d.ts +17 -0
  84. package/dist/input-group.js +133 -0
  85. package/dist/input.cjs +65 -0
  86. package/dist/input.d.cts +11 -0
  87. package/dist/input.d.ts +11 -0
  88. package/dist/input.js +59 -0
  89. package/dist/link.cjs +483 -0
  90. package/dist/link.d.cts +37 -0
  91. package/dist/link.d.ts +37 -0
  92. package/dist/link.js +459 -0
  93. package/dist/menu.cjs +305 -0
  94. package/dist/menu.d.cts +84 -0
  95. package/dist/menu.d.ts +84 -0
  96. package/dist/menu.js +269 -0
  97. package/dist/option-chain.cjs +221 -0
  98. package/dist/option-chain.d.cts +37 -0
  99. package/dist/option-chain.d.ts +37 -0
  100. package/dist/option-chain.js +199 -0
  101. package/dist/option-position.cjs +164 -0
  102. package/dist/option-position.d.cts +29 -0
  103. package/dist/option-position.d.ts +29 -0
  104. package/dist/option-position.js +142 -0
  105. package/dist/pin-input.cjs +182 -0
  106. package/dist/pin-input.d.cts +25 -0
  107. package/dist/pin-input.d.ts +25 -0
  108. package/dist/pin-input.js +160 -0
  109. package/dist/pnl-diagram.cjs +453 -0
  110. package/dist/pnl-diagram.d.cts +11 -0
  111. package/dist/pnl-diagram.d.ts +11 -0
  112. package/dist/pnl-diagram.js +431 -0
  113. package/dist/popover.cjs +342 -0
  114. package/dist/popover.d.cts +73 -0
  115. package/dist/popover.d.ts +73 -0
  116. package/dist/popover.js +310 -0
  117. package/dist/progress-circle.cjs +152 -0
  118. package/dist/progress-circle.d.cts +47 -0
  119. package/dist/progress-circle.d.ts +47 -0
  120. package/dist/progress-circle.js +128 -0
  121. package/dist/progress.cjs +117 -0
  122. package/dist/progress.d.cts +28 -0
  123. package/dist/progress.d.ts +28 -0
  124. package/dist/progress.js +94 -0
  125. package/dist/provider.cjs +62 -0
  126. package/dist/provider.d.cts +7 -0
  127. package/dist/provider.d.ts +7 -0
  128. package/dist/provider.js +40 -0
  129. package/dist/radio.cjs +177 -0
  130. package/dist/radio.d.cts +53 -0
  131. package/dist/radio.d.ts +53 -0
  132. package/dist/radio.js +153 -0
  133. package/dist/rating.cjs +80 -0
  134. package/dist/rating.d.cts +14 -0
  135. package/dist/rating.d.ts +14 -0
  136. package/dist/rating.js +58 -0
  137. package/dist/select.cjs +644 -0
  138. package/dist/select.d.cts +130 -0
  139. package/dist/select.d.ts +130 -0
  140. package/dist/select.js +610 -0
  141. package/dist/separator.cjs +57 -0
  142. package/dist/separator.d.cts +13 -0
  143. package/dist/separator.d.ts +13 -0
  144. package/dist/separator.js +51 -0
  145. package/dist/skeleton.cjs +223 -0
  146. package/dist/skeleton.d.cts +60 -0
  147. package/dist/skeleton.d.ts +60 -0
  148. package/dist/skeleton.js +199 -0
  149. package/dist/slider.cjs +138 -0
  150. package/dist/slider.d.cts +24 -0
  151. package/dist/slider.d.ts +24 -0
  152. package/dist/slider.js +115 -0
  153. package/dist/strategy-builder.cjs +373 -0
  154. package/dist/strategy-builder.d.cts +26 -0
  155. package/dist/strategy-builder.d.ts +26 -0
  156. package/dist/strategy-builder.js +351 -0
  157. package/dist/switch.cjs +163 -0
  158. package/dist/switch.d.cts +27 -0
  159. package/dist/switch.d.ts +27 -0
  160. package/dist/switch.js +140 -0
  161. package/dist/table.cjs +897 -0
  162. package/dist/table.d.cts +107 -0
  163. package/dist/table.d.ts +107 -0
  164. package/dist/table.js +866 -0
  165. package/dist/tabs.cjs +240 -0
  166. package/dist/tabs.d.cts +78 -0
  167. package/dist/tabs.d.ts +78 -0
  168. package/dist/tabs.js +213 -0
  169. package/dist/tag.cjs +529 -0
  170. package/dist/tag.d.cts +21 -0
  171. package/dist/tag.d.ts +21 -0
  172. package/dist/tag.js +507 -0
  173. package/dist/textarea.cjs +65 -0
  174. package/dist/textarea.d.cts +11 -0
  175. package/dist/textarea.d.ts +11 -0
  176. package/dist/textarea.js +59 -0
  177. package/dist/toaster.cjs +99 -0
  178. package/dist/toaster.d.cts +29 -0
  179. package/dist/toaster.d.ts +29 -0
  180. package/dist/toaster.js +96 -0
  181. package/dist/tooltip.cjs +172 -0
  182. package/dist/tooltip.d.cts +38 -0
  183. package/dist/tooltip.d.ts +38 -0
  184. package/dist/tooltip.js +150 -0
  185. package/dist/utils.cjs +11 -0
  186. package/dist/utils.d.cts +5 -0
  187. package/dist/utils.d.ts +5 -0
  188. package/dist/utils.js +9 -0
  189. package/package.json +284 -89
  190. package/src/accordion.tsx +285 -0
  191. package/src/alert.tsx +221 -0
  192. package/src/avatar.tsx +174 -0
  193. package/src/badge.tsx +147 -0
  194. package/src/bank.tsx +325 -0
  195. package/src/button.tsx +477 -0
  196. package/src/checkbox.tsx +307 -0
  197. package/src/close-button.tsx +77 -0
  198. package/src/collapsible.tsx +126 -0
  199. package/src/color-mode.tsx +125 -0
  200. package/src/dialog.tsx +346 -0
  201. package/src/drawer.tsx +186 -0
  202. package/src/empty-state.tsx +97 -0
  203. package/src/expiration-selector.tsx +163 -0
  204. package/src/field.tsx +202 -0
  205. package/src/greeks-display.tsx +138 -0
  206. package/src/heading.tsx +44 -0
  207. package/src/icon-button.tsx +217 -0
  208. package/src/image.tsx +280 -0
  209. package/src/index.ts +267 -138
  210. package/src/input-group.tsx +159 -0
  211. package/src/input.tsx +60 -0
  212. package/src/link.tsx +326 -0
  213. package/src/menu.tsx +471 -0
  214. package/src/option-chain.tsx +261 -0
  215. package/src/option-position.tsx +200 -0
  216. package/src/pin-input.tsx +187 -0
  217. package/src/pnl-diagram.tsx +458 -0
  218. package/src/popover.tsx +386 -0
  219. package/src/progress-circle.tsx +180 -0
  220. package/src/progress.tsx +109 -0
  221. package/src/provider.tsx +12 -0
  222. package/src/radio.tsx +175 -0
  223. package/src/rating.tsx +79 -0
  224. package/src/select.tsx +696 -0
  225. package/src/separator.tsx +59 -0
  226. package/src/skeleton.tsx +254 -0
  227. package/src/slider.tsx +152 -0
  228. package/src/strategy-builder.tsx +466 -0
  229. package/src/switch.tsx +158 -0
  230. package/src/table.tsx +621 -0
  231. package/src/tabs.tsx +354 -0
  232. package/src/tag.tsx +159 -0
  233. package/src/textarea.tsx +60 -0
  234. package/src/toaster.tsx +117 -0
  235. package/src/tokens.css +438 -0
  236. package/src/tooltip.tsx +191 -0
  237. package/src/utils/cn.ts +7 -0
  238. package/src/utils.ts +6 -0
  239. package/tokens.css +438 -0
  240. package/LICENSE +0 -122
  241. package/README.md +0 -109
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+
3
+ import { cn } from './utils';
4
+
5
+ type Orientation = 'horizontal' | 'vertical';
6
+ type Variant = 'solid' | 'dashed' | 'dotted';
7
+ type Size = 'xs' | 'sm' | 'md' | 'lg';
8
+
9
+ export interface SeparatorProps extends React.HTMLAttributes<HTMLHRElement> {
10
+ readonly orientation?: Orientation;
11
+ readonly variant?: Variant;
12
+ readonly size?: Size;
13
+ }
14
+
15
+ const VARIANT_CLASSES: Record<Variant, string> = {
16
+ solid: 'border-solid',
17
+ dashed: 'border-dashed',
18
+ dotted: 'border-dotted',
19
+ };
20
+
21
+ const HORIZONTAL_SIZE_CLASSES: Record<Size, string> = {
22
+ xs: 'border-t-[0.5px]',
23
+ sm: 'border-t',
24
+ md: 'border-t-2',
25
+ lg: 'border-t-[3px]',
26
+ };
27
+
28
+ const VERTICAL_SIZE_CLASSES: Record<Size, string> = {
29
+ xs: 'border-l-[0.5px]',
30
+ sm: 'border-l',
31
+ md: 'border-l-2',
32
+ lg: 'border-l-[3px]',
33
+ };
34
+
35
+ export const Separator = React.forwardRef<HTMLHRElement, SeparatorProps>(
36
+ function Separator(
37
+ { orientation = 'horizontal', variant = 'solid', size = 'sm', className, ...rest },
38
+ ref,
39
+ ) {
40
+ const isVertical = orientation === 'vertical';
41
+
42
+ return (
43
+ <hr
44
+ ref={ ref }
45
+ role="separator"
46
+ aria-orientation={ orientation }
47
+ className={ cn(
48
+ 'border-[var(--color-border-divider)]',
49
+ 'border-0',
50
+ VARIANT_CLASSES[variant],
51
+ isVertical ? VERTICAL_SIZE_CLASSES[size] : HORIZONTAL_SIZE_CLASSES[size],
52
+ isVertical ? 'self-stretch h-auto w-0' : 'w-full',
53
+ className,
54
+ ) }
55
+ { ...rest }
56
+ />
57
+ );
58
+ },
59
+ );
@@ -0,0 +1,254 @@
1
+ import * as React from 'react';
2
+
3
+ import { cn } from './utils';
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Skeleton
7
+ // ---------------------------------------------------------------------------
8
+
9
+ export interface SkeletonProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'color'> {
10
+ readonly loading: boolean | undefined;
11
+
12
+ /** When true, the Skeleton wraps its single child element instead of adding a wrapper div. */
13
+ readonly asChild?: boolean;
14
+
15
+ // Legacy Chakra style-prop shims — converted to inline style.
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ readonly w?: any;
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ readonly h?: any;
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ readonly minW?: any;
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ readonly maxW?: any;
24
+ readonly display?: string;
25
+ readonly flexGrow?: number;
26
+ readonly flexShrink?: number;
27
+ readonly flexBasis?: string;
28
+ readonly fontWeight?: number | string;
29
+ readonly textStyle?: string;
30
+ readonly borderRadius?: string;
31
+ readonly alignSelf?: string;
32
+ readonly alignItems?: string;
33
+ readonly justifyContent?: string;
34
+ readonly color?: string;
35
+ readonly mt?: number | string;
36
+ readonly mb?: number | string;
37
+ readonly ml?: number | string;
38
+ readonly mr?: number | string;
39
+ readonly height?: string;
40
+ readonly overflow?: string;
41
+ readonly whiteSpace?: string;
42
+ readonly textOverflow?: string;
43
+ readonly textTransform?: string;
44
+ readonly gap?: number | string;
45
+ readonly gridTemplateColumns?: string;
46
+ readonly minWidth?: string;
47
+ readonly boxSize?: number | string;
48
+ readonly py?: number | string;
49
+ readonly px?: number | string;
50
+ readonly p?: number | string;
51
+ readonly hideBelow?: string;
52
+ readonly as?: React.ElementType;
53
+ readonly fontSize?: string;
54
+ readonly flexWrap?: React.CSSProperties['flexWrap'];
55
+ readonly wordBreak?: React.CSSProperties['wordBreak'];
56
+ readonly lineHeight?: string;
57
+ readonly marginRight?: string;
58
+ readonly position?: React.CSSProperties['position'];
59
+ readonly background?: string;
60
+ }
61
+
62
+ // Style-prop shim names that must NOT leak into the DOM.
63
+ const SHIM_PROPS = [
64
+ 'w', 'h', 'minW', 'maxW', 'display', 'flexGrow', 'flexShrink', 'flexBasis',
65
+ 'fontWeight', 'textStyle', 'borderRadius', 'alignSelf', 'alignItems',
66
+ 'justifyContent', 'color', 'mt', 'mb', 'ml', 'mr', 'height', 'overflow',
67
+ 'whiteSpace', 'textOverflow', 'textTransform', 'gap', 'gridTemplateColumns',
68
+ 'minWidth', 'boxSize', 'py', 'px', 'p', 'hideBelow', 'fontSize', 'flexWrap',
69
+ 'wordBreak', 'lineHeight', 'marginRight', 'position', 'background',
70
+ ] as const;
71
+
72
+ const S = 4; // Chakra spacing scale
73
+
74
+ /** Resolve a dimension value (number → px via spacing scale, responsive object → first value). */
75
+ function dim(v: unknown): string | undefined {
76
+ if (v == null) return undefined;
77
+ if (typeof v === 'number') return `${ v * S }px`;
78
+ if (typeof v === 'string') return v;
79
+ if (typeof v === 'object') {
80
+ const o = v as Record<string, string>;
81
+ return o.base ?? o.lg ?? o.xl ?? Object.values(o)[0];
82
+ }
83
+ return undefined;
84
+ }
85
+
86
+ /** Convert a spacing number|string via the 4px scale. */
87
+ function sp(v: number | string | undefined): string | undefined {
88
+ if (v === undefined) return undefined;
89
+ return typeof v === 'number' ? `${ v * S }px` : v;
90
+ }
91
+
92
+ /** Build a CSSProperties object from legacy Chakra style-prop shims. */
93
+ function shimToStyle(props: SkeletonProps): React.CSSProperties {
94
+ const s: React.CSSProperties = {};
95
+ if (props.w !== undefined) s.width = dim(props.w);
96
+ if (props.h !== undefined) s.height = dim(props.h);
97
+ if (props.minW !== undefined) s.minWidth = dim(props.minW);
98
+ if (props.maxW !== undefined) s.maxWidth = dim(props.maxW);
99
+ if (props.display !== undefined) s.display = props.display;
100
+ if (props.flexGrow !== undefined) s.flexGrow = props.flexGrow;
101
+ if (props.flexShrink !== undefined) s.flexShrink = props.flexShrink;
102
+ if (props.flexBasis !== undefined) s.flexBasis = props.flexBasis;
103
+ if (props.fontWeight !== undefined) s.fontWeight = props.fontWeight;
104
+ if (props.borderRadius !== undefined) s.borderRadius = props.borderRadius;
105
+ if (props.alignSelf !== undefined) s.alignSelf = props.alignSelf;
106
+ if (props.alignItems !== undefined) s.alignItems = props.alignItems;
107
+ if (props.justifyContent !== undefined) s.justifyContent = props.justifyContent;
108
+ if (props.color !== undefined) s.color = props.color;
109
+ if (props.mt !== undefined) s.marginTop = sp(props.mt);
110
+ if (props.mb !== undefined) s.marginBottom = sp(props.mb);
111
+ if (props.ml !== undefined) s.marginLeft = sp(props.ml);
112
+ if (props.mr !== undefined) s.marginRight = sp(props.mr);
113
+ if (props.height !== undefined) s.height = props.height;
114
+ if (props.overflow !== undefined) s.overflow = props.overflow;
115
+ if (props.whiteSpace !== undefined) s.whiteSpace = props.whiteSpace;
116
+ if (props.textOverflow !== undefined) s.textOverflow = props.textOverflow;
117
+ if (props.textTransform !== undefined) s.textTransform = props.textTransform;
118
+ if (props.gap !== undefined) s.gap = sp(props.gap);
119
+ if (props.gridTemplateColumns !== undefined) s.gridTemplateColumns = props.gridTemplateColumns;
120
+ if (props.minWidth !== undefined) s.minWidth = props.minWidth;
121
+ if (props.boxSize !== undefined) {
122
+ const v = typeof props.boxSize === 'number' ? `${ props.boxSize * S }px` : props.boxSize;
123
+ s.width = v; s.height = v;
124
+ }
125
+ if (props.py !== undefined) { const v = sp(props.py); s.paddingTop = v; s.paddingBottom = v; }
126
+ if (props.px !== undefined) { const v = sp(props.px); s.paddingLeft = v; s.paddingRight = v; }
127
+ if (props.p !== undefined) s.padding = sp(props.p);
128
+ if (props.fontSize !== undefined) s.fontSize = props.fontSize;
129
+ if (props.flexWrap !== undefined) s.flexWrap = props.flexWrap;
130
+ if (props.wordBreak !== undefined) s.wordBreak = props.wordBreak;
131
+ if (props.lineHeight !== undefined) s.lineHeight = props.lineHeight;
132
+ if (props.marginRight !== undefined) s.marginRight = props.marginRight;
133
+ if (props.position !== undefined) s.position = props.position;
134
+ if (props.background !== undefined) s.background = props.background;
135
+ return s;
136
+ }
137
+
138
+ /** Strip shim keys from props so they don't leak into the DOM. */
139
+ function stripShims<T extends Record<string, unknown>>(props: T): Omit<T, (typeof SHIM_PROPS)[number]> {
140
+ const out = { ...props };
141
+ for (const k of SHIM_PROPS) delete out[k];
142
+ return out as Omit<T, (typeof SHIM_PROPS)[number]>;
143
+ }
144
+
145
+ const SKELETON_CLASSES = [
146
+ 'animate-skeleton-shimmer rounded-sm',
147
+ 'bg-[linear-gradient(90deg,var(--color-skeleton-start)_0%,var(--color-skeleton-end)_50%,var(--color-skeleton-start)_100%)]',
148
+ 'bg-[length:200%_100%]',
149
+ ].join(' ');
150
+
151
+ const HIDE_BELOW_MAP: Record<string, string> = { lg: 'lg:hidden', md: 'md:hidden', sm: 'sm:hidden' };
152
+
153
+ export const Skeleton = React.forwardRef<HTMLDivElement, SkeletonProps>(
154
+ function Skeleton(props, ref) {
155
+ const { loading = false, asChild, className, children, style: styleProp, as: Component = 'div', ...allRest } = props;
156
+
157
+ const shimStyle = shimToStyle(props);
158
+ const mergedStyle = Object.keys(shimStyle).length > 0 || styleProp ? { ...shimStyle, ...styleProp } : undefined;
159
+ const hideBelowClass = props.hideBelow ? HIDE_BELOW_MAP[props.hideBelow] : undefined;
160
+ const cls = hideBelowClass ? cn(className, hideBelowClass) : className;
161
+
162
+ // Strip shim props from what reaches the DOM.
163
+ const htmlRest = stripShims(allRest);
164
+
165
+ if (!loading) {
166
+ if (asChild && React.isValidElement(children)) return children;
167
+ return (
168
+ <Component ref={ ref } className={ cls } style={ mergedStyle } { ...htmlRest }>
169
+ { children }
170
+ </Component>
171
+ );
172
+ }
173
+
174
+ if (asChild && React.isValidElement(children)) {
175
+ return (
176
+ <Component
177
+ ref={ ref }
178
+ data-loading
179
+ className={ cn(SKELETON_CLASSES, 'text-transparent [&_*]:invisible', cls) }
180
+ style={ mergedStyle }
181
+ { ...htmlRest }
182
+ >
183
+ { children }
184
+ </Component>
185
+ );
186
+ }
187
+
188
+ return (
189
+ <Component
190
+ ref={ ref }
191
+ data-loading
192
+ className={ cn(SKELETON_CLASSES, children ? 'text-transparent [&_*]:invisible' : 'min-h-5', cls) }
193
+ style={ mergedStyle }
194
+ { ...htmlRest }
195
+ >
196
+ { children }
197
+ </Component>
198
+ );
199
+ },
200
+ );
201
+
202
+ // ---------------------------------------------------------------------------
203
+ // SkeletonCircle
204
+ // ---------------------------------------------------------------------------
205
+
206
+ export interface SkeletonCircleProps extends React.HTMLAttributes<HTMLDivElement> {
207
+ readonly size?: string | number;
208
+ readonly loading?: boolean;
209
+ }
210
+
211
+ export const SkeletonCircle = React.forwardRef<HTMLDivElement, SkeletonCircleProps>(
212
+ function SkeletonCircle(props, ref) {
213
+ const { size = 40, loading = true, className, ...rest } = props;
214
+
215
+ const dimension = typeof size === 'number' ? `${ size }px` : size;
216
+
217
+ return (
218
+ <Skeleton
219
+ ref={ ref }
220
+ loading={ loading }
221
+ className={ cn('rounded-full shrink-0', className) }
222
+ style={{ width: dimension, height: dimension, ...rest.style }}
223
+ { ...rest }
224
+ />
225
+ );
226
+ },
227
+ );
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // SkeletonText
231
+ // ---------------------------------------------------------------------------
232
+
233
+ export interface SkeletonTextProps extends React.HTMLAttributes<HTMLDivElement> {
234
+ readonly noOfLines?: number;
235
+ readonly loading?: boolean;
236
+ }
237
+
238
+ export const SkeletonText = React.forwardRef<HTMLDivElement, SkeletonTextProps>(
239
+ function SkeletonText(props, ref) {
240
+ const { noOfLines = 3, loading = true, className, ...rest } = props;
241
+
242
+ return (
243
+ <div ref={ ref } className={ cn('flex w-full flex-col gap-2', className) } { ...rest }>
244
+ { Array.from({ length: noOfLines }).map((_, index) => (
245
+ <Skeleton
246
+ key={ index }
247
+ loading={ loading }
248
+ className={ cn('h-4', index === noOfLines - 1 && 'max-w-[80%]') }
249
+ />
250
+ )) }
251
+ </div>
252
+ );
253
+ },
254
+ );
package/src/slider.tsx ADDED
@@ -0,0 +1,152 @@
1
+ import * as RadixSlider from '@radix-ui/react-slider';
2
+ import * as React from 'react';
3
+
4
+ import { cn } from './utils';
5
+
6
+ export interface SliderProps {
7
+ readonly marks?: Array<number | { readonly value: number; readonly label: React.ReactNode }>;
8
+ readonly label?: React.ReactNode;
9
+ readonly showValue?: boolean;
10
+ readonly value?: Array<number>;
11
+ readonly defaultValue?: Array<number>;
12
+ readonly min?: number;
13
+ readonly max?: number;
14
+ readonly step?: number;
15
+ readonly disabled?: boolean;
16
+ readonly orientation?: 'horizontal' | 'vertical';
17
+ readonly name?: string;
18
+ readonly onValueChange?: (value: Array<number>) => void;
19
+ readonly onValueCommit?: (value: Array<number>) => void;
20
+ readonly className?: string;
21
+ }
22
+
23
+ const THUMB_SIZE = 20;
24
+ const TRACK_HEIGHT = 6;
25
+
26
+ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
27
+ function Slider(props, ref) {
28
+ const {
29
+ marks: marksProp,
30
+ label,
31
+ showValue,
32
+ value,
33
+ defaultValue,
34
+ min = 0,
35
+ max = 100,
36
+ step,
37
+ disabled,
38
+ orientation,
39
+ name,
40
+ onValueChange,
41
+ onValueCommit,
42
+ className,
43
+ } = props;
44
+
45
+ const resolvedValue = defaultValue ?? value;
46
+
47
+ const marks = marksProp?.map((mark) => {
48
+ if (typeof mark === 'number') return { value: mark, label: undefined };
49
+ return mark;
50
+ });
51
+
52
+ const hasMarkLabel = Boolean(marks?.some((mark) => mark.label));
53
+
54
+ return (
55
+ <div className={ cn('flex flex-col gap-1', className) }>
56
+ { label && !showValue && (
57
+ <label className="text-sm font-medium text-text-secondary">
58
+ { label }
59
+ </label>
60
+ ) }
61
+ { label && showValue && (
62
+ <div className="flex items-center justify-between">
63
+ <label className="text-sm font-medium text-text-secondary">
64
+ { label }
65
+ </label>
66
+ <span className="text-sm text-text-secondary">
67
+ { (value ?? defaultValue ?? [])?.join(', ') }
68
+ </span>
69
+ </div>
70
+ ) }
71
+
72
+ <RadixSlider.Root
73
+ ref={ ref }
74
+ className={ cn(
75
+ 'relative flex w-full touch-none select-none items-center',
76
+ hasMarkLabel && 'mb-6',
77
+ ) }
78
+ value={ value }
79
+ defaultValue={ defaultValue }
80
+ min={ min }
81
+ max={ max }
82
+ step={ step }
83
+ disabled={ disabled }
84
+ orientation={ orientation }
85
+ name={ name }
86
+ onValueChange={ onValueChange }
87
+ onValueCommit={ onValueCommit }
88
+ >
89
+ <RadixSlider.Track
90
+ className={ cn(
91
+ 'relative grow rounded-full bg-border-divider',
92
+ `h-[${ TRACK_HEIGHT }px]`,
93
+ ) }
94
+ >
95
+ <RadixSlider.Range className="absolute h-full rounded-full bg-link-primary"/>
96
+ </RadixSlider.Track>
97
+
98
+ { resolvedValue?.map((_, index) => (
99
+ <RadixSlider.Thumb
100
+ key={ index }
101
+ className={ cn(
102
+ `block h-[${ THUMB_SIZE }px] w-[${ THUMB_SIZE }px]`,
103
+ 'rounded-full border-2 border-link-primary bg-white',
104
+ 'shadow-sm transition-colors',
105
+ 'hover:border-link-primary-hover',
106
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-link-primary focus-visible:ring-offset-2',
107
+ 'disabled:pointer-events-none disabled:opacity-50',
108
+ ) }
109
+ />
110
+ )) }
111
+ </RadixSlider.Root>
112
+
113
+ <SliderMarks marks={ marks } min={ min } max={ max }/>
114
+ </div>
115
+ );
116
+ },
117
+ );
118
+
119
+ interface SliderMarksProps {
120
+ readonly marks?: Array<{ readonly value: number; readonly label?: React.ReactNode }>;
121
+ readonly min: number;
122
+ readonly max: number;
123
+ }
124
+
125
+ function SliderMarks(props: SliderMarksProps): React.ReactNode {
126
+ const { marks, min, max } = props;
127
+ if (!marks?.length) return null;
128
+
129
+ const range = max - min;
130
+
131
+ return (
132
+ <div className="relative w-full h-4">
133
+ { marks.map((mark, index) => {
134
+ const percent = ((mark.value - min) / range) * 100;
135
+ return (
136
+ <div
137
+ key={ index }
138
+ className="absolute flex flex-col items-center -translate-x-1/2"
139
+ style={{ left: `${ percent }%` }}
140
+ >
141
+ <div className="h-1.5 w-0.5 bg-border-divider"/>
142
+ { mark.label != null && (
143
+ <span className="mt-0.5 text-xs text-text-secondary whitespace-nowrap">
144
+ { mark.label }
145
+ </span>
146
+ ) }
147
+ </div>
148
+ );
149
+ }) }
150
+ </div>
151
+ );
152
+ }