@sehgaltech/psui 1.0.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.
package/dist/index.js ADDED
@@ -0,0 +1,365 @@
1
+ import React, { forwardRef, useState, useEffect, useRef, useId } from 'react';
2
+ import { clsx } from 'clsx';
3
+ import { twMerge } from 'tailwind-merge';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+ import * as LucideIcons from 'lucide-react';
6
+
7
+ // src/components/Button/Button.tsx
8
+ function cn(...inputs) {
9
+ return twMerge(clsx(inputs));
10
+ }
11
+ var sizeClasses = {
12
+ sm: "px-3 py-1.5 text-sm",
13
+ md: "px-4 py-2 text-base",
14
+ lg: "px-6 py-3 text-lg"
15
+ };
16
+ var variantClasses = {
17
+ solid: {
18
+ primary: "bg-primary text-primary-content hover:opacity-90",
19
+ secondary: "bg-secondary text-secondary-content hover:opacity-90",
20
+ success: "bg-success text-success-content hover:opacity-90",
21
+ danger: "bg-error text-error-content hover:opacity-90",
22
+ warning: "bg-warning text-warning-content hover:opacity-90",
23
+ info: "bg-info text-info-content hover:opacity-90",
24
+ ghost: "bg-base-200 text-base-content hover:bg-base-300"
25
+ },
26
+ outlined: {
27
+ primary: "border border-primary text-primary bg-transparent hover:bg-primary/10",
28
+ secondary: "border border-secondary text-secondary bg-transparent hover:bg-secondary/10",
29
+ success: "border border-success text-success bg-transparent hover:bg-success/10",
30
+ danger: "border border-error text-error bg-transparent hover:bg-error/10",
31
+ warning: "border border-warning text-warning bg-transparent hover:bg-warning/10",
32
+ info: "border border-info text-info bg-transparent hover:bg-info/10",
33
+ ghost: "border border-base-200 text-base-content bg-transparent hover:bg-base-200/10"
34
+ },
35
+ dashed: {
36
+ primary: "border border-dashed border-primary text-primary bg-transparent hover:bg-primary/10",
37
+ secondary: "border border-dashed border-secondary text-secondary bg-transparent hover:bg-secondary/10",
38
+ success: "border border-dashed border-success text-success bg-transparent hover:bg-success/10",
39
+ danger: "border border-dashed border-error text-error bg-transparent hover:bg-error/10",
40
+ warning: "border border-dashed border-warning text-warning bg-transparent hover:bg-warning/10",
41
+ info: "border border-dashed border-info text-info bg-transparent hover:bg-info/10",
42
+ ghost: "border border-dashed border-base-200 text-base-content bg-transparent hover:bg-base-200/10"
43
+ },
44
+ text: {
45
+ primary: "text-primary bg-transparent hover:bg-primary/10",
46
+ secondary: "text-secondary bg-transparent hover:bg-secondary/10",
47
+ success: "text-success bg-transparent hover:bg-success/10",
48
+ danger: "text-error bg-transparent hover:bg-error/10",
49
+ warning: "text-warning bg-transparent hover:bg-warning/10",
50
+ info: "text-info bg-transparent hover:bg-info/10",
51
+ ghost: "text-base-content bg-transparent hover:bg-base-200/10"
52
+ },
53
+ link: {
54
+ primary: "text-primary underline bg-transparent hover:text-primary/80",
55
+ secondary: "text-secondary underline bg-transparent hover:text-secondary/80",
56
+ success: "text-success underline bg-transparent hover:text-success/80",
57
+ danger: "text-error underline bg-transparent hover:text-error/80",
58
+ warning: "text-warning underline bg-transparent hover:text-warning/80",
59
+ info: "text-info underline bg-transparent hover:text-info/80",
60
+ ghost: "text-base-content underline bg-transparent hover:text-base-content/80"
61
+ }
62
+ };
63
+ var getButtonClasses = (variant, type) => {
64
+ return variantClasses[variant][type] || variantClasses.solid.primary;
65
+ };
66
+ var SuccessIcon = () => /* @__PURE__ */ jsx(
67
+ "svg",
68
+ {
69
+ className: "h-4 w-4",
70
+ xmlns: "http://www.w3.org/2000/svg",
71
+ fill: "none",
72
+ viewBox: "0 0 24 24",
73
+ "aria-hidden": "true",
74
+ children: /* @__PURE__ */ jsx(
75
+ "path",
76
+ {
77
+ stroke: "currentColor",
78
+ strokeLinecap: "round",
79
+ strokeLinejoin: "round",
80
+ strokeWidth: "2.5",
81
+ d: "M5 12.5l4.5 4.5L19 7.5"
82
+ }
83
+ )
84
+ }
85
+ );
86
+ var Button = forwardRef(
87
+ ({
88
+ variant = "solid",
89
+ type = "primary",
90
+ size = "md",
91
+ fullWidth = false,
92
+ loading = false,
93
+ loadingText,
94
+ success = false,
95
+ successText,
96
+ successIcon,
97
+ successDuration = 1500,
98
+ disabled = false,
99
+ startIcon,
100
+ endIcon,
101
+ color,
102
+ className = "",
103
+ children,
104
+ ...props
105
+ }, ref) => {
106
+ const [isShowingSuccess, setIsShowingSuccess] = useState(false);
107
+ useEffect(() => {
108
+ if (!success || loading) {
109
+ return;
110
+ }
111
+ setIsShowingSuccess(true);
112
+ const timer = window.setTimeout(() => {
113
+ setIsShowingSuccess(false);
114
+ }, successDuration);
115
+ return () => window.clearTimeout(timer);
116
+ }, [success, loading, successDuration]);
117
+ const baseClasses = "inline-flex items-center justify-center font-medium rounded-[var(--radius-selector)] transition-all duration-200 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed active:scale-95 gap-2";
118
+ const variantClass = getButtonClasses(variant, type);
119
+ const sizeClass = sizeClasses[size];
120
+ const widthClass = fullWidth ? "w-full" : "";
121
+ const loadingClass = loading ? "cursor-wait" : "";
122
+ const combinedClasses = cn(
123
+ baseClasses,
124
+ variantClass,
125
+ sizeClass,
126
+ widthClass,
127
+ loadingClass,
128
+ className
129
+ );
130
+ const customStyle = { ...props.style };
131
+ if (color) {
132
+ if (variant === "solid") {
133
+ customStyle.backgroundColor = color;
134
+ customStyle.borderColor = color;
135
+ } else if (variant === "outlined" || variant === "dashed") {
136
+ customStyle.borderColor = color;
137
+ customStyle.color = color;
138
+ } else if (variant === "text" || variant === "link") {
139
+ customStyle.color = color;
140
+ }
141
+ }
142
+ const content = loading ? loadingText ?? children : isShowingSuccess ? successText ?? children : children;
143
+ return /* @__PURE__ */ jsxs(
144
+ "button",
145
+ {
146
+ ref,
147
+ type: "button",
148
+ className: combinedClasses,
149
+ style: customStyle,
150
+ disabled: disabled || loading,
151
+ "aria-busy": loading || void 0,
152
+ ...props,
153
+ children: [
154
+ loading && /* @__PURE__ */ jsxs(
155
+ "svg",
156
+ {
157
+ className: "animate-spin -ml-1 h-4 w-4",
158
+ xmlns: "http://www.w3.org/2000/svg",
159
+ fill: "none",
160
+ viewBox: "0 0 24 24",
161
+ "aria-hidden": "true",
162
+ children: [
163
+ /* @__PURE__ */ jsx(
164
+ "circle",
165
+ {
166
+ className: "opacity-25",
167
+ cx: "12",
168
+ cy: "12",
169
+ r: "10",
170
+ stroke: "currentColor",
171
+ strokeWidth: "4"
172
+ }
173
+ ),
174
+ /* @__PURE__ */ jsx(
175
+ "path",
176
+ {
177
+ className: "opacity-75",
178
+ fill: "currentColor",
179
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
180
+ }
181
+ )
182
+ ]
183
+ }
184
+ ),
185
+ !loading && isShowingSuccess && /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0", children: successIcon ?? /* @__PURE__ */ jsx(SuccessIcon, {}) }),
186
+ !loading && !isShowingSuccess && startIcon && /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0", children: startIcon }),
187
+ content,
188
+ !loading && !isShowingSuccess && endIcon && /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0", children: endIcon })
189
+ ]
190
+ }
191
+ );
192
+ }
193
+ );
194
+ Button.displayName = "Button";
195
+ var SplitButton = forwardRef(
196
+ ({
197
+ label,
198
+ onClick,
199
+ items,
200
+ menuLabel = "More actions",
201
+ align = "right",
202
+ variant = "solid",
203
+ type = "primary",
204
+ size = "md",
205
+ color,
206
+ disabled = false,
207
+ loading = false,
208
+ className,
209
+ menuClassName,
210
+ ...buttonProps
211
+ }, ref) => {
212
+ const [open, setOpen] = useState(false);
213
+ const rootRef = useRef(null);
214
+ const menuId = useId();
215
+ useEffect(() => {
216
+ const handleEscape = (event) => {
217
+ if (event.key === "Escape") {
218
+ setOpen(false);
219
+ }
220
+ };
221
+ document.addEventListener("keydown", handleEscape);
222
+ return () => {
223
+ document.removeEventListener("keydown", handleEscape);
224
+ };
225
+ }, []);
226
+ const handleItemSelect = (item) => {
227
+ if (item.disabled) {
228
+ return;
229
+ }
230
+ item.onClick?.();
231
+ setOpen(false);
232
+ };
233
+ const hasItems = items.length > 0;
234
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative inline-flex", className), children: [
235
+ /* @__PURE__ */ jsx(
236
+ Button,
237
+ {
238
+ ref,
239
+ variant,
240
+ type,
241
+ size,
242
+ color,
243
+ disabled,
244
+ loading,
245
+ onClick,
246
+ className: cn("rounded-r-none", variant !== "link" && "border-r-0"),
247
+ ...buttonProps,
248
+ children: label
249
+ }
250
+ ),
251
+ /* @__PURE__ */ jsx(
252
+ Button,
253
+ {
254
+ variant,
255
+ type,
256
+ size,
257
+ color,
258
+ disabled: disabled || loading || !hasItems,
259
+ "aria-label": menuLabel,
260
+ "aria-haspopup": "menu",
261
+ "aria-expanded": open,
262
+ "aria-controls": menuId,
263
+ onClick: () => setOpen((prev) => !prev),
264
+ className: cn("rounded-l-none px-3", variant !== "link" && "border-l"),
265
+ children: /* @__PURE__ */ jsx(
266
+ "svg",
267
+ {
268
+ xmlns: "http://www.w3.org/2000/svg",
269
+ viewBox: "0 0 20 20",
270
+ fill: "currentColor",
271
+ className: cn("h-4 w-4 transition-transform duration-150", open && "rotate-180"),
272
+ "aria-hidden": "true",
273
+ children: /* @__PURE__ */ jsx(
274
+ "path",
275
+ {
276
+ fillRule: "evenodd",
277
+ d: "M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z",
278
+ clipRule: "evenodd"
279
+ }
280
+ )
281
+ }
282
+ )
283
+ }
284
+ ),
285
+ open && hasItems && /* @__PURE__ */ jsx(
286
+ "div",
287
+ {
288
+ id: menuId,
289
+ role: "menu",
290
+ className: cn(
291
+ "absolute z-20 top-full mt-2 min-w-44 overflow-hidden rounded-lg border border-base-300 bg-base-100 p-1 shadow-lg",
292
+ align === "right" ? "right-0" : "left-0",
293
+ menuClassName
294
+ ),
295
+ children: items.map((item, index) => /* @__PURE__ */ jsx(
296
+ "button",
297
+ {
298
+ type: "button",
299
+ role: "menuitem",
300
+ disabled: item.disabled,
301
+ onClick: () => handleItemSelect(item),
302
+ className: cn(
303
+ "w-full rounded-md px-3 py-2 text-left text-sm transition-colors",
304
+ item.disabled ? "cursor-not-allowed opacity-50" : item.destructive ? "text-error hover:bg-error/10" : "text-base-content hover:bg-base-200"
305
+ ),
306
+ children: item.label
307
+ },
308
+ item.key ?? `split-item-${index}`
309
+ ))
310
+ }
311
+ )
312
+ ] });
313
+ }
314
+ );
315
+ SplitButton.displayName = "SplitButton";
316
+ var sizeMap = {
317
+ xs: 12,
318
+ sm: 16,
319
+ md: 24,
320
+ lg: 32,
321
+ xl: 48,
322
+ "2xl": 64
323
+ };
324
+ var colorMap = {
325
+ primary: "text-primary",
326
+ secondary: "text-secondary",
327
+ accent: "text-accent",
328
+ neutral: "text-neutral",
329
+ info: "text-info",
330
+ success: "text-success",
331
+ warning: "text-warning",
332
+ error: "text-error",
333
+ current: "text-current"
334
+ };
335
+ var Icon = React.forwardRef(
336
+ ({ icon: ExplicitIcon, name, size = "md", color = "current", className, ...props }, ref) => {
337
+ const sizeValue = sizeMap[size];
338
+ const isThemeColor = color && Object.keys(colorMap).includes(color);
339
+ const colorClass = isThemeColor ? colorMap[color] : "";
340
+ const customColor = !isThemeColor ? color : void 0;
341
+ const IconComponent = ExplicitIcon || (name ? LucideIcons[name] : null);
342
+ if (!IconComponent) {
343
+ console.warn(`Icon: No icon provided. Pass either 'icon' component or 'name' string.`);
344
+ return null;
345
+ }
346
+ return /* @__PURE__ */ jsx(
347
+ IconComponent,
348
+ {
349
+ ref,
350
+ width: sizeValue,
351
+ height: sizeValue,
352
+ size: sizeValue,
353
+ color: customColor,
354
+ style: { color: customColor, ...props.style },
355
+ className: `${colorClass} ${className || ""}`,
356
+ ...props
357
+ }
358
+ );
359
+ }
360
+ );
361
+ Icon.displayName = "Icon";
362
+
363
+ export { Button, Icon };
364
+ //# sourceMappingURL=index.js.map
365
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/cn.ts","../src/components/Button/Button.tsx","../src/components/Icon/Icon.tsx"],"names":["jsx"],"mappings":"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","file":"index.js","sourcesContent":["import { type ClassValue, clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","import { forwardRef, useEffect, useId, useRef, useState } from 'react';\nimport type { ComponentProps } from '../../types';\nimport { cn } from '../../utils/cn';\n\nexport type ButtonVariant = 'solid' | 'outlined' | 'dashed' | 'text' | 'link';\nexport type ButtonType = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'ghost';\n\nexport interface ButtonProps extends Omit<ComponentProps<'button'>, 'type'> {\n /**\n * The visual style variant of the button\n * @default 'solid'\n */\n variant?: ButtonVariant;\n /**\n * The type theme of the button\n * @default 'primary'\n */\n type?: ButtonType;\n /**\n * Custom hex, rgb, or css color string that overrides the type colors\n */\n color?: string;\n /**\n * The size of the button\n * @default 'md'\n */\n size?: 'sm' | 'md' | 'lg';\n /**\n * Whether the button should take full width of its container\n * @default false\n */\n fullWidth?: boolean;\n /**\n * Whether the button is in a loading state\n * @default false\n */\n loading?: boolean;\n /**\n * Optional text shown while loading.\n */\n loadingText?: React.ReactNode;\n /**\n * Whether the button should show temporary success feedback.\n */\n success?: boolean;\n /**\n * Optional text shown in success state.\n */\n successText?: React.ReactNode;\n /**\n * Optional icon shown in success state.\n */\n successIcon?: React.ReactNode;\n /**\n * Duration in milliseconds before success state resets.\n * @default 1500\n */\n successDuration?: number;\n /**\n * Element to place before the children.\n */\n startIcon?: React.ReactNode;\n /**\n * Element to place after the children.\n */\n endIcon?: React.ReactNode;\n}\n\nexport interface SplitButtonItem {\n /**\n * Stable key for rendering list items\n */\n key?: string;\n /**\n * Rendered menu item label\n */\n label: React.ReactNode;\n /**\n * Item click handler\n */\n onClick?: () => void;\n /**\n * Whether the item is disabled\n */\n disabled?: boolean;\n /**\n * Marks a destructive menu item with error color\n */\n destructive?: boolean;\n}\n\nexport interface SplitButtonProps extends Omit<ButtonProps, 'children' | 'onClick' | 'fullWidth'> {\n /**\n * Main action label/content\n */\n label: React.ReactNode;\n /**\n * Main action handler\n */\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n /**\n * Dropdown menu items\n */\n items: SplitButtonItem[];\n /**\n * Accessible label for toggle button\n * @default 'More actions'\n */\n menuLabel?: string;\n /**\n * Menu alignment relative to trigger\n * @default 'right'\n */\n align?: 'left' | 'right';\n /**\n * Optional wrapper classes for split group container\n */\n className?: string;\n /**\n * Optional menu panel classes\n */\n menuClassName?: string;\n}\n\nconst sizeClasses = {\n sm: 'px-3 py-1.5 text-sm',\n md: 'px-4 py-2 text-base',\n lg: 'px-6 py-3 text-lg',\n};\n\nconst variantClasses: Record<ButtonVariant, Record<ButtonType, string>> = {\n solid: {\n primary: 'bg-primary text-primary-content hover:opacity-90',\n secondary: 'bg-secondary text-secondary-content hover:opacity-90',\n success: 'bg-success text-success-content hover:opacity-90',\n danger: 'bg-error text-error-content hover:opacity-90',\n warning: 'bg-warning text-warning-content hover:opacity-90',\n info: 'bg-info text-info-content hover:opacity-90',\n ghost: 'bg-base-200 text-base-content hover:bg-base-300',\n },\n outlined: {\n primary: 'border border-primary text-primary bg-transparent hover:bg-primary/10',\n secondary: 'border border-secondary text-secondary bg-transparent hover:bg-secondary/10',\n success: 'border border-success text-success bg-transparent hover:bg-success/10',\n danger: 'border border-error text-error bg-transparent hover:bg-error/10',\n warning: 'border border-warning text-warning bg-transparent hover:bg-warning/10',\n info: 'border border-info text-info bg-transparent hover:bg-info/10',\n ghost: 'border border-base-200 text-base-content bg-transparent hover:bg-base-200/10',\n },\n dashed: {\n primary: 'border border-dashed border-primary text-primary bg-transparent hover:bg-primary/10',\n secondary: 'border border-dashed border-secondary text-secondary bg-transparent hover:bg-secondary/10',\n success: 'border border-dashed border-success text-success bg-transparent hover:bg-success/10',\n danger: 'border border-dashed border-error text-error bg-transparent hover:bg-error/10',\n warning: 'border border-dashed border-warning text-warning bg-transparent hover:bg-warning/10',\n info: 'border border-dashed border-info text-info bg-transparent hover:bg-info/10',\n ghost: 'border border-dashed border-base-200 text-base-content bg-transparent hover:bg-base-200/10',\n },\n text: {\n primary: 'text-primary bg-transparent hover:bg-primary/10',\n secondary: 'text-secondary bg-transparent hover:bg-secondary/10',\n success: 'text-success bg-transparent hover:bg-success/10',\n danger: 'text-error bg-transparent hover:bg-error/10',\n warning: 'text-warning bg-transparent hover:bg-warning/10',\n info: 'text-info bg-transparent hover:bg-info/10',\n ghost: 'text-base-content bg-transparent hover:bg-base-200/10',\n },\n link: {\n primary: 'text-primary underline bg-transparent hover:text-primary/80',\n secondary: 'text-secondary underline bg-transparent hover:text-secondary/80',\n success: 'text-success underline bg-transparent hover:text-success/80',\n danger: 'text-error underline bg-transparent hover:text-error/80',\n warning: 'text-warning underline bg-transparent hover:text-warning/80',\n info: 'text-info underline bg-transparent hover:text-info/80',\n ghost: 'text-base-content underline bg-transparent hover:text-base-content/80',\n },\n};\n\nconst getButtonClasses = (variant: ButtonVariant, type: ButtonType) => {\n return variantClasses[variant][type] || variantClasses.solid.primary;\n};\n\nconst SuccessIcon = () => (\n <svg\n className=\"h-4 w-4\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2.5\"\n d=\"M5 12.5l4.5 4.5L19 7.5\"\n />\n </svg>\n);\n\n/**\n * Primary UI component for user interaction\n *\n * Supports all standard HTML button attributes including `style` and `className`.\n * - Use `type` and `variant` for standard theming.\n * - Use `className` with Tailwind classes for one-off overrides (merged intelligently).\n * - Use `style` for inline dynamic styles.\n */\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n variant = 'solid',\n type = 'primary',\n size = 'md',\n fullWidth = false,\n loading = false,\n loadingText,\n success = false,\n successText,\n successIcon,\n successDuration = 1500,\n disabled = false,\n startIcon,\n endIcon,\n color,\n className = '',\n children,\n ...props\n },\n ref\n ) => {\n const [isShowingSuccess, setIsShowingSuccess] = useState(false);\n\n useEffect(() => {\n if (!success || loading) {\n return;\n }\n\n setIsShowingSuccess(true);\n const timer = window.setTimeout(() => {\n setIsShowingSuccess(false);\n }, successDuration);\n\n return () => window.clearTimeout(timer);\n }, [success, loading, successDuration]);\n\n const baseClasses = 'inline-flex items-center justify-center font-medium rounded-[var(--radius-selector)] transition-all duration-200 focus:outline-none disabled:opacity-50 disabled:cursor-not-allowed active:scale-95 gap-2';\n const variantClass = getButtonClasses(variant, type);\n const sizeClass = sizeClasses[size];\n const widthClass = fullWidth ? 'w-full' : '';\n const loadingClass = loading ? 'cursor-wait' : '';\n\n const combinedClasses = cn(\n baseClasses,\n variantClass,\n sizeClass,\n widthClass,\n loadingClass,\n className\n );\n\n const customStyle: React.CSSProperties = { ...props.style };\n if (color) {\n if (variant === 'solid') {\n customStyle.backgroundColor = color;\n customStyle.borderColor = color;\n } else if (variant === 'outlined' || variant === 'dashed') {\n customStyle.borderColor = color;\n customStyle.color = color;\n } else if (variant === 'text' || variant === 'link') {\n customStyle.color = color;\n }\n }\n\n const content = loading\n ? loadingText ?? children\n : isShowingSuccess\n ? successText ?? children\n : children;\n\n return (\n <button\n ref={ref}\n type=\"button\"\n className={combinedClasses}\n style={customStyle}\n disabled={disabled || loading}\n aria-busy={loading || undefined}\n {...props}\n >\n {loading && (\n <svg\n className=\"animate-spin -ml-1 h-4 w-4\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n )}\n {!loading && isShowingSuccess && <span className=\"inline-flex shrink-0\">{successIcon ?? <SuccessIcon />}</span>}\n {!loading && !isShowingSuccess && startIcon && <span className=\"inline-flex shrink-0\">{startIcon}</span>}\n {content}\n {!loading && !isShowingSuccess && endIcon && <span className=\"inline-flex shrink-0\">{endIcon}</span>}\n </button>\n );\n }\n);\n\nButton.displayName = 'Button';\n\nexport const SplitButton = forwardRef<HTMLButtonElement, SplitButtonProps>(\n (\n {\n label,\n onClick,\n items,\n menuLabel = 'More actions',\n align = 'right',\n variant = 'solid',\n type = 'primary',\n size = 'md',\n color,\n disabled = false,\n loading = false,\n className,\n menuClassName,\n ...buttonProps\n },\n ref\n ) => {\n const [open, setOpen] = useState(false);\n const rootRef = useRef<HTMLDivElement>(null);\n const menuId = useId();\n\n useEffect(() => {\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n setOpen(false);\n }\n };\n\n document.addEventListener('keydown', handleEscape);\n\n return () => {\n document.removeEventListener('keydown', handleEscape);\n };\n }, []);\n\n const handleItemSelect = (item: SplitButtonItem) => {\n if (item.disabled) {\n return;\n }\n\n item.onClick?.();\n setOpen(false);\n };\n\n const hasItems = items.length > 0;\n\n return (\n <div ref={rootRef} className={cn('relative inline-flex', className)}>\n <Button\n ref={ref}\n variant={variant}\n type={type}\n size={size}\n color={color}\n disabled={disabled}\n loading={loading}\n onClick={onClick}\n className={cn('rounded-r-none', variant !== 'link' && 'border-r-0')}\n {...buttonProps}\n >\n {label}\n </Button>\n\n <Button\n variant={variant}\n type={type}\n size={size}\n color={color}\n disabled={disabled || loading || !hasItems}\n aria-label={menuLabel}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-controls={menuId}\n onClick={() => setOpen((prev) => !prev)}\n className={cn('rounded-l-none px-3', variant !== 'link' && 'border-l')}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n className={cn('h-4 w-4 transition-transform duration-150', open && 'rotate-180')}\n aria-hidden=\"true\"\n >\n <path\n fillRule=\"evenodd\"\n d=\"M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z\"\n clipRule=\"evenodd\"\n />\n </svg>\n </Button>\n\n {open && hasItems && (\n <div\n id={menuId}\n role=\"menu\"\n className={cn(\n 'absolute z-20 top-full mt-2 min-w-44 overflow-hidden rounded-lg border border-base-300 bg-base-100 p-1 shadow-lg',\n align === 'right' ? 'right-0' : 'left-0',\n menuClassName\n )}\n >\n {items.map((item, index) => (\n <button\n key={item.key ?? `split-item-${index}`}\n type=\"button\"\n role=\"menuitem\"\n disabled={item.disabled}\n onClick={() => handleItemSelect(item)}\n className={cn(\n 'w-full rounded-md px-3 py-2 text-left text-sm transition-colors',\n item.disabled\n ? 'cursor-not-allowed opacity-50'\n : item.destructive\n ? 'text-error hover:bg-error/10'\n : 'text-base-content hover:bg-base-200'\n )}\n >\n {item.label}\n </button>\n ))}\n </div>\n )}\n </div>\n );\n }\n);\n\nSplitButton.displayName = 'SplitButton';\n","import * as LucideIcons from 'lucide-react';\nimport React from 'react';\n\nexport type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';\n\nexport type IconColor =\n | 'primary'\n | 'secondary'\n | 'accent'\n | 'neutral'\n | 'info'\n | 'success'\n | 'warning'\n | 'error'\n | 'current';\n\nexport type IconName = keyof typeof LucideIcons;\n\nexport interface IconProps extends Omit<React.SVGProps<SVGSVGElement>, 'color'> {\n /** The icon component to render (e.g., from Phosphor, Heroicons, Radix, etc.) */\n icon?: React.ElementType;\n /** The name of the Lucide icon to render (backward compatibility) */\n name?: IconName;\n /** Size of the icon */\n size?: IconSize;\n /** Color of the icon. Can be a theme color (e.g., 'primary') or any valid CSS color string. */\n color?: IconColor | string;\n}\n\nconst sizeMap: Record<IconSize, number> = {\n xs: 12,\n sm: 16,\n md: 24,\n lg: 32,\n xl: 48,\n '2xl': 64,\n};\n\nconst colorMap: Record<IconColor, string> = {\n primary: 'text-primary',\n secondary: 'text-secondary',\n accent: 'text-accent',\n neutral: 'text-neutral',\n info: 'text-info',\n success: 'text-success',\n warning: 'text-warning',\n error: 'text-error',\n current: 'text-current',\n};\n\nexport const Icon = React.forwardRef<SVGSVGElement, IconProps>(\n ({ icon: ExplicitIcon, name, size = 'md', color = 'current', className, ...props }, ref) => {\n const sizeValue = sizeMap[size];\n\n const isThemeColor = color && Object.keys(colorMap).includes(color as string);\n const colorClass = isThemeColor ? colorMap[color as IconColor] : '';\n const customColor = !isThemeColor ? color : undefined;\n\n // Resolve the component to render: explicitly passed icon takes precedence over named lucide icon.\n const IconComponent = ExplicitIcon || (name ? (LucideIcons[name] as React.ElementType) : null);\n\n if (!IconComponent) {\n console.warn(`Icon: No icon provided. Pass either 'icon' component or 'name' string.`);\n return null;\n }\n\n // We pass standard SVG width/height and color styles. \n // This makes it compatible with almost ALL external icon libraries out of the box\n // because components generally forward standard SVG props to their root <svg>.\n return (\n <IconComponent\n ref={ref}\n width={sizeValue}\n height={sizeValue}\n // Fallback for libraries like Lucide or Phosphor that use a custom `size` prop\n size={sizeValue}\n // Fallback for libraries like Lucide or Phosphor that use a custom `color` prop\n color={customColor}\n style={{ color: customColor, ...props.style }}\n className={`${colorClass} ${className || ''}`}\n {...props}\n />\n );\n }\n);\n\nIcon.displayName = 'Icon';\n"]}
@@ -0,0 +1,2 @@
1
+ @import "tailwindcss";
2
+ @import "./theme.css";
package/dist/theme.css ADDED
@@ -0,0 +1,145 @@
1
+ /* Theme tokens: Tailwind utilities use these; values come from theme selectors below */
2
+ @theme {
3
+ /* Colors (semantic) */
4
+ --color-base-100: var(--theme-base-100);
5
+ --color-base-200: var(--theme-base-200);
6
+ --color-base-300: var(--theme-base-300);
7
+ --color-base-content: var(--theme-base-content);
8
+ --color-primary: var(--theme-primary);
9
+ --color-primary-content: var(--theme-primary-content);
10
+ --color-secondary: var(--theme-secondary);
11
+ --color-secondary-content: var(--theme-secondary-content);
12
+ --color-accent: var(--theme-accent);
13
+ --color-accent-content: var(--theme-accent-content);
14
+ --color-neutral: var(--theme-neutral);
15
+ --color-neutral-content: var(--theme-neutral-content);
16
+ --color-info: var(--theme-info);
17
+ --color-info-content: var(--theme-info-content);
18
+ --color-success: var(--theme-success);
19
+ --color-success-content: var(--theme-success-content);
20
+ --color-warning: var(--theme-warning);
21
+ --color-warning-content: var(--theme-warning-content);
22
+ --color-error: var(--theme-error);
23
+ --color-error-content: var(--theme-error-content);
24
+
25
+ /* Radius */
26
+ --radius-selector: var(--theme-radius-selector);
27
+ --radius-field: var(--theme-radius-field);
28
+ --radius-box: var(--theme-radius-box);
29
+
30
+ /* Size */
31
+ --size-selector: var(--theme-size-selector);
32
+ --size-field: var(--theme-size-field);
33
+
34
+ /* Border */
35
+ --border-width: var(--theme-border);
36
+
37
+ /* Typography (unchanged) */
38
+ --font-size-xs: 0.75rem;
39
+ --font-size-sm: 0.875rem;
40
+ --font-size-base: 1rem;
41
+ --font-size-lg: 1.125rem;
42
+ --font-size-xl: 1.25rem;
43
+ --font-size-2xl: 1.5rem;
44
+ --font-size-3xl: 1.875rem;
45
+ --font-size-4xl: 2.25rem;
46
+ --font-size-5xl: 3rem;
47
+ --font-weight-normal: 400;
48
+ --font-weight-medium: 500;
49
+ --font-weight-semibold: 600;
50
+ --font-weight-bold: 700;
51
+ --line-height-tight: 1.25;
52
+ --line-height-normal: 1.5;
53
+ --line-height-relaxed: 1.75;
54
+
55
+ /* Spacing */
56
+ --spacing-xs: 0.25rem;
57
+ --spacing-sm: 0.5rem;
58
+ --spacing-md: 1rem;
59
+ --spacing-lg: 1.5rem;
60
+ --spacing-xl: 2rem;
61
+ --spacing-2xl: 3rem;
62
+ --spacing-3xl: 4rem;
63
+
64
+ /* Shadows */
65
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
66
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
67
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
68
+ --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
69
+
70
+ /* Transitions */
71
+ --transition-fast: 150ms ease-in-out;
72
+ --transition-base: 200ms ease-in-out;
73
+ --transition-slow: 300ms ease-in-out;
74
+ }
75
+
76
+ /* ========== Winter (light) – default ========== */
77
+ :root,
78
+ [data-theme="winter"] {
79
+ color-scheme: light;
80
+
81
+ --theme-base-100: oklch(97% 0.013 236.62);
82
+ --theme-base-200: oklch(95% 0.026 236.824);
83
+ --theme-base-300: oklch(90% 0.058 230.902);
84
+ --theme-base-content: oklch(39% 0.09 240.876);
85
+ --theme-primary: oklch(80% 0.105 251.813);
86
+ --theme-primary-content: oklch(28% 0.091 267.935);
87
+ --theme-secondary: oklch(82% 0.111 230.318);
88
+ --theme-secondary-content: oklch(29% 0.066 243.157);
89
+ --theme-accent: oklch(0% 0 0);
90
+ --theme-accent-content: oklch(100% 0 0);
91
+ --theme-neutral: oklch(44% 0.11 240.79);
92
+ --theme-neutral-content: oklch(97% 0.013 236.62);
93
+ --theme-info: oklch(58% 0.158 241.966);
94
+ --theme-info-content: oklch(97% 0.013 236.62);
95
+ --theme-success: oklch(60% 0.118 184.704);
96
+ --theme-success-content: oklch(98% 0.014 180.72);
97
+ --theme-warning: oklch(66% 0.179 58.318);
98
+ --theme-warning-content: oklch(98% 0.022 95.277);
99
+ --theme-error: oklch(58% 0.253 17.585);
100
+ --theme-error-content: oklch(96% 0.015 12.422);
101
+
102
+ --theme-radius-selector: 0.5rem;
103
+ --theme-radius-field: 0rem;
104
+ --theme-radius-box: 1rem;
105
+ --theme-size-selector: 0.25rem;
106
+ --theme-size-field: 0.25rem;
107
+ --theme-border: 1px;
108
+ --theme-depth: 1;
109
+ --theme-noise: 0;
110
+ }
111
+
112
+ /* ========== Starlight (dark) ========== */
113
+ [data-theme="starlight"] {
114
+ color-scheme: dark;
115
+
116
+ --theme-base-100: oklch(27% 0.041 260.031);
117
+ --theme-base-200: oklch(20% 0.042 265.755);
118
+ --theme-base-300: oklch(35% 0.041 260.031);
119
+ --theme-base-content: oklch(96% 0.007 247.896);
120
+ --theme-primary: oklch(80% 0.105 251.813);
121
+ --theme-primary-content: oklch(28% 0.091 267.935);
122
+ --theme-secondary: oklch(82% 0.111 230.318);
123
+ --theme-secondary-content: oklch(29% 0.066 243.157);
124
+ --theme-accent: oklch(89% 0.057 293.283);
125
+ --theme-accent-content: oklch(28% 0.091 267.935);
126
+ --theme-neutral: oklch(70% 0.041 260.031);
127
+ --theme-neutral-content: oklch(98% 0.003 247.858);
128
+ --theme-info: oklch(70% 0.165 254.624);
129
+ --theme-info-content: oklch(28% 0.091 267.935);
130
+ --theme-success: oklch(84% 0.238 128.85);
131
+ --theme-success-content: oklch(27% 0.072 132.109);
132
+ --theme-warning: oklch(85% 0.199 91.936);
133
+ --theme-warning-content: oklch(28% 0.066 53.813);
134
+ --theme-error: oklch(70% 0.191 22.216);
135
+ --theme-error-content: oklch(25% 0.092 26.042);
136
+
137
+ --theme-radius-selector: 0.5rem;
138
+ --theme-radius-field: 2rem;
139
+ --theme-radius-box: 0.5rem;
140
+ --theme-size-selector: 0.25rem;
141
+ --theme-size-field: 0.25rem;
142
+ --theme-border: 1px;
143
+ --theme-depth: 0;
144
+ --theme-noise: 0;
145
+ }
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@sehgaltech/psui",
3
+ "version": "1.0.0",
4
+ "description": "A scalable and customizable UI library built with Tailwind CSS v4, TypeScript, and React",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "./styles": "./dist/styles.css",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md"
26
+ ],
27
+ "sideEffects": false,
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "dev": "tsup --watch",
31
+ "test": "vitest",
32
+ "test:ui": "vitest --ui",
33
+ "type-check": "tsc --noEmit"
34
+ },
35
+ "keywords": [
36
+ "ui-library",
37
+ "react",
38
+ "typescript",
39
+ "tailwindcss",
40
+ "components",
41
+ "design-system"
42
+ ],
43
+ "author": "purvisehgal",
44
+ "license": "MIT",
45
+ "peerDependencies": {
46
+ "react": "^18.0.0 || ^19.0.0",
47
+ "react-dom": "^18.0.0 || ^19.0.0"
48
+ },
49
+ "devDependencies": {
50
+ "@tailwindcss/vite": "^4.2.0",
51
+ "@testing-library/jest-dom": "^6.1.5",
52
+ "@testing-library/react": "^14.1.2",
53
+ "@testing-library/user-event": "^14.5.1",
54
+ "@types/react": "^18.2.43",
55
+ "@types/react-dom": "^18.2.17",
56
+ "@vitejs/plugin-react": "^4.2.1",
57
+ "jsdom": "^23.0.1",
58
+ "tailwindcss": "^4.2.0",
59
+ "tsup": "^8.0.1",
60
+ "typescript": "^5.3.3",
61
+ "vitest": "^4.0.18"
62
+ },
63
+ "overrides": {
64
+ "minimatch": "^10.2.1"
65
+ },
66
+ "dependencies": {
67
+ "@heroicons/react": "^2.2.0",
68
+ "@phosphor-icons/react": "^2.1.10",
69
+ "@radix-ui/react-icons": "^1.3.2",
70
+ "@remixicon/react": "^4.9.0",
71
+ "@types/react-virtualized-auto-sizer": "^1.0.4",
72
+ "@types/react-window": "^1.8.8",
73
+ "clsx": "^2.1.1",
74
+ "iconoir-react": "^7.11.0",
75
+ "lucide-react": "^0.574.0",
76
+ "react-virtualized-auto-sizer": "^2.0.3",
77
+ "react-window": "^2.2.7",
78
+ "tailwind-merge": "^3.5.0"
79
+ }
80
+ }