@astrake/lumora-ui 0.1.5 → 0.2.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 (53) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/package.json +9 -1
  3. package/src/components/LuAlert.vue +33 -0
  4. package/src/components/LuBreadcrumb.vue +63 -0
  5. package/src/components/LuCard.vue +8 -1
  6. package/src/components/LuCheckbox.vue +94 -0
  7. package/src/components/LuCodeBlock.vue +168 -0
  8. package/src/components/LuForm.types.ts +24 -0
  9. package/src/components/LuForm.vue +121 -0
  10. package/src/components/LuInput.vue +57 -5
  11. package/src/components/LuMenu.vue +86 -0
  12. package/src/components/LuMenuItem.vue +37 -0
  13. package/src/components/LuModal.vue +115 -0
  14. package/src/components/LuPagination.vue +118 -0
  15. package/src/components/LuRadio.vue +55 -0
  16. package/src/components/LuRadioGroup.types.ts +10 -0
  17. package/src/components/LuRadioGroup.vue +66 -0
  18. package/src/components/LuSelect.vue +38 -6
  19. package/src/components/LuSkeleton.vue +15 -0
  20. package/src/components/LuSpinner.vue +36 -0
  21. package/src/components/LuSwitch.vue +48 -12
  22. package/src/components/LuTag.vue +35 -0
  23. package/src/components/LuTextarea.vue +62 -0
  24. package/src/components/LuThemeSelect.vue +1 -1
  25. package/src/components/LuToggleButton.vue +35 -0
  26. package/src/components/LuToggleGroup.vue +27 -0
  27. package/src/components/__tests__/LuForm.test.ts +206 -0
  28. package/src/components/index.ts +18 -0
  29. package/src/context.ts +8 -5
  30. package/src/index.ts +2 -2
  31. package/src/layout/LuDock.vue +53 -20
  32. package/src/layout/LuDockItem.vue +3 -1
  33. package/src/layout/LuFill.vue +15 -5
  34. package/src/layout/LuFixed.vue +15 -5
  35. package/src/layout/LuGrid.vue +28 -5
  36. package/src/layout/LuScroll.vue +3 -1
  37. package/src/layout/LuSplitPane.vue +3 -3
  38. package/src/layout/LuSplitResizer.vue +5 -3
  39. package/src/layout/LuStack.vue +16 -11
  40. package/src/lumora.css +16 -0
  41. package/src/plugin.ts +3 -2
  42. package/src/shell/desktop/LuDesktopRailItem.vue +2 -2
  43. package/src/shell/desktop/LuDesktopShell.vue +3 -3
  44. package/src/shell/embedded/LuEmbeddedShell.vue +2 -2
  45. package/src/shell/embedded/LuEmbeddedStatusBar.vue +16 -0
  46. package/src/shell/embedded/LuEmbeddedTopBar.vue +17 -0
  47. package/src/shell/index.ts +4 -1
  48. package/src/shell/mobile/LuMobileHeader.vue +17 -0
  49. package/src/shell/mobile/LuMobileNavBar.vue +15 -0
  50. package/src/shell/mobile/LuMobileShell.vue +2 -2
  51. package/src/skins/default.ts +361 -29
  52. package/src/tailwind.ts +25 -0
  53. package/src/utils.ts +95 -0
