@skewedaspect/sleekspace-ui 0.2.0-beta.1

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 (266) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/dist/sleekspace-ui.css +12844 -0
  4. package/dist/sleekspace-ui.es.js +19021 -0
  5. package/dist/sleekspace-ui.umd.js +19040 -0
  6. package/docs/components/accordion.md +92 -0
  7. package/docs/components/alert.md +72 -0
  8. package/docs/components/avatar.md +69 -0
  9. package/docs/components/breadcrumbs.md +65 -0
  10. package/docs/components/button/_meta.yaml +12 -0
  11. package/docs/components/button/accessibility.md +16 -0
  12. package/docs/components/button/custom-colors.md +18 -0
  13. package/docs/components/button/icons.md +31 -0
  14. package/docs/components/button/intro.md +8 -0
  15. package/docs/components/button/kinds.md +25 -0
  16. package/docs/components/button/sizes.md +14 -0
  17. package/docs/components/button/states.md +12 -0
  18. package/docs/components/button/usage.md +23 -0
  19. package/docs/components/button/variants.md +14 -0
  20. package/docs/components/button.md +110 -0
  21. package/docs/components/card.md +87 -0
  22. package/docs/components/checkbox.md +77 -0
  23. package/docs/components/collapsible.md +71 -0
  24. package/docs/components/divider.md +62 -0
  25. package/docs/components/dropdown.md +88 -0
  26. package/docs/components/field.md +80 -0
  27. package/docs/components/group.md +41 -0
  28. package/docs/components/input.md +84 -0
  29. package/docs/components/listbox.md +82 -0
  30. package/docs/components/modal.md +101 -0
  31. package/docs/components/navbar.md +64 -0
  32. package/docs/components/number-input.md +78 -0
  33. package/docs/components/page.md +77 -0
  34. package/docs/components/pagination.md +88 -0
  35. package/docs/components/panel.md +74 -0
  36. package/docs/components/popover.md +93 -0
  37. package/docs/components/progress.md +76 -0
  38. package/docs/components/radio.md +86 -0
  39. package/docs/components/sidebar.md +74 -0
  40. package/docs/components/skeleton.md +76 -0
  41. package/docs/components/slider.md +94 -0
  42. package/docs/components/spinner.md +59 -0
  43. package/docs/components/switch.md +97 -0
  44. package/docs/components/table.md +91 -0
  45. package/docs/components/tabs.md +108 -0
  46. package/docs/components/tag.md +75 -0
  47. package/docs/components/tags-input.md +88 -0
  48. package/docs/components/textarea.md +80 -0
  49. package/docs/components/theme.md +65 -0
  50. package/docs/components/toast.md +95 -0
  51. package/docs/components/tooltip.md +90 -0
  52. package/docs/guides/custom-colors.md +84 -0
  53. package/docs/guides/design-tokens.md +105 -0
  54. package/docs/guides/getting-started.md +144 -0
  55. package/docs/guides/installation.md +62 -0
  56. package/docs/guides/theming.md +101 -0
  57. package/package.json +76 -0
  58. package/src/components/Accordion/SkAccordion.vue +133 -0
  59. package/src/components/Accordion/SkAccordionItem.vue +131 -0
  60. package/src/components/Accordion/index.ts +3 -0
  61. package/src/components/Accordion/types.ts +9 -0
  62. package/src/components/Alert/SkAlert.vue +137 -0
  63. package/src/components/Alert/types.ts +10 -0
  64. package/src/components/Avatar/SkAvatar.vue +141 -0
  65. package/src/components/Avatar/index.ts +8 -0
  66. package/src/components/Avatar/types.ts +31 -0
  67. package/src/components/Breadcrumbs/SkBreadcrumbItem.vue +76 -0
  68. package/src/components/Breadcrumbs/SkBreadcrumbSeparator.vue +38 -0
  69. package/src/components/Breadcrumbs/SkBreadcrumbs.vue +93 -0
  70. package/src/components/Breadcrumbs/index.ts +10 -0
  71. package/src/components/Breadcrumbs/types.ts +36 -0
  72. package/src/components/Button/SkButton.vue +148 -0
  73. package/src/components/Button/types.ts +21 -0
  74. package/src/components/Card/SkCard.vue +144 -0
  75. package/src/components/Card/types.ts +12 -0
  76. package/src/components/Checkbox/SkCheckbox.vue +136 -0
  77. package/src/components/Checkbox/index.ts +8 -0
  78. package/src/components/Checkbox/types.ts +10 -0
  79. package/src/components/Collapsible/SkCollapsible.vue +159 -0
  80. package/src/components/Collapsible/index.ts +2 -0
  81. package/src/components/Collapsible/types.ts +8 -0
  82. package/src/components/Divider/SkDivider.vue +63 -0
  83. package/src/components/Divider/types.ts +15 -0
  84. package/src/components/Dropdown/SkDropdown.vue +150 -0
  85. package/src/components/Dropdown/SkDropdownMenuItem.vue +58 -0
  86. package/src/components/Dropdown/SkDropdownMenuSeparator.vue +26 -0
  87. package/src/components/Dropdown/SkDropdownSubmenu.vue +107 -0
  88. package/src/components/Dropdown/index.ts +11 -0
  89. package/src/components/Dropdown/types.ts +11 -0
  90. package/src/components/Field/SkField.vue +152 -0
  91. package/src/components/Field/index.ts +8 -0
  92. package/src/components/Field/types.ts +7 -0
  93. package/src/components/Group/SkGroup.vue +52 -0
  94. package/src/components/Group/types.ts +10 -0
  95. package/src/components/Input/SkInput.vue +117 -0
  96. package/src/components/Input/index.ts +8 -0
  97. package/src/components/Input/types.ts +11 -0
  98. package/src/components/Listbox/SkListbox.vue +164 -0
  99. package/src/components/Listbox/SkListboxItem.vue +68 -0
  100. package/src/components/Listbox/SkListboxSeparator.vue +26 -0
  101. package/src/components/Listbox/index.ts +10 -0
  102. package/src/components/Listbox/types.ts +10 -0
  103. package/src/components/Modal/SkModal.vue +231 -0
  104. package/src/components/Modal/index.ts +8 -0
  105. package/src/components/Modal/types.ts +12 -0
  106. package/src/components/NavBar/SkNavBar.vue +83 -0
  107. package/src/components/NavBar/index.ts +8 -0
  108. package/src/components/NavBar/types.ts +15 -0
  109. package/src/components/NumberInput/SkNumberInput.vue +168 -0
  110. package/src/components/NumberInput/index.ts +8 -0
  111. package/src/components/NumberInput/types.ts +10 -0
  112. package/src/components/Page/SkPage.vue +94 -0
  113. package/src/components/Page/index.ts +8 -0
  114. package/src/components/Page/types.ts +21 -0
  115. package/src/components/Pagination/SkPagination.vue +185 -0
  116. package/src/components/Pagination/SkPaginationItem.vue +107 -0
  117. package/src/components/Pagination/index.ts +9 -0
  118. package/src/components/Pagination/types.ts +40 -0
  119. package/src/components/Panel/SkPanel.vue +96 -0
  120. package/src/components/Panel/types.ts +15 -0
  121. package/src/components/Popover/SkPopover.vue +185 -0
  122. package/src/components/Popover/index.ts +8 -0
  123. package/src/components/Popover/types.ts +11 -0
  124. package/src/components/Progress/SkProgress.vue +144 -0
  125. package/src/components/Progress/index.ts +8 -0
  126. package/src/components/Progress/types.ts +34 -0
  127. package/src/components/Radio/SkRadio.vue +110 -0
  128. package/src/components/Radio/SkRadioGroup.vue +92 -0
  129. package/src/components/Radio/index.ts +9 -0
  130. package/src/components/Radio/types.ts +11 -0
  131. package/src/components/Sidebar/README.md +405 -0
  132. package/src/components/Sidebar/SkSidebar.vue +88 -0
  133. package/src/components/Sidebar/SkSidebarItem.vue +58 -0
  134. package/src/components/Sidebar/SkSidebarSection.vue +40 -0
  135. package/src/components/Sidebar/types.ts +3 -0
  136. package/src/components/Skeleton/SkSkeleton.vue +171 -0
  137. package/src/components/Skeleton/index.ts +8 -0
  138. package/src/components/Skeleton/types.ts +31 -0
  139. package/src/components/Slider/SkSlider.vue +165 -0
  140. package/src/components/Slider/index.ts +8 -0
  141. package/src/components/Slider/types.ts +44 -0
  142. package/src/components/Spinner/SkSpinner.vue +105 -0
  143. package/src/components/Spinner/index.ts +8 -0
  144. package/src/components/Spinner/types.ts +28 -0
  145. package/src/components/Switch/SkSwitch.vue +215 -0
  146. package/src/components/Switch/index.ts +8 -0
  147. package/src/components/Switch/types.ts +12 -0
  148. package/src/components/Table/SkTable.vue +109 -0
  149. package/src/components/Table/index.ts +2 -0
  150. package/src/components/Table/types.ts +15 -0
  151. package/src/components/Tabs/README.md +331 -0
  152. package/src/components/Tabs/SkTab.vue +84 -0
  153. package/src/components/Tabs/SkTabList.vue +62 -0
  154. package/src/components/Tabs/SkTabPanel.vue +47 -0
  155. package/src/components/Tabs/SkTabPanels.vue +23 -0
  156. package/src/components/Tabs/SkTabs.vue +124 -0
  157. package/src/components/Tabs/types.ts +21 -0
  158. package/src/components/Tag/SkTag.vue +129 -0
  159. package/src/components/Tag/types.ts +15 -0
  160. package/src/components/TagsInput/SkTagsInput.vue +184 -0
  161. package/src/components/TagsInput/index.ts +8 -0
  162. package/src/components/TagsInput/types.ts +10 -0
  163. package/src/components/Textarea/SkTextarea.vue +117 -0
  164. package/src/components/Textarea/index.ts +8 -0
  165. package/src/components/Textarea/types.ts +10 -0
  166. package/src/components/Theme/SkTheme.vue +47 -0
  167. package/src/components/Theme/types.ts +17 -0
  168. package/src/components/Theme/useTheme.ts +131 -0
  169. package/src/components/Toast/SkToast.vue +156 -0
  170. package/src/components/Toast/SkToastProvider.vue +180 -0
  171. package/src/components/Toast/index.ts +15 -0
  172. package/src/components/Toast/types.ts +63 -0
  173. package/src/components/Toast/useToast.ts +78 -0
  174. package/src/components/Tooltip/SkTooltip.vue +162 -0
  175. package/src/components/Tooltip/SkTooltipProvider.vue +114 -0
  176. package/src/components/Tooltip/index.ts +9 -0
  177. package/src/components/Tooltip/types.ts +13 -0
  178. package/src/composables/useCustomColors.test.ts +505 -0
  179. package/src/composables/useCustomColors.ts +124 -0
  180. package/src/composables/usePortalContext.test.ts +402 -0
  181. package/src/composables/usePortalContext.ts +95 -0
  182. package/src/global.d.ts +76 -0
  183. package/src/index.ts +259 -0
  184. package/src/styles/_scrollbar.scss +100 -0
  185. package/src/styles/base/_fonts.scss +105 -0
  186. package/src/styles/base/_global.scss +47 -0
  187. package/src/styles/base/_index.scss +24 -0
  188. package/src/styles/base/_reset.scss +11 -0
  189. package/src/styles/base/_typography.scss +178 -0
  190. package/src/styles/components/_accordion.scss +250 -0
  191. package/src/styles/components/_alert.scss +239 -0
  192. package/src/styles/components/_avatar.scss +133 -0
  193. package/src/styles/components/_breadcrumbs.scss +137 -0
  194. package/src/styles/components/_button.scss +731 -0
  195. package/src/styles/components/_card.scss +141 -0
  196. package/src/styles/components/_checkbox.scss +232 -0
  197. package/src/styles/components/_collapsible.scss +158 -0
  198. package/src/styles/components/_divider.scss +121 -0
  199. package/src/styles/components/_field.scss +87 -0
  200. package/src/styles/components/_group.scss +138 -0
  201. package/src/styles/components/_index.scss +46 -0
  202. package/src/styles/components/_input.scss +205 -0
  203. package/src/styles/components/_listbox.scss +453 -0
  204. package/src/styles/components/_menu.scss +216 -0
  205. package/src/styles/components/_modal.scss +329 -0
  206. package/src/styles/components/_navbar.scss +258 -0
  207. package/src/styles/components/_number-input.scss +352 -0
  208. package/src/styles/components/_page.scss +98 -0
  209. package/src/styles/components/_pagination.scss +411 -0
  210. package/src/styles/components/_panel.scss +281 -0
  211. package/src/styles/components/_popover.scss +258 -0
  212. package/src/styles/components/_progress.scss +280 -0
  213. package/src/styles/components/_radio.scss +255 -0
  214. package/src/styles/components/_sidebar.scss +92 -0
  215. package/src/styles/components/_skeleton.scss +138 -0
  216. package/src/styles/components/_slider.scss +262 -0
  217. package/src/styles/components/_spinner.scss +331 -0
  218. package/src/styles/components/_switch.scss +370 -0
  219. package/src/styles/components/_table.scss +405 -0
  220. package/src/styles/components/_tabs.scss +486 -0
  221. package/src/styles/components/_tag.scss +425 -0
  222. package/src/styles/components/_tags-input.scss +279 -0
  223. package/src/styles/components/_textarea.scss +208 -0
  224. package/src/styles/components/_toast.scss +331 -0
  225. package/src/styles/components/_tooltip.scss +206 -0
  226. package/src/styles/fonts/Titillium_Web/OFL.txt +93 -0
  227. package/src/styles/fonts/Titillium_Web/TitilliumWeb-Black.ttf +0 -0
  228. package/src/styles/fonts/Titillium_Web/TitilliumWeb-Bold.ttf +0 -0
  229. package/src/styles/fonts/Titillium_Web/TitilliumWeb-BoldItalic.ttf +0 -0
  230. package/src/styles/fonts/Titillium_Web/TitilliumWeb-ExtraLight.ttf +0 -0
  231. package/src/styles/fonts/Titillium_Web/TitilliumWeb-ExtraLightItalic.ttf +0 -0
  232. package/src/styles/fonts/Titillium_Web/TitilliumWeb-Italic.ttf +0 -0
  233. package/src/styles/fonts/Titillium_Web/TitilliumWeb-Light.ttf +0 -0
  234. package/src/styles/fonts/Titillium_Web/TitilliumWeb-LightItalic.ttf +0 -0
  235. package/src/styles/fonts/Titillium_Web/TitilliumWeb-Regular.ttf +0 -0
  236. package/src/styles/fonts/Titillium_Web/TitilliumWeb-SemiBold.ttf +0 -0
  237. package/src/styles/fonts/Titillium_Web/TitilliumWeb-SemiBoldItalic.ttf +0 -0
  238. package/src/styles/index.scss +17 -0
  239. package/src/styles/mixins/_cut-border.scss +254 -0
  240. package/src/styles/mixins/_index.scss +7 -0
  241. package/src/styles/theme/_variables.scss +42 -0
  242. package/src/styles/themes/README.md +127 -0
  243. package/src/styles/themes/_colorful.scss +58 -0
  244. package/src/styles/themes/_greyscale.scss +58 -0
  245. package/src/styles/themes/index.scss +9 -0
  246. package/src/styles/tokens/README.md +268 -0
  247. package/src/styles/tokens/_foundation-borders.scss +26 -0
  248. package/src/styles/tokens/_foundation-colors.scss +169 -0
  249. package/src/styles/tokens/_foundation-glow.scss +36 -0
  250. package/src/styles/tokens/_foundation-radius.scss +53 -0
  251. package/src/styles/tokens/_foundation-scrollbar.scss +31 -0
  252. package/src/styles/tokens/_foundation-shadows.scss +37 -0
  253. package/src/styles/tokens/_foundation-space.scss +36 -0
  254. package/src/styles/tokens/_foundation-transitions.scss +37 -0
  255. package/src/styles/tokens/_foundation-typography.scss +58 -0
  256. package/src/styles/tokens/_semantic-color-kinds.scss +112 -0
  257. package/src/styles/tokens/_semantic-colors.scss +10 -0
  258. package/src/styles/tokens/_semantic-interactive.scss +29 -0
  259. package/src/styles/tokens/_semantic-scrollbar.scss +48 -0
  260. package/src/styles/tokens/_semantic-surfaces.scss +36 -0
  261. package/src/styles/tokens/index.scss +38 -0
  262. package/src/styles/tokens.scss +268 -0
  263. package/src/styles/utilities/_index.scss +9 -0
  264. package/src/styles/utilities/_typography.scss +121 -0
  265. package/src/types.ts +50 -0
  266. package/web-types.json +3524 -0