@@ -3,11 +3,19 @@ import type { SkinMap } from "../types";
3
3
  export const defaultSkin: SkinMap = {
4
4
  LuButton: {
5
5
  default:
6
- "inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none",
7
- primary: "bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800",
8
- secondary: "bg-gray-100 text-gray-800 hover:bg-gray-200",
9
- ghost: "hover:bg-gray-100 text-gray-700",
10
- danger: "bg-red-600 text-white hover:bg-red-700",
6
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border border-gray-200 bg-white text-gray-900 shadow-sm hover:bg-gray-50 px-4 py-2 text-sm font-medium transition-all duration-150 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50 dark:hover:bg-gray-900 dark:focus-visible:ring-offset-gray-900",
7
+ primary:
8
+ "border-blue-600 bg-blue-600 text-white shadow-sm hover:bg-blue-700 hover:border-blue-700 active:bg-blue-800 dark:border-blue-500 dark:bg-blue-500 dark:hover:bg-blue-600 dark:hover:border-blue-600",
9
+ secondary:
10
+ "border-gray-300 bg-white text-gray-700 shadow-sm hover:bg-gray-50 hover:border-gray-400 active:bg-gray-100 dark:border-gray-600 dark:bg-gray-900 dark:text-gray-200 dark:hover:bg-gray-800 dark:hover:border-gray-500",
11
+ ghost:
12
+ "border-transparent bg-transparent text-gray-600 hover:bg-gray-100 hover:text-gray-900 active:bg-gray-200 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100",
13
+ danger:
14
+ "border-red-600 bg-red-600 text-white shadow-sm hover:bg-red-700 hover:border-red-700 active:bg-red-800 dark:border-red-500 dark:bg-red-500 dark:hover:bg-red-600",
15
+ icon:
16
+ "border-transparent bg-transparent p-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700 active:bg-gray-200 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200",
17
+ "code-action":
18
+ "h-8 w-8 rounded-md border-transparent bg-gray-950/50 text-white backdrop-blur-sm hover:bg-gray-950/80 active:bg-gray-950",
11
19
  },
12
20
  LuIcon: {
13
21
  default: "inline-flex items-center justify-center shrink-0",
@@ -18,51 +26,232 @@ export const defaultSkin: SkinMap = {
18
26
  },
19
27
  LuInput: {
20
28
  default:
21
- "flex h-10 w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50",
29
+ "flex h-10 w-full rounded-md border border-gray-300 bg-transparent px-3 py-2 text-sm placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-700 dark:text-gray-50 dark:focus:ring-blue-500",
30
+ },
31
+ LuInputPrepend: {
32
+ default: "absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-400 dark:text-gray-500"
33
+ },
34
+ LuInputAppend: {
35
+ default: "absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none text-gray-400 dark:text-gray-500"
36
+ },
37
+ LuTextarea: {
38
+ default:
39
+ "flex min-h-[80px] w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-500",
40
+ error: "flex min-h-[80px] w-full rounded-md border border-red-500 bg-white px-3 py-2 text-sm placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-red-500 disabled:opacity-50 dark:border-red-500 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-500",
41
+ },
42
+ LuCheckboxContainer: {
43
+ default: "flex items-center space-x-2",
44
+ },
45
+ LuCheckbox: {
46
+ default: "h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500 disabled:opacity-50 dark:border-gray-700 dark:bg-gray-900 dark:ring-offset-gray-900 dark:focus:ring-blue-500",
47
+ },
48
+ LuCheckboxLabel: {
49
+ default: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-gray-900 dark:text-gray-100 cursor-pointer",
50
+ },
51
+ LuRadioGroup: {
52
+ default: "flex flex-col space-y-2",
53
+ horizontal: "flex flex-row space-x-4",
54
+ },
55
+ LuRadioContainer: {
56
+ default: "flex items-center space-x-2",
57
+ },
58
+ LuRadio: {
59
+ default: "h-4 w-4 rounded-full border-gray-300 text-blue-600 focus:ring-blue-500 disabled:opacity-50 dark:border-gray-700 dark:bg-gray-900 dark:ring-offset-gray-900 dark:focus:ring-blue-500",
60
+ },
61
+ LuRadioLabel: {
62
+ default: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-gray-900 dark:text-gray-100 cursor-pointer",
63
+ },
64
+ LuAlert: {
65
+ default: "relative w-full rounded-lg border p-4 flex gap-3 text-sm bg-white text-gray-900 border-gray-200 dark:bg-gray-950 dark:text-gray-100 dark:border-gray-800",
66
+ success: "relative w-full rounded-lg border p-4 flex gap-3 text-sm bg-green-50 text-green-900 border-green-200 dark:bg-green-900/20 dark:text-green-100 dark:border-green-900/50",
67
+ warning: "relative w-full rounded-lg border p-4 flex gap-3 text-sm bg-yellow-50 text-yellow-900 border-yellow-200 dark:bg-yellow-900/20 dark:text-yellow-100 dark:border-yellow-900/50",
68
+ destructive: "relative w-full rounded-lg border p-4 flex gap-3 text-sm bg-red-50 text-red-900 border-red-200 dark:bg-red-900/20 dark:text-red-100 dark:border-red-900/50",
69
+ info: "relative w-full rounded-lg border p-4 flex gap-3 text-sm bg-blue-50 text-blue-900 border-blue-200 dark:bg-blue-900/20 dark:text-blue-100 dark:border-blue-900/50",
70
+ },
71
+ LuAlertIcon: {
72
+ default: "shrink-0 mt-0.5 w-5 h-5",
73
+ },
74
+ LuAlertContent: {
75
+ default: "flex-1 flex flex-col gap-1",
76
+ },
77
+ LuAlertAction: {
78
+ default: "shrink-0 ml-auto",
79
+ },
80
+ LuSpinner: {
81
+ default: "animate-spin text-gray-900 dark:text-gray-100 h-5 w-5",
82
+ sm: "animate-spin text-gray-900 dark:text-gray-100 h-4 w-4",
83
+ md: "animate-spin text-gray-900 dark:text-gray-100 h-5 w-5",
84
+ lg: "animate-spin text-gray-900 dark:text-gray-100 h-8 w-8",
85
+ primary: "animate-spin text-blue-600 dark:text-blue-500 h-5 w-5",
86
+ },
87
+ LuSkeleton: {
88
+ default: "animate-pulse rounded-md bg-gray-200 dark:bg-gray-800",
89
+ circle: "animate-pulse rounded-full bg-gray-200 dark:bg-gray-800",
90
+ line: "animate-pulse rounded bg-gray-200 dark:bg-gray-800 h-4 w-full",
91
+ block: "animate-pulse rounded-md bg-gray-200 dark:bg-gray-800 h-24 w-full",
92
+ },
93
+ LuTag: {
94
+ default: "inline-flex items-center rounded-md border border-gray-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-gray-950 focus:ring-offset-2 dark:border-gray-800 dark:focus:ring-gray-300",
95
+ },
96
+ LuTagCloseButton: {
97
+ default: "ml-1 inline-flex items-center rounded-full outline-none hover:bg-gray-200 focus:bg-gray-200 dark:hover:bg-gray-800 dark:focus:bg-gray-800",
98
+ },
99
+ LuTagIcon: {
100
+ default: "h-3 w-3",
101
+ },
102
+ LuBreadcrumb: {
103
+ default: "flex flex-wrap items-center gap-1.5 break-words text-sm text-gray-500 sm:gap-2.5 dark:text-gray-400",
104
+ },
105
+ LuBreadcrumbItem: {
106
+ default: "inline-flex items-center gap-1.5",
107
+ },
108
+ LuBreadcrumbLink: {
109
+ default: "transition-colors hover:text-gray-950 dark:hover:text-gray-50",
110
+ },
111
+ LuBreadcrumbPage: {
112
+ default: "font-normal text-gray-950 dark:text-gray-50",
113
+ },
114
+ LuBreadcrumbSeparator: {
115
+ default: "h-3.5 w-3.5",
116
+ },
117
+ LuMenu: {
118
+ default: "relative inline-block text-left",
119
+ },
120
+ LuMenuTrigger: {
121
+ default: "inline-flex items-center justify-center",
122
+ },
123
+ LuMenuContent: {
124
+ default: "absolute z-50 mt-2 min-w-[8rem] rounded-md border border-gray-200 bg-white p-1 text-gray-950 shadow-md dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50",
125
+ },
126
+ LuMenuGroup: {
127
+ default: "flex flex-col gap-1",
128
+ },
129
+ LuMenuItem: {
130
+ default: "relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-gray-100 focus:bg-gray-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:hover:bg-gray-800 dark:focus:bg-gray-800",
131
+ },
132
+ LuPagination: {
133
+ default: "flex flex-row items-center justify-center gap-2",
134
+ },
135
+ LuPaginationButton: {
136
+ default: "h-9 w-9 p-0 disabled:opacity-50 disabled:pointer-events-none",
137
+ },
138
+ LuPaginationPages: {
139
+ default: "flex flex-row items-center gap-1",
140
+ },
141
+ LuPaginationPageButton: {
142
+ default: "h-9 w-9 p-0",
143
+ },
144
+ LuPaginationEllipsis: {
145
+ default: "flex h-9 w-9 items-center justify-center",
146
+ },
147
+ LuModalOverlay: {
148
+ default: "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm grid place-items-center p-4",
149
+ },
150
+ LuModal: {
151
+ default: "relative w-full max-w-lg rounded-xl border border-gray-200 bg-white shadow-lg dark:border-gray-800 dark:bg-gray-950",
152
+ },
153
+ LuModalHeader: {
154
+ default: "flex flex-col space-y-1.5 p-6 text-center sm:text-left",
155
+ },
156
+ LuModalContent: {
157
+ default: "p-6 pt-0",
158
+ },
159
+ LuModalFooter: {
160
+ default: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 p-6 pt-0",
161
+ },
162
+ LuModalCloseButton: {
163
+ default: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-gray-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-gray-100 data-[state=open]:text-gray-500 dark:ring-offset-gray-950 dark:focus:ring-gray-300 dark:data-[state=open]:bg-gray-800 dark:data-[state=open]:text-gray-400",
22
164
  },
23
165
  LuDesktopRailBar: {
24
- default: "flex flex-col w-12 h-full border-r border-gray-200 bg-white transition-all duration-200 overflow-hidden",
166
+ default: "flex flex-col w-12 h-full border-r border-gray-200 bg-white transition-all duration-200 overflow-hidden dark:bg-gray-900 dark:border-gray-800 shrink-0",
25
167
  expanded: "w-56",
26
168
  },
27
169
  LuDesktopRailItem: {
28
- default: "flex items-center gap-3 px-3 py-2 text-gray-600 hover:bg-gray-100 cursor-pointer whitespace-nowrap",
29
- active: "bg-blue-50 text-blue-700 font-medium",
170
+ default: "flex items-center gap-3 px-3 py-2 text-gray-600 hover:bg-gray-100 cursor-pointer whitespace-nowrap dark:text-gray-300 dark:hover:bg-gray-800",
171
+ active: "bg-blue-50 text-blue-700 font-medium dark:bg-blue-900/50 dark:text-blue-400",
30
172
  },
31
173
  LuDesktopTopBar: {
32
- default: "flex items-center h-14 border-b border-gray-200 bg-white px-4 shrink-0",
174
+ default: "flex items-center justify-between w-full h-14 border-b border-gray-200 bg-white px-4 shrink-0 dark:bg-gray-900 dark:border-gray-800",
33
175
  },
34
176
  LuDesktopSidebar: {
35
- default: "flex flex-col border-r border-gray-200 bg-gray-50 overflow-y-auto h-full",
177
+ default: "flex flex-col w-64 shrink-0 border-r border-gray-200 bg-gray-50 overflow-y-auto h-full dark:bg-gray-800 dark:border-gray-800",
178
+ },
179
+ LuDesktopSidebarHeader: {
180
+ default: "p-4 shrink-0",
181
+ },
182
+ LuDesktopSidebarContent: {
183
+ default: "flex-1 overflow-y-auto p-4",
184
+ },
185
+ LuDesktopSidebarFooter: {
186
+ default: "p-4 shrink-0",
36
187
  },
37
188
  LuDesktopStatusBar: {
38
- default: "flex items-center h-8 border-t border-gray-200 bg-gray-50 px-4 text-xs text-gray-500 shrink-0",
189
+ default: "flex items-center justify-between w-full h-8 border-t border-gray-200 bg-gray-50 px-4 text-xs text-gray-500 shrink-0 dark:bg-gray-800 dark:border-gray-800 dark:text-gray-400",
190
+ },
191
+ LuDesktopShell: {
192
+ default: "flex flex-col h-screen w-full overflow-hidden bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100",
193
+ },
194
+ LuDesktopShellContentWrapper: {
195
+ default: "flex flex-1 w-full overflow-hidden",
196
+ },
197
+ LuDesktopShellMainContent: {
198
+ default: "flex flex-1 flex-col overflow-hidden relative",
199
+ },
200
+ LuSplit: {
201
+ default: "flex w-full h-full overflow-hidden",
202
+ horizontal: "flex flex-row w-full h-full overflow-hidden",
203
+ vertical: "flex flex-col w-full h-full overflow-hidden",
39
204
  },
40
205
  LuSplitPane: {
41
206
  default: "relative overflow-hidden",
42
207
  },
43
208
  LuSplitResizer: {
44
- default: "bg-gray-200 hover:bg-blue-400 transition-colors z-10",
209
+ default: "bg-gray-200 hover:bg-blue-400 transition-colors z-10 dark:bg-gray-800 dark:hover:bg-blue-500",
45
210
  horizontal: "w-1 cursor-col-resize",
46
211
  vertical: "h-1 cursor-row-resize",
47
212
  },
213
+ LuMobileShell: {
214
+ default: "flex flex-col h-full w-full overflow-hidden bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100",
215
+ },
216
+ LuMobileShellContent: {
217
+ default: "flex flex-1 flex-col overflow-hidden relative",
218
+ },
48
219
  LuMobileNavBar: {
49
- default: "flex items-center justify-around h-14 border-t border-gray-200 bg-white shrink-0 pb-safe",
220
+ default: "flex items-center justify-around w-full h-14 border-t border-gray-200 bg-white shrink-0 pb-safe dark:bg-gray-900 dark:border-gray-800",
50
221
  },
51
222
  LuMobileHeader: {
52
- default: "flex items-center h-14 border-b border-gray-200 bg-white px-4 shrink-0 pt-safe",
223
+ default: "flex items-center justify-between w-full h-14 border-b border-gray-200 bg-white px-4 shrink-0 pt-safe dark:bg-gray-900 dark:border-gray-800",
224
+ },
225
+ LuEmbeddedShell: {
226
+ default: "flex flex-col h-full w-full overflow-hidden bg-white text-gray-900 dark:bg-gray-950 dark:text-gray-100 select-none",
227
+ },
228
+ LuEmbeddedShellContent: {
229
+ default: "flex flex-1 w-full overflow-hidden relative",
230
+ },
231
+ LuEmbeddedTopBar: {
232
+ default: "flex items-center justify-between w-full h-16 bg-gray-900 text-white px-6 shrink-0",
233
+ },
234
+ LuEmbeddedStatusBar: {
235
+ default: "flex items-center justify-between w-full h-10 bg-gray-800 text-gray-300 px-6 text-sm shrink-0",
53
236
  },
54
237
  LuStack: {
55
238
  default: "flex",
56
- vertical: "flex-col",
57
- horizontal: "flex-row",
239
+ vertical: "flex flex-col",
240
+ horizontal: "flex flex-row",
241
+ container: "flex flex-col w-full max-w-4xl mx-auto min-h-full",
58
242
  },
59
243
  LuDock: {
60
244
  default: "flex flex-col h-full w-full overflow-hidden",
61
245
  vertical: "flex flex-col h-full w-full overflow-hidden",
62
246
  horizontal: "flex flex-row h-full w-full overflow-hidden",
63
247
  },
64
- LuDockContent: {
65
- default: "flex flex-1 overflow-hidden",
248
+ LuDockItem: {
249
+ default: "shrink-0",
250
+ top: "shrink-0",
251
+ bottom: "shrink-0",
252
+ left: "shrink-0 h-full",
253
+ right: "shrink-0 h-full",
254
+ fill: "flex-1 min-h-0 min-w-0 overflow-hidden",
66
255
  },
67
256
  LuFixed: {
68
257
  default: "shrink-0",
@@ -77,18 +266,161 @@ export const defaultSkin: SkinMap = {
77
266
  LuOverlay: {
78
267
  default: "absolute inset-0 z-50",
79
268
  },
80
- LuDockItem: {
81
- default: "shrink-0",
82
- top: "shrink-0",
83
- bottom: "shrink-0",
84
- left: "shrink-0 h-full",
85
- right: "shrink-0 h-full",
86
- fill: "flex-1 min-h-0 min-w-0 overflow-hidden",
87
- },
88
269
  LuGrid: {
89
270
  default: "grid",
271
+ "responsive-3": "grid grid-cols-1 md:grid-cols-3 lg:grid-cols-3",
90
272
  },
91
- LuMobileShellContent: {
92
- default: "flex flex-1 flex-col overflow-hidden relative",
273
+ LuAvatar: {
274
+ default: "relative inline-flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
275
+ sm: "h-8 w-8",
276
+ md: "h-10 w-10",
277
+ lg: "h-12 w-12",
278
+ xl: "h-16 w-16",
279
+ },
280
+ LuAvatarImage: {
281
+ default: "aspect-square h-full w-full object-cover",
282
+ },
283
+ LuAvatarFallback: {
284
+ default: "flex h-full w-full items-center justify-center rounded-full bg-gray-200 text-gray-700 text-sm font-medium dark:bg-gray-700 dark:text-gray-200",
285
+ },
286
+ LuBadge: {
287
+ default: "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300",
288
+ primary: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400",
289
+ secondary: "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300",
290
+ danger: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400",
291
+ success: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400",
292
+ warning: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400",
293
+ info: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400",
294
+ },
295
+ LuCard: {
296
+ default: "rounded-xl border border-gray-200 bg-white text-gray-950 shadow-sm p-6 dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50",
297
+ panel: "rounded-xl border border-gray-200 bg-white text-gray-950 shadow-sm p-8 dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50",
298
+ surface: "rounded-xl border border-gray-200 bg-white text-gray-950 shadow-sm overflow-hidden dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50",
299
+ footer: "border-t border-gray-200 bg-gray-50/50 dark:border-gray-800 dark:bg-gray-900/50 p-6",
300
+ },
301
+ LuDivider: {
302
+ default: "shrink-0 bg-gray-200 dark:bg-gray-800",
303
+ horizontal: "h-[1px] w-full",
304
+ vertical: "h-full w-[1px]",
305
+ },
306
+ LuSelect: {
307
+ default: "flex h-10 w-full items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-2 text-sm ring-offset-white placeholder:text-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-700 dark:bg-gray-900 dark:ring-offset-gray-950 dark:placeholder:text-gray-400",
308
+ theme: "w-32",
309
+ },
310
+ LuSwitch: {
311
+ default: "peer inline-flex h-6 w-11 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-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50 bg-gray-200 dark:bg-gray-700 dark:focus-visible:ring-offset-gray-950",
312
+ checked: "bg-blue-600 dark:bg-blue-500",
313
+ thumb: "pointer-events-none block h-5 w-5 rounded-full bg-white shadow-lg ring-0 transition-transform dark:bg-white",
314
+ thumbChecked: "translate-x-5",
315
+ thumbUnchecked: "translate-x-0",
316
+ theme: "peer inline-flex h-6 w-11 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-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50 bg-gray-200 dark:bg-gray-700 dark:focus-visible:ring-offset-gray-950",
317
+ },
318
+ LuSwitchThumb: {
319
+ default: "pointer-events-none block h-5 w-5 rounded-full bg-white shadow-lg ring-0 transition-transform dark:bg-white",
320
+ checked: "translate-x-5",
321
+ },
322
+ LuTable: {
323
+ default: "w-full caption-bottom text-sm",
324
+ },
325
+ LuTableHead: {
326
+ default: "border-b border-gray-200 dark:border-gray-800",
327
+ },
328
+ LuTableBody: {
329
+ default: "[&_tr:last-child]:border-0",
330
+ },
331
+ LuTableRow: {
332
+ default: "border-b border-gray-200 transition-colors hover:bg-gray-100/50 data-[state=selected]:bg-gray-100 dark:border-gray-800 dark:hover:bg-gray-800/50 dark:data-[state=selected]:bg-gray-800",
333
+ },
334
+ LuTableHeadCell: {
335
+ default: "h-12 px-4 text-left align-middle font-medium text-gray-500 dark:text-gray-400 [&:has([role=checkbox])]:pr-0",
336
+ },
337
+ LuTableCell: {
338
+ default: "p-4 align-middle [&:has([role=checkbox])]:pr-0",
339
+ },
340
+ LuTabs: {
341
+ default: "flex flex-col w-full",
342
+ },
343
+ LuTabList: {
344
+ default: "flex items-center gap-1 border-b border-gray-200 dark:border-gray-800",
345
+ "card-header": "flex items-center gap-1 w-full border-b border-gray-200 px-4 dark:border-gray-800",
346
+ },
347
+ LuTab: {
348
+ default: "relative px-3 py-2 text-sm font-medium text-gray-500 hover:text-gray-700 border-b-2 border-transparent -mb-px transition-colors duration-150 focus:outline-none cursor-pointer dark:text-gray-400 dark:hover:text-gray-200",
349
+ active: "text-blue-600 border-blue-600 dark:text-blue-400 dark:border-blue-400",
350
+ },
351
+ LuTabPanel: {
352
+ default: "pt-4 focus:outline-none",
353
+ },
354
+ LuTooltip: {
355
+ default: "relative inline-block",
356
+ },
357
+ LuTooltipContent: {
358
+ default: "absolute z-50 whitespace-nowrap overflow-hidden rounded-md border border-gray-200 bg-white px-3 py-1.5 text-sm text-gray-950 shadow-md animate-in fade-in-0 zoom-in-95 dark:border-gray-800 dark:bg-gray-950 dark:text-gray-50",
359
+ top: "bottom-full mb-2 left-1/2 -translate-x-1/2",
360
+ bottom: "top-full mt-2 left-1/2 -translate-x-1/2",
361
+ left: "right-full mr-2 top-1/2 -translate-y-1/2",
362
+ right: "left-full ml-2 top-1/2 -translate-y-1/2",
363
+ },
364
+ LuProgressBar: {
365
+ default: "h-2 w-full bg-gray-100 dark:bg-gray-800 rounded-full overflow-hidden",
366
+ primary: "bg-blue-100 dark:bg-blue-900/30",
367
+ success: "bg-green-100 dark:bg-green-900/30",
368
+ },
369
+ LuProgressBarFill: {
370
+ default: "h-full bg-gray-900 dark:bg-gray-50 transition-all duration-300 ease-in-out",
371
+ primary: "bg-blue-600 dark:bg-blue-500",
372
+ success: "bg-green-500 dark:bg-green-400",
373
+ },
374
+ LuCollapsible: {
375
+ default: "w-full",
376
+ },
377
+ LuCollapsibleTrigger: {
378
+ default: "w-full text-left flex items-center justify-between px-2 py-2 rounded text-sm font-medium text-gray-700 hover:bg-gray-100 transition-colors dark:text-gray-300 dark:hover:bg-gray-800 cursor-pointer focus:outline-none",
379
+ },
380
+ LuCollapsibleContent: {
381
+ default: "overflow-hidden",
382
+ },
383
+ LuLink: {
384
+ default: "inline-flex items-center gap-1 font-medium hover:underline underline-offset-4 hover:text-gray-600 dark:hover:text-gray-300 focus:outline-none",
385
+ nav: "inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
386
+ "nav-active": "inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-gray-50 focus:outline-none",
387
+ brand: "inline-flex items-center gap-1 font-medium text-gray-900 dark:text-gray-50 hover:opacity-80 transition-opacity focus:outline-none",
388
+ },
389
+ LuText: {
390
+ default: "text-gray-900 dark:text-gray-100",
391
+ "page-title": "text-4xl sm:text-5xl font-extrabold tracking-tight text-gray-950 dark:text-white",
392
+ "page-subtitle": "text-lg sm:text-xl text-gray-500 dark:text-gray-400 leading-relaxed max-w-[750px]",
393
+ "section-title": "text-2xl font-semibold tracking-tight text-gray-900 dark:text-gray-50",
394
+ "body": "text-base text-gray-600 dark:text-gray-300 leading-7",
395
+ "label": "text-xs font-semibold uppercase tracking-widest text-gray-500 dark:text-gray-400",
396
+ "muted": "text-sm text-gray-400 dark:text-gray-500",
397
+ "code": "font-mono text-sm border border-gray-200 dark:border-gray-800 bg-gray-100/50 text-gray-800 dark:bg-gray-800/50 dark:text-gray-200 px-1.5 py-0.5 rounded-md",
398
+ },
399
+ LuPageHeader: {
400
+ default: "mb-10 pb-8 border-b border-gray-200 dark:border-gray-800",
401
+ },
402
+ LuToggleGroup: {
403
+ default: "inline-flex items-center rounded-lg border border-gray-300 bg-gray-100 p-0.5 gap-0.5 shadow-sm dark:border-gray-600 dark:bg-gray-800",
404
+ },
405
+ LuToggleButton: {
406
+ default:
407
+ "inline-flex items-center justify-center rounded-md p-2 text-gray-500 transition-all duration-150 focus:outline-none hover:bg-white hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200",
408
+ active:
409
+ "bg-white text-gray-900 shadow-sm dark:bg-gray-600 dark:text-gray-100",
410
+ },
411
+ LuCodeBlock: {
412
+ container: "flex flex-col gap-4",
413
+ header: "flex flex-col gap-2",
414
+ title: "text-xl font-semibold tracking-tight text-gray-900 dark:text-gray-50",
415
+ description: "text-base text-gray-600 dark:text-gray-400",
416
+ card: "flex flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-950",
417
+ previewArea: "flex flex-col p-8 items-center justify-center min-h-[200px] w-full overflow-auto bg-gray-50 dark:bg-gray-900/50",
418
+ codeArea: "flex flex-col relative w-full",
419
+ splitCodeArea: "flex flex-col relative border-t md:border-t-0 md:border-l border-gray-200 dark:border-gray-800 w-full md:w-1/2 shrink-0",
420
+ splitContainer: "flex flex-col md:flex-row min-h-[300px]",
421
+ codeHeader: "flex items-center justify-between px-4 py-2 border-b border-gray-800 bg-[#282c34]",
422
+ badge: "text-xs font-mono font-medium text-gray-400 uppercase tracking-widest",
423
+ copyButton: "text-gray-400 hover:text-white transition-colors p-1.5 rounded-md hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-400",
424
+ codeContent: "p-4 overflow-x-auto text-sm bg-[#282c34] [&>pre]:!bg-transparent [&>pre]:!m-0 [&>pre]:!p-0",
93
425
  },
94
426
  };
@@ -0,0 +1,25 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+
4
+ // getLumoraSourceDir() — resolves the absolute path to @astrake/lumora-ui/src
5
+ //
6
+ // PRIMARY use: Tailwind CSS v4 via @tailwindcss/vite
7
+ // In your CSS entry file:
8
+ // @import "tailwindcss";
9
+ // @source "/absolute/path/from/getLumoraSourceDir()";
10
+ // Or use the @source directive with a relative path manually:
11
+ // @source "../../node_modules/@astrake/lumora-ui/src";
12
+ //
13
+ // NOTE: The framework ships lumora.css for all structural layout.
14
+ // You only need this helper if your skin strings use Tailwind utility classes.
15
+
16
+ export function getLumoraSourceDir(importerUrl?: string): string {
17
+ try {
18
+ const req = createRequire(importerUrl ?? import.meta.url);
19
+ const pkgRoot = path.dirname(req.resolve("@astrake/lumora-ui/package.json"));
20
+ return path.join(pkgRoot, "src");
21
+ } catch {
22
+ // Fallback: relative path from project root
23
+ return "./node_modules/@astrake/lumora-ui/src";
24
+ }
25
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,95 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ import type { SkinMap } from "./types";
4
+
5
+ /**
6
+ * Merges Tailwind classes intelligently
7
+ */
8
+ export function cn(...inputs: ClassValue[]) {
9
+ return twMerge(clsx(inputs));
10
+ }
11
+
12
+ /**
13
+ * Extends a base skin with a user skin, intelligently merging Tailwind classes
14
+ */
15
+ export function extendSkin(baseSkin: SkinMap, userSkin: SkinMap): SkinMap {
16
+ const merged: SkinMap = { ...baseSkin };
17
+
18
+ for (const componentName in userSkin) {
19
+ if (!merged[componentName]) {
20
+ merged[componentName] = { ...userSkin[componentName] };
21
+ continue;
22
+ }
23
+
24
+ const componentBase = merged[componentName];
25
+ const componentUser = userSkin[componentName];
26
+
27
+ const mergedComponent: any = { ...componentBase };
28
+
29
+ for (const variant in componentUser) {
30
+ if (componentBase[variant]) {
31
+ mergedComponent[variant] = cn(componentBase[variant], componentUser[variant]);
32
+ } else {
33
+ mergedComponent[variant] = componentUser[variant];
34
+ }
35
+ }
36
+
37
+ merged[componentName] = mergedComponent;
38
+ }
39
+
40
+ return merged;
41
+ }
42
+
43
+ /**
44
+ * Resolves standard layout props to Tailwind classes statically.
45
+ */
46
+ export function resolveLayoutProps(props: {
47
+ gap?: string | number,
48
+ padding?: string | number,
49
+ align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline',
50
+ justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly',
51
+ width?: string,
52
+ height?: string
53
+ }): string {
54
+ const classes: string[] = [];
55
+
56
+ if (props.gap) classes.push(`gap-${props.gap}`);
57
+ if (props.padding) classes.push(`p-${props.padding}`);
58
+
59
+ if (props.align) {
60
+ const alignMap: Record<string, string> = {
61
+ 'start': 'items-start',
62
+ 'center': 'items-center',
63
+ 'end': 'items-end',
64
+ 'stretch': 'items-stretch',
65
+ 'baseline': 'items-baseline'
66
+ };
67
+ if (alignMap[props.align]) classes.push(alignMap[props.align]);
68
+ }
69
+
70
+ if (props.justify) {
71
+ const justifyMap: Record<string, string> = {
72
+ 'start': 'justify-start',
73
+ 'center': 'justify-center',
74
+ 'end': 'justify-end',
75
+ 'between': 'justify-between',
76
+ 'around': 'justify-around',
77
+ 'evenly': 'justify-evenly'
78
+ };
79
+ if (justifyMap[props.justify]) classes.push(justifyMap[props.justify]);
80
+ }
81
+
82
+ if (props.width) {
83
+ if (props.width === 'full') classes.push('w-full');
84
+ else if (props.width === 'screen') classes.push('w-screen');
85
+ else classes.push(`w-${props.width}`);
86
+ }
87
+
88
+ if (props.height) {
89
+ if (props.height === 'full') classes.push('h-full');
90
+ else if (props.height === 'screen') classes.push('h-screen');
91
+ else classes.push(`h-${props.height}`);
92
+ }
93
+
94
+ return classes.join(' ');
95
+ }