@@ -0,0 +1,114 @@
1
+ <template>
2
+ <TooltipProvider
3
+ :delay-duration="delayDuration"
4
+ :skip-delay-duration="skipDelayDuration"
5
+ :disable-hoverable-content="disableHoverableContent"
6
+ :disable-closing-trigger="disableClosingTrigger"
7
+ :ignore-non-keyboard-focus="ignoreNonKeyboardFocus"
8
+ :disabled="disabled"
9
+ >
10
+ <slot />
11
+ </TooltipProvider>
12
+ </template>
13
+
14
+ <!--------------------------------------------------------------------------------------------------------------------->
15
+
16
+ <script setup lang="ts">
17
+ /**
18
+ * @component
19
+ * Provider component for tooltips that enables shared behavior across all child SkTooltip instances.
20
+ *
21
+ * When SkTooltip components are wrapped in SkTooltipProvider, they share a common context that enables:
22
+ * - **Skip delay**: Moving quickly between tooltips shows the next one immediately
23
+ * - **Shared configuration**: Set delay duration once for all tooltips
24
+ * - **Consistent behavior**: All tooltips behave as a coordinated group
25
+ *
26
+ * Without a provider, each SkTooltip creates its own isolated context and works independently.
27
+ * Both patterns are valid - use the provider when you want coordinated tooltip behavior.
28
+ */
29
+
30
+ import { provide } from 'vue';
31
+ import { TooltipProvider } from 'reka-ui';
32
+
33
+ //------------------------------------------------------------------------------------------------------------------
34
+
35
+ /**
36
+ * Provider component for tooltips that enables shared behavior.
37
+ *
38
+ * @example Basic usage - wrap your app or a section
39
+ * ```vue
40
+ * <SkTooltipProvider :delay-duration="300" :skip-delay-duration="100">
41
+ * <MyApp />
42
+ * </SkTooltipProvider>
43
+ * ```
44
+ *
45
+ * @example In App.vue for app-wide tooltip coordination
46
+ * ```vue
47
+ * <template>
48
+ * <SkTheme theme="colorful">
49
+ * <SkTooltipProvider>
50
+ * <RouterView />
51
+ * </SkTooltipProvider>
52
+ * </SkTheme>
53
+ * </template>
54
+ * ```
55
+ */
56
+ export interface SkTooltipProviderProps
57
+ {
58
+ /**
59
+ * The duration (ms) from when the pointer enters the trigger until the tooltip opens.
60
+ * @default 400
61
+ */
62
+ delayDuration ?: number;
63
+
64
+ /**
65
+ * How much time (ms) a user has to enter another trigger without incurring a delay again.
66
+ * This creates the "skip delay" behavior where moving between tooltips feels instant.
67
+ * @default 300
68
+ */
69
+ skipDelayDuration ?: number;
70
+
71
+ /**
72
+ * When true, hovering over the tooltip content will close it (pointer must stay on trigger).
73
+ * @default false
74
+ */
75
+ disableHoverableContent ?: boolean;
76
+
77
+ /**
78
+ * When true, clicking on the trigger will not close the tooltip.
79
+ * @default false
80
+ */
81
+ disableClosingTrigger ?: boolean;
82
+
83
+ /**
84
+ * When true, tooltips only open on keyboard focus (not mouse focus).
85
+ * Matches against :focus-visible selector.
86
+ * @default false
87
+ */
88
+ ignoreNonKeyboardFocus ?: boolean;
89
+
90
+ /**
91
+ * When true, disables all tooltips within this provider.
92
+ * @default false
93
+ */
94
+ disabled ?: boolean;
95
+ }
96
+
97
+ //------------------------------------------------------------------------------------------------------------------
98
+
99
+ withDefaults(defineProps<SkTooltipProviderProps>(), {
100
+ delayDuration: 400,
101
+ skipDelayDuration: 300,
102
+ disableHoverableContent: false,
103
+ disableClosingTrigger: false,
104
+ ignoreNonKeyboardFocus: false,
105
+ disabled: false,
106
+ });
107
+
108
+ //------------------------------------------------------------------------------------------------------------------
109
+
110
+ // Signal to child SkTooltip components that they're inside a provider
111
+ provide('sk-tooltip-provider', true);
112
+ </script>
113
+
114
+ <!--------------------------------------------------------------------------------------------------------------------->
@@ -0,0 +1,9 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Tooltip Component Exports
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ export { default as SkTooltip } from './SkTooltip.vue';
6
+ export { default as SkTooltipProvider } from './SkTooltipProvider.vue';
7
+ export type { SkTooltipKind, SkTooltipSide } from './types';
8
+
9
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,13 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Tooltip Component Types
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ import type { ComponentKind, ComponentVariant } from '@/types';
6
+
7
+ //----------------------------------------------------------------------------------------------------------------------
8
+
9
+ export type SkTooltipKind = ComponentKind;
10
+ export type SkTooltipVariant = ComponentVariant;
11
+ export type SkTooltipSide = 'top' | 'right' | 'bottom' | 'left';
12
+
13
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,505 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { nextTick, ref } from 'vue';
3
+ import { useCustomColors } from './useCustomColors';
4
+
5
+ //----------------------------------------------------------------------------------------------------------------------
6
+
7
+ describe('useCustomColors', () =>
8
+ {
9
+ describe('with no colors provided', () =>
10
+ {
11
+ it('should return empty styles object when baseColor is undefined', () =>
12
+ {
13
+ const result = useCustomColors('button', undefined, undefined);
14
+
15
+ expect(result.value).toEqual({});
16
+ });
17
+
18
+ it('should return empty styles object when baseColor ref is undefined', () =>
19
+ {
20
+ const baseColor = ref<string | undefined>(undefined);
21
+ const result = useCustomColors('button', baseColor, undefined);
22
+
23
+ expect(result.value).toEqual({});
24
+ });
25
+ });
26
+
27
+ describe('with baseColor only', () =>
28
+ {
29
+ it('should generate base color variable with OKLCH value', () =>
30
+ {
31
+ const result = useCustomColors('button', 'oklch(0.7 0.25 300)', undefined);
32
+
33
+ expect(result.value).toEqual({
34
+ '--sk-button-color-base': 'oklch(0.7 0.25 300)',
35
+ '--sk-button-fg': 'var(--sk-neutral-text)',
36
+ });
37
+ });
38
+
39
+ it('should generate base color variable with hex value', () =>
40
+ {
41
+ const result = useCustomColors('panel', '#FF6B6B', undefined);
42
+
43
+ expect(result.value).toEqual({
44
+ '--sk-panel-color-base': '#FF6B6B',
45
+ '--sk-panel-fg': 'var(--sk-neutral-text)',
46
+ });
47
+ });
48
+
49
+ it('should generate base color variable with CSS variable', () =>
50
+ {
51
+ const result = useCustomColors('alert', 'var(--my-custom-color)', undefined);
52
+
53
+ expect(result.value).toEqual({
54
+ '--sk-alert-color-base': 'var(--my-custom-color)',
55
+ '--sk-alert-fg': 'var(--sk-neutral-text)',
56
+ });
57
+ });
58
+
59
+ it('should fallback to --sk-neutral-text when textColor not provided', () =>
60
+ {
61
+ const result = useCustomColors('tag', 'oklch(0.65 0.2 150)', undefined);
62
+
63
+ expect(result.value['--sk-tag-fg']).toBe('var(--sk-neutral-text)');
64
+ });
65
+ });
66
+
67
+ describe('with both baseColor and textColor', () =>
68
+ {
69
+ it('should generate both color variables', () =>
70
+ {
71
+ const result = useCustomColors(
72
+ 'button',
73
+ 'oklch(0.7 0.25 300)',
74
+ 'white'
75
+ );
76
+
77
+ expect(result.value).toEqual({
78
+ '--sk-button-color-base': 'oklch(0.7 0.25 300)',
79
+ '--sk-button-fg': 'white',
80
+ });
81
+ });
82
+
83
+ it('should use provided textColor instead of fallback', () =>
84
+ {
85
+ const result = useCustomColors(
86
+ 'panel',
87
+ 'oklch(0.3 0.15 260)',
88
+ 'oklch(0.85 0.05 260)'
89
+ );
90
+
91
+ expect(result.value).toEqual({
92
+ '--sk-panel-color-base': 'oklch(0.3 0.15 260)',
93
+ '--sk-panel-fg': 'oklch(0.85 0.05 260)',
94
+ });
95
+ });
96
+
97
+ it('should work with hex colors for both values', () =>
98
+ {
99
+ const result = useCustomColors('alert', '#10B981', '#FFFFFF');
100
+
101
+ expect(result.value).toEqual({
102
+ '--sk-alert-color-base': '#10B981',
103
+ '--sk-alert-fg': '#FFFFFF',
104
+ });
105
+ });
106
+ });
107
+
108
+ describe('with reactive refs', () =>
109
+ {
110
+ it('should react to baseColor changes', async () =>
111
+ {
112
+ const baseColor = ref('oklch(0.7 0.25 300)');
113
+ const result = useCustomColors('button', baseColor, undefined);
114
+
115
+ expect(result.value['--sk-button-color-base']).toBe('oklch(0.7 0.25 300)');
116
+
117
+ baseColor.value = 'oklch(0.6 0.2 150)';
118
+ await nextTick();
119
+
120
+ expect(result.value['--sk-button-color-base']).toBe('oklch(0.6 0.2 150)');
121
+ });
122
+
123
+ it('should react to textColor changes', async () =>
124
+ {
125
+ const baseColor = ref('oklch(0.7 0.25 300)');
126
+ const textColor = ref('white');
127
+ const result = useCustomColors('button', baseColor, textColor);
128
+
129
+ expect(result.value['--sk-button-fg']).toBe('white');
130
+
131
+ textColor.value = 'black';
132
+ await nextTick();
133
+
134
+ expect(result.value['--sk-button-fg']).toBe('black');
135
+ });
136
+
137
+ it('should return empty object when baseColor ref changes to undefined', async () =>
138
+ {
139
+ const baseColor = ref<string | undefined>('oklch(0.7 0.25 300)');
140
+ const result = useCustomColors('button', baseColor, undefined);
141
+
142
+ expect(result.value).toEqual({
143
+ '--sk-button-color-base': 'oklch(0.7 0.25 300)',
144
+ '--sk-button-fg': 'var(--sk-neutral-text)',
145
+ });
146
+
147
+ baseColor.value = undefined;
148
+ await nextTick();
149
+
150
+ expect(result.value).toEqual({});
151
+ });
152
+
153
+ it('should fallback to neutral when textColor ref changes to undefined', async () =>
154
+ {
155
+ const baseColor = ref('oklch(0.7 0.25 300)');
156
+ const textColor = ref<string | undefined>('white');
157
+ const result = useCustomColors('button', baseColor, textColor);
158
+
159
+ expect(result.value['--sk-button-fg']).toBe('white');
160
+
161
+ textColor.value = undefined;
162
+ await nextTick();
163
+
164
+ expect(result.value['--sk-button-fg']).toBe('var(--sk-neutral-text)');
165
+ });
166
+ });
167
+
168
+ describe('component name flexibility', () =>
169
+ {
170
+ it('should work with any component name', () =>
171
+ {
172
+ const result = useCustomColors('my-custom-widget', 'oklch(0.7 0.25 45)', 'white');
173
+
174
+ expect(result.value).toEqual({
175
+ '--sk-my-custom-widget-color-base': 'oklch(0.7 0.25 45)',
176
+ '--sk-my-custom-widget-fg': 'white',
177
+ });
178
+ });
179
+
180
+ it('should work with kebab-case names', () =>
181
+ {
182
+ const result = useCustomColors('custom-component', '#FF0000', '#FFFFFF');
183
+
184
+ expect(result.value).toEqual({
185
+ '--sk-custom-component-color-base': '#FF0000',
186
+ '--sk-custom-component-fg': '#FFFFFF',
187
+ });
188
+ });
189
+
190
+ it('should work with all built-in component names', () =>
191
+ {
192
+ const components = [
193
+ 'button',
194
+ 'panel',
195
+ 'navbar',
196
+ 'table',
197
+ 'alert',
198
+ 'tag',
199
+ 'card',
200
+ 'switch',
201
+ 'tabs',
202
+ 'sidebar',
203
+ ];
204
+
205
+ components.forEach((component) =>
206
+ {
207
+ const result = useCustomColors(component, 'oklch(0.7 0.25 300)', 'white');
208
+
209
+ expect(result.value).toEqual({
210
+ [`--sk-${ component }-color-base`]: 'oklch(0.7 0.25 300)',
211
+ [`--sk-${ component }-fg`]: 'white',
212
+ });
213
+ });
214
+ });
215
+ });
216
+
217
+ describe('CSS color format support', () =>
218
+ {
219
+ it('should support hex colors', () =>
220
+ {
221
+ const result = useCustomColors('button', '#FF6B6B', '#FFFFFF');
222
+
223
+ expect(result.value['--sk-button-color-base']).toBe('#FF6B6B');
224
+ expect(result.value['--sk-button-fg']).toBe('#FFFFFF');
225
+ });
226
+
227
+ it('should support RGB colors', () =>
228
+ {
229
+ const result = useCustomColors('button', 'rgb(255, 107, 107)', 'rgb(255, 255, 255)');
230
+
231
+ expect(result.value['--sk-button-color-base']).toBe('rgb(255, 107, 107)');
232
+ expect(result.value['--sk-button-fg']).toBe('rgb(255, 255, 255)');
233
+ });
234
+
235
+ it('should support HSL colors', () =>
236
+ {
237
+ const result = useCustomColors('button', 'hsl(0, 100%, 71%)', 'hsl(0, 0%, 100%)');
238
+
239
+ expect(result.value['--sk-button-color-base']).toBe('hsl(0, 100%, 71%)');
240
+ expect(result.value['--sk-button-fg']).toBe('hsl(0, 0%, 100%)');
241
+ });
242
+
243
+ it('should support OKLCH colors', () =>
244
+ {
245
+ const result = useCustomColors('button', 'oklch(0.7 0.25 300)', 'oklch(0.95 0.05 300)');
246
+
247
+ expect(result.value['--sk-button-color-base']).toBe('oklch(0.7 0.25 300)');
248
+ expect(result.value['--sk-button-fg']).toBe('oklch(0.95 0.05 300)');
249
+ });
250
+
251
+ it('should support named colors', () =>
252
+ {
253
+ const result = useCustomColors('button', 'tomato', 'white');
254
+
255
+ expect(result.value['--sk-button-color-base']).toBe('tomato');
256
+ expect(result.value['--sk-button-fg']).toBe('white');
257
+ });
258
+
259
+ it('should support CSS variables', () =>
260
+ {
261
+ const result = useCustomColors(
262
+ 'button',
263
+ 'var(--my-brand-color)',
264
+ 'var(--my-text-color)'
265
+ );
266
+
267
+ expect(result.value['--sk-button-color-base']).toBe('var(--my-brand-color)');
268
+ expect(result.value['--sk-button-fg']).toBe('var(--my-text-color)');
269
+ });
270
+
271
+ it('should support color-mix functions', () =>
272
+ {
273
+ const result = useCustomColors(
274
+ 'button',
275
+ 'color-mix(in oklch, red, blue)',
276
+ 'white'
277
+ );
278
+
279
+ expect(result.value['--sk-button-color-base']).toBe('color-mix(in oklch, red, blue)');
280
+ });
281
+ });
282
+
283
+ describe('mixed ref and string parameters', () =>
284
+ {
285
+ it('should work with string baseColor and ref textColor', async () =>
286
+ {
287
+ const textColor = ref('white');
288
+ const result = useCustomColors('button', 'oklch(0.7 0.25 300)', textColor);
289
+
290
+ expect(result.value).toEqual({
291
+ '--sk-button-color-base': 'oklch(0.7 0.25 300)',
292
+ '--sk-button-fg': 'white',
293
+ });
294
+
295
+ textColor.value = 'black';
296
+ await nextTick();
297
+
298
+ expect(result.value['--sk-button-fg']).toBe('black');
299
+ });
300
+
301
+ it('should work with ref baseColor and string textColor', async () =>
302
+ {
303
+ const baseColor = ref('oklch(0.7 0.25 300)');
304
+ const result = useCustomColors('button', baseColor, 'white');
305
+
306
+ expect(result.value).toEqual({
307
+ '--sk-button-color-base': 'oklch(0.7 0.25 300)',
308
+ '--sk-button-fg': 'white',
309
+ });
310
+
311
+ baseColor.value = 'oklch(0.6 0.2 150)';
312
+ await nextTick();
313
+
314
+ expect(result.value['--sk-button-color-base']).toBe('oklch(0.6 0.2 150)');
315
+ });
316
+
317
+ it('should work with both refs', async () =>
318
+ {
319
+ const baseColor = ref('oklch(0.7 0.25 300)');
320
+ const textColor = ref('white');
321
+ const result = useCustomColors('button', baseColor, textColor);
322
+
323
+ expect(result.value).toEqual({
324
+ '--sk-button-color-base': 'oklch(0.7 0.25 300)',
325
+ '--sk-button-fg': 'white',
326
+ });
327
+
328
+ baseColor.value = 'oklch(0.6 0.2 150)';
329
+ textColor.value = 'black';
330
+ await nextTick();
331
+
332
+ expect(result.value).toEqual({
333
+ '--sk-button-color-base': 'oklch(0.6 0.2 150)',
334
+ '--sk-button-fg': 'black',
335
+ });
336
+ });
337
+
338
+ it('should work with both strings', () =>
339
+ {
340
+ const result = useCustomColors('button', 'oklch(0.7 0.25 300)', 'white');
341
+
342
+ expect(result.value).toEqual({
343
+ '--sk-button-color-base': 'oklch(0.7 0.25 300)',
344
+ '--sk-button-fg': 'white',
345
+ });
346
+ });
347
+ });
348
+
349
+ describe('edge cases', () =>
350
+ {
351
+ it('should handle empty string baseColor as falsy', () =>
352
+ {
353
+ const result = useCustomColors('button', '', undefined);
354
+
355
+ expect(result.value).toEqual({});
356
+ });
357
+
358
+ it('should handle empty string textColor', () =>
359
+ {
360
+ const result = useCustomColors('button', 'oklch(0.7 0.25 300)', '');
361
+
362
+ // Empty string is falsy for textColor, should use fallback
363
+ expect(result.value).toEqual({
364
+ '--sk-button-color-base': 'oklch(0.7 0.25 300)',
365
+ '--sk-button-fg': 'var(--sk-neutral-text)',
366
+ });
367
+ });
368
+
369
+ it('should work with component names containing numbers', () =>
370
+ {
371
+ const result = useCustomColors('widget-v2', 'oklch(0.7 0.25 300)', 'white');
372
+
373
+ expect(result.value).toEqual({
374
+ '--sk-widget-v2-color-base': 'oklch(0.7 0.25 300)',
375
+ '--sk-widget-v2-fg': 'white',
376
+ });
377
+ });
378
+
379
+ it('should preserve exact color values without modification', () =>
380
+ {
381
+ const complexColor = 'oklch(from var(--base) calc(l * 0.9) c h)';
382
+ const result = useCustomColors('button', complexColor, 'white');
383
+
384
+ expect(result.value['--sk-button-color-base']).toBe(complexColor);
385
+ });
386
+ });
387
+
388
+ describe('reactivity', () =>
389
+ {
390
+ it('should recompute when reactive baseColor changes', async () =>
391
+ {
392
+ const baseColor = ref('oklch(0.7 0.25 300)');
393
+ const result = useCustomColors('button', baseColor, 'white');
394
+
395
+ const initialValue = result.value;
396
+ expect(initialValue['--sk-button-color-base']).toBe('oklch(0.7 0.25 300)');
397
+
398
+ baseColor.value = 'oklch(0.8 0.3 150)';
399
+ await nextTick();
400
+
401
+ const updatedValue = result.value;
402
+ expect(updatedValue['--sk-button-color-base']).toBe('oklch(0.8 0.3 150)');
403
+
404
+ // Should generate new object (computed reactivity)
405
+ expect(updatedValue).not.toBe(initialValue);
406
+ });
407
+
408
+ it('should recompute when reactive textColor changes', async () =>
409
+ {
410
+ const textColor = ref('white');
411
+ const result = useCustomColors('button', 'oklch(0.7 0.25 300)', textColor);
412
+
413
+ expect(result.value['--sk-button-fg']).toBe('white');
414
+
415
+ textColor.value = 'oklch(0.2 0.05 300)';
416
+ await nextTick();
417
+
418
+ expect(result.value['--sk-button-fg']).toBe('oklch(0.2 0.05 300)');
419
+ });
420
+
421
+ it('should recompute when both reactive colors change', async () =>
422
+ {
423
+ const baseColor = ref('oklch(0.7 0.25 300)');
424
+ const textColor = ref('white');
425
+ const result = useCustomColors('button', baseColor, textColor);
426
+
427
+ baseColor.value = 'oklch(0.8 0.3 150)';
428
+ textColor.value = 'black';
429
+ await nextTick();
430
+
431
+ expect(result.value).toEqual({
432
+ '--sk-button-color-base': 'oklch(0.8 0.3 150)',
433
+ '--sk-button-fg': 'black',
434
+ });
435
+ });
436
+ });
437
+
438
+ describe('multiple component instances', () =>
439
+ {
440
+ it('should generate unique variables for different components', () =>
441
+ {
442
+ const button = useCustomColors('button', 'oklch(0.7 0.25 300)', 'white');
443
+ const panel = useCustomColors('panel', 'oklch(0.6 0.2 150)', 'black');
444
+
445
+ expect(button.value).toEqual({
446
+ '--sk-button-color-base': 'oklch(0.7 0.25 300)',
447
+ '--sk-button-fg': 'white',
448
+ });
449
+
450
+ expect(panel.value).toEqual({
451
+ '--sk-panel-color-base': 'oklch(0.6 0.2 150)',
452
+ '--sk-panel-fg': 'black',
453
+ });
454
+ });
455
+
456
+ it('should not interfere with each other when using refs', async () =>
457
+ {
458
+ const buttonBase = ref('oklch(0.7 0.25 300)');
459
+ const panelBase = ref('oklch(0.6 0.2 150)');
460
+
461
+ const button = useCustomColors('button', buttonBase, 'white');
462
+ const panel = useCustomColors('panel', panelBase, 'black');
463
+
464
+ buttonBase.value = 'oklch(0.8 0.3 45)';
465
+ await nextTick();
466
+
467
+ // Button should update
468
+ expect(button.value['--sk-button-color-base']).toBe('oklch(0.8 0.3 45)');
469
+
470
+ // Panel should remain unchanged
471
+ expect(panel.value['--sk-panel-color-base']).toBe('oklch(0.6 0.2 150)');
472
+ });
473
+ });
474
+
475
+ describe('type coercion', () =>
476
+ {
477
+ it('should handle Ref<string | undefined> correctly', () =>
478
+ {
479
+ const baseColor = ref<string | undefined>('oklch(0.7 0.25 300)');
480
+ const textColor = ref<string | undefined>('white');
481
+
482
+ const result = useCustomColors('button', baseColor, textColor);
483
+
484
+ expect(result.value).toEqual({
485
+ '--sk-button-color-base': 'oklch(0.7 0.25 300)',
486
+ '--sk-button-fg': 'white',
487
+ });
488
+ });
489
+
490
+ it('should handle string | undefined correctly', () =>
491
+ {
492
+ const baseColor : string | undefined = 'oklch(0.7 0.25 300)';
493
+ const textColor : string | undefined = 'white';
494
+
495
+ const result = useCustomColors('button', baseColor, textColor);
496
+
497
+ expect(result.value).toEqual({
498
+ '--sk-button-color-base': 'oklch(0.7 0.25 300)',
499
+ '--sk-button-fg': 'white',
500
+ });
501
+ });
502
+ });
503
+ });
504
+
505
+ //----------------------------------------------------------------------------------------------------------------------