@skewedaspect/sleekspace-ui 0.8.1 → 0.9.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 (177) hide show
  1. package/dist/components/Dropdown/SkDropdown.vue.d.ts +9 -1
  2. package/dist/components/Dropdown/types.d.ts +2 -1
  3. package/dist/components/NavBar/SkNavBar.vue.d.ts +9 -1
  4. package/dist/components/NavBar/context.d.ts +2 -0
  5. package/dist/components/NavBar/types.d.ts +5 -1
  6. package/dist/components/Page/SkPage.vue.d.ts +9 -0
  7. package/dist/components/ScrollArea/SkScrollArea.vue.d.ts +105 -4
  8. package/dist/composables/useCustomColors.d.ts +18 -56
  9. package/{src → dist}/global.d.ts +6 -2
  10. package/dist/sleekspace-ui.css +4253 -1251
  11. package/dist/sleekspace-ui.es.js +204 -109
  12. package/dist/sleekspace-ui.umd.js +204 -109
  13. package/dist/static/classes.d.ts +18 -0
  14. package/dist/static/components/alert.d.ts +12 -0
  15. package/dist/static/components/avatar.d.ts +9 -0
  16. package/dist/static/components/breadcrumbs.d.ts +6 -0
  17. package/dist/static/components/button.d.ts +13 -0
  18. package/dist/static/components/card.d.ts +5 -0
  19. package/dist/static/components/checkbox.d.ts +10 -0
  20. package/dist/static/components/colorPicker.d.ts +8 -0
  21. package/dist/static/components/divider.d.ts +8 -0
  22. package/dist/static/components/dropdown.d.ts +8 -0
  23. package/dist/static/components/field.d.ts +15 -0
  24. package/dist/static/components/group.d.ts +5 -0
  25. package/dist/static/components/input.d.ts +14 -0
  26. package/dist/static/components/navBar.d.ts +16 -0
  27. package/dist/static/components/numberInput.d.ts +15 -0
  28. package/dist/static/components/page.d.ts +9 -0
  29. package/dist/static/components/pagination.d.ts +5 -0
  30. package/dist/static/components/panel.d.ts +11 -0
  31. package/dist/static/components/progress.d.ts +9 -0
  32. package/dist/static/components/radio.d.ts +11 -0
  33. package/dist/static/components/select.d.ts +10 -0
  34. package/dist/static/components/sidebar.d.ts +9 -0
  35. package/dist/static/components/skeleton.d.ts +11 -0
  36. package/dist/static/components/slider.d.ts +12 -0
  37. package/dist/static/components/spinner.d.ts +12 -0
  38. package/dist/static/components/switchInput.d.ts +10 -0
  39. package/dist/static/components/table.d.ts +12 -0
  40. package/dist/static/components/tag.d.ts +8 -0
  41. package/dist/static/components/tagsInput.d.ts +7 -0
  42. package/dist/static/components/textarea.d.ts +12 -0
  43. package/dist/static/components/toolbar.d.ts +12 -0
  44. package/dist/static/components/tooltip.d.ts +7 -0
  45. package/dist/static/escape.d.ts +2 -0
  46. package/dist/static/index.cjs.js +1 -0
  47. package/dist/static/index.d.ts +68 -0
  48. package/dist/static/index.es.js +732 -0
  49. package/dist/static/render.d.ts +12 -0
  50. package/dist/static/specs.d.ts +2 -0
  51. package/dist/static/types.d.ts +43 -0
  52. package/dist/tokens.css +322 -0
  53. package/dist/types/index.d.ts +36 -0
  54. package/docs/guides/installation.md +8 -2
  55. package/docs/guides/pure-css/_meta.yaml +8 -0
  56. package/docs/guides/pure-css/class-api.md +1070 -0
  57. package/docs/guides/pure-css/custom-elements.md +574 -0
  58. package/docs/guides/pure-css/index.md +86 -0
  59. package/docs/guides/pure-css/limitations.md +152 -0
  60. package/docs/guides/pure-css/static-helpers.md +1203 -0
  61. package/llms-full.txt +3736 -261
  62. package/package.json +16 -5
  63. package/src/components/Card/SkCard.vue +1 -0
  64. package/src/components/ContextMenu/SkContextMenuRadioGroup.vue +4 -1
  65. package/src/components/Dropdown/SkDropdown.vue +20 -3
  66. package/src/components/Dropdown/SkDropdownRadioGroup.vue +4 -1
  67. package/src/components/Dropdown/types.ts +2 -1
  68. package/src/components/NavBar/SkNavBar.vue +14 -4
  69. package/src/components/NavBar/context.ts +4 -2
  70. package/src/components/NavBar/types.ts +6 -1
  71. package/src/components/Page/SkPage.vue +11 -0
  72. package/src/components/Panel/SkPanel.vue +2 -1
  73. package/src/components/ScrollArea/SkScrollArea.vue +78 -5
  74. package/src/components/TreeView/SkTreeView.vue +7 -2
  75. package/src/composables/useCustomColors.ts +86 -77
  76. package/src/composables/usePortalContext.test.ts +0 -2
  77. package/src/shims.d.ts +10 -0
  78. package/src/static/__tests__/parity.test.ts +717 -0
  79. package/src/static/__tests__/parityHarness.test.ts +98 -0
  80. package/src/static/__tests__/parityHarness.ts +260 -0
  81. package/src/static/classes.test.ts +82 -0
  82. package/src/static/classes.ts +111 -0
  83. package/src/static/components/__tests__/helpers.test.ts +837 -0
  84. package/src/static/components/alert.ts +117 -0
  85. package/src/static/components/avatar.ts +86 -0
  86. package/src/static/components/breadcrumbs.ts +28 -0
  87. package/src/static/components/button.ts +75 -0
  88. package/src/static/components/card.ts +27 -0
  89. package/src/static/components/checkbox.ts +48 -0
  90. package/src/static/components/colorPicker.ts +45 -0
  91. package/src/static/components/divider.ts +39 -0
  92. package/src/static/components/dropdown.ts +36 -0
  93. package/src/static/components/field.ts +86 -0
  94. package/src/static/components/group.ts +27 -0
  95. package/src/static/components/input.ts +55 -0
  96. package/src/static/components/navBar.ts +94 -0
  97. package/src/static/components/numberInput.ts +64 -0
  98. package/src/static/components/page.ts +31 -0
  99. package/src/static/components/pagination.ts +27 -0
  100. package/src/static/components/panel.ts +33 -0
  101. package/src/static/components/progress.ts +31 -0
  102. package/src/static/components/radio.ts +53 -0
  103. package/src/static/components/select.ts +51 -0
  104. package/src/static/components/sidebar.ts +85 -0
  105. package/src/static/components/skeleton.ts +66 -0
  106. package/src/static/components/slider.ts +50 -0
  107. package/src/static/components/spinner.ts +94 -0
  108. package/src/static/components/switchInput.ts +49 -0
  109. package/src/static/components/table.ts +88 -0
  110. package/src/static/components/tag.ts +76 -0
  111. package/src/static/components/tagsInput.ts +35 -0
  112. package/src/static/components/textarea.ts +53 -0
  113. package/src/static/components/toolbar.ts +74 -0
  114. package/src/static/components/tooltip.ts +29 -0
  115. package/src/static/escape.test.ts +53 -0
  116. package/src/static/escape.ts +28 -0
  117. package/src/static/generated/defaults.ts +378 -0
  118. package/src/static/generated/propTypes.ts +425 -0
  119. package/src/static/index.ts +116 -0
  120. package/src/static/render.test.ts +83 -0
  121. package/src/static/render.ts +76 -0
  122. package/src/static/specs.test.ts +58 -0
  123. package/src/static/specs.ts +230 -0
  124. package/src/static/types.ts +176 -0
  125. package/src/styles/__tests__/testHelpers.ts +97 -0
  126. package/src/styles/base/_custom-elements.scss +51 -0
  127. package/src/styles/base/_index.scss +4 -0
  128. package/src/styles/components/__tests__/componentSelectors.test.ts +2575 -0
  129. package/src/styles/components/_alert.scss +82 -39
  130. package/src/styles/components/_avatar.scss +102 -47
  131. package/src/styles/components/_breadcrumbs.scss +39 -37
  132. package/src/styles/components/_button.scss +58 -5
  133. package/src/styles/components/_card.scss +64 -2
  134. package/src/styles/components/_checkbox.scss +35 -5
  135. package/src/styles/components/_color-picker.scss +48 -13
  136. package/src/styles/components/_divider.scss +86 -52
  137. package/src/styles/components/_dropdown.scss +214 -0
  138. package/src/styles/components/_field.scss +76 -23
  139. package/src/styles/components/_group.scss +190 -79
  140. package/src/styles/components/_index.scss +1 -0
  141. package/src/styles/components/_input.scss +81 -5
  142. package/src/styles/components/_menu.scss +1 -1
  143. package/src/styles/components/_navbar.scss +76 -45
  144. package/src/styles/components/_number-input.scss +88 -83
  145. package/src/styles/components/_page.scss +82 -23
  146. package/src/styles/components/_pagination.scss +240 -212
  147. package/src/styles/components/_panel.scss +268 -122
  148. package/src/styles/components/_progress.scss +120 -70
  149. package/src/styles/components/_radio.scss +35 -5
  150. package/src/styles/components/_scroll-area.scss +50 -22
  151. package/src/styles/components/_select.scss +40 -9
  152. package/src/styles/components/_sidebar.scss +59 -34
  153. package/src/styles/components/_skeleton.scss +111 -65
  154. package/src/styles/components/_slider.scss +34 -10
  155. package/src/styles/components/_spinner.scss +107 -56
  156. package/src/styles/components/_switch.scss +36 -5
  157. package/src/styles/components/_table.scss +150 -166
  158. package/src/styles/components/_tag.scss +244 -154
  159. package/src/styles/components/_tags-input.scss +46 -12
  160. package/src/styles/components/_textarea.scss +36 -5
  161. package/src/styles/components/_toolbar.scss +85 -31
  162. package/src/styles/components/_tooltip.scss +172 -3
  163. package/src/styles/mixins/_cut-border.scss +18 -4
  164. package/src/styles/mixins/_dual-selector.scss +192 -0
  165. package/src/styles/mixins/_index.scss +1 -0
  166. package/src/styles/mixins/dualSelector.test.ts +151 -0
  167. package/src/styles/themes/_colorful.scss +25 -0
  168. package/src/styles/themes/_greyscale.scss +25 -0
  169. package/src/styles/themes/_shade-scale.scss +39 -0
  170. package/src/styles/tokens/_semantic-color-kinds.scss +66 -0
  171. package/src/{types.ts → types/index.ts} +19 -11
  172. package/web-types.json +970 -137
  173. package/dist/composables/useCustomColors.test.d.ts +0 -1
  174. package/dist/composables/useFocusTrap.test.d.ts +0 -1
  175. package/dist/composables/usePortalContext.test.d.ts +0 -1
  176. package/dist/styles/mixins/fluidSize.test.d.ts +0 -1
  177. package/dist/types.d.ts +0 -29
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skewedaspect/sleekspace-ui",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "A Vue 3 component library with a cyberpunk aesthetic, featuring OKLCH colors, beveled corners, and a powerful design token system",
5
5
  "type": "module",
6
6
  "main": "dist/sleekspace-ui.umd.js",
@@ -13,10 +13,19 @@
13
13
  "import": "./dist/sleekspace-ui.es.js",
14
14
  "require": "./dist/sleekspace-ui.umd.js"
15
15
  },
16
+ "./global": {
17
+ "types": "./dist/global.d.ts"
18
+ },
16
19
  "./style": "./dist/sleekspace-ui.css",
20
+ "./css": "./dist/sleekspace-ui.css",
17
21
  "./styles/source": "./src/styles/index.scss",
18
22
  "./styles/mixins/responsive": "./src/styles/mixins/_responsive.scss",
19
- "./tokens.css": "./dist/tokens.css"
23
+ "./tokens.css": "./dist/tokens.css",
24
+ "./static": {
25
+ "types": "./dist/static/index.d.ts",
26
+ "import": "./dist/static/index.es.js",
27
+ "require": "./dist/static/index.cjs.js"
28
+ }
20
29
  },
21
30
  "files": [
22
31
  "dist",
@@ -29,12 +38,14 @@
29
38
  "LICENSE"
30
39
  ],
31
40
  "scripts": {
32
- "dev": "vite build --watch",
33
- "build": "npm run generate:types && vue-docgen-web-types && vite build && npm run build:tokens && npm run copy:docs && npm run generate:llms",
41
+ "dev": "concurrently -n vite,tokens -c cyan,magenta \"vite build --watch\" \"sass --watch src/styles/tokens.scss dist/tokens.css --no-source-map\"",
42
+ "build": "npm run generate:static-types && npm run generate:web-types && vite build && npm run generate:types && npm run build:static && npm run build:tokens && npm run copy:docs && npm run generate:llms",
43
+ "build:static": "vite build -c vite.static.config.ts",
34
44
  "build:tokens": "sass src/styles/tokens.scss dist/tokens.css --no-source-map",
35
45
  "copy:docs": "cp ../../Readme.md README.md && cp ../../LICENSE LICENSE",
36
46
  "generate:types": "tsx scripts/generate-global-types.ts",
37
- "generate:web-types": "vue-docgen-web-types",
47
+ "generate:static-types": "node scripts/generate-static-types.ts",
48
+ "generate:web-types": "vue-docgen-web-types --configFile web-types.config.cjs && tsx scripts/patch-web-types.ts",
38
49
  "generate:api-docs": "node ../docs-site/scripts/generate-api-docs.ts",
39
50
  "generate:llms": "npm run generate:api-docs && node ../docs-site/scripts/generate-llms-txt.ts --output-dir=.",
40
51
  "lint": "eslint src"
@@ -10,6 +10,7 @@
10
10
  :no-border="noBorder"
11
11
  :base-color="baseColor"
12
12
  :text-color="textColor"
13
+ :border-color="borderColor"
13
14
  :corners="corners"
14
15
  :decoration-corner="decorationCorner"
15
16
  :class="classes"
@@ -3,7 +3,10 @@
3
3
  --------------------------------------------------------------------------------------------------------------------->
4
4
 
5
5
  <template>
6
- <ContextMenuRadioGroup :model-value="modelValue" @update:model-value="$emit('update:modelValue', $event)">
6
+ <ContextMenuRadioGroup
7
+ :model-value="modelValue"
8
+ @update:model-value="$emit('update:modelValue', $event as string)"
9
+ >
7
10
  <slot />
8
11
  </ContextMenuRadioGroup>
9
12
  </template>
@@ -6,7 +6,7 @@
6
6
  <DropdownMenuRoot>
7
7
  <DropdownMenuTrigger as-child>
8
8
  <slot name="trigger">
9
- <SkButton :kind="kind">
9
+ <SkButton :kind="kind" :size="effectiveSize">
10
10
  {{ triggerText }}
11
11
  <template #trailing>
12
12
  <svg
@@ -73,7 +73,7 @@
73
73
  * SkDropdownSubmenu components.
74
74
  */
75
75
 
76
- import { computed, provide, toRef } from 'vue';
76
+ import { type Ref, computed, inject, provide, toRef } from 'vue';
77
77
  import {
78
78
  DropdownMenuContent,
79
79
  DropdownMenuPortal,
@@ -83,7 +83,7 @@
83
83
 
84
84
  // Types
85
85
  import type { ComponentCustomColors } from '@/types';
86
- import type { SkDropdownAlign, SkDropdownKind, SkDropdownSide } from './types';
86
+ import type { SkDropdownAlign, SkDropdownKind, SkDropdownSide, SkDropdownSize } from './types';
87
87
 
88
88
  // Components
89
89
  import SkButton from '../Button/SkButton.vue';
@@ -92,6 +92,9 @@
92
92
  import { useCustomColors } from '@/composables/useCustomColors';
93
93
  import { usePortalContext } from '@/composables/usePortalContext';
94
94
 
95
+ // Context
96
+ import { NAVBAR_SIZE_KEY } from '../NavBar/context';
97
+
95
98
  //------------------------------------------------------------------------------------------------------------------
96
99
 
97
100
  export interface SkDropdownComponentProps extends ComponentCustomColors
@@ -135,6 +138,14 @@
135
138
  * @default 4
136
139
  */
137
140
  sideOffset ?: number;
141
+
142
+ /**
143
+ * Size of the default trigger button. When omitted and the dropdown is rendered inside
144
+ * an SkNavBar, falls back to the navbar's `size` prop so the trigger naturally matches
145
+ * surrounding nav controls. Ignored when the `trigger` slot is used.
146
+ * @default 'md' (or navbar size when nested in an SkNavBar)
147
+ */
148
+ size ?: SkDropdownSize;
138
149
  }
139
150
 
140
151
  //------------------------------------------------------------------------------------------------------------------
@@ -145,6 +156,7 @@
145
156
  side: 'bottom',
146
157
  align: 'start',
147
158
  sideOffset: 4,
159
+ size: undefined,
148
160
  });
149
161
 
150
162
  //------------------------------------------------------------------------------------------------------------------
@@ -152,6 +164,11 @@
152
164
  // Handle portal context (theme injection/re-provision for nested portal components)
153
165
  const { theme } = usePortalContext();
154
166
 
167
+ // Pick up the navbar's size when nested inside an SkNavBar so the trigger fits the bar.
168
+ const navbarSize = inject<Ref<SkDropdownSize> | undefined>(NAVBAR_SIZE_KEY, undefined);
169
+
170
+ const effectiveSize = computed<SkDropdownSize>(() => props.size ?? navbarSize?.value ?? 'md');
171
+
155
172
  // Provide kind for submenus (reactive computed so changes propagate)
156
173
  provide('dropdown-kind', computed(() => props.kind));
157
174
 
@@ -3,7 +3,10 @@
3
3
  --------------------------------------------------------------------------------------------------------------------->
4
4
 
5
5
  <template>
6
- <DropdownMenuRadioGroup :model-value="modelValue" @update:model-value="$emit('update:modelValue', $event)">
6
+ <DropdownMenuRadioGroup
7
+ :model-value="modelValue"
8
+ @update:model-value="$emit('update:modelValue', $event as string)"
9
+ >
7
10
  <slot />
8
11
  </DropdownMenuRadioGroup>
9
12
  </template>
@@ -2,9 +2,10 @@
2
2
  // Dropdown Component Types
3
3
  //----------------------------------------------------------------------------------------------------------------------
4
4
 
5
- import type { ComponentKind } from '@/types';
5
+ import type { ComponentKind, ComponentSize } from '@/types';
6
6
 
7
7
  export type SkDropdownKind = ComponentKind;
8
+ export type SkDropdownSize = ComponentSize;
8
9
  export type SkDropdownSide = 'top' | 'right' | 'bottom' | 'left';
9
10
  export type SkDropdownAlign = 'start' | 'center' | 'end';
10
11
 
@@ -59,10 +59,10 @@
59
59
  */
60
60
 
61
61
  import { computed, provide, toRef, useSlots } from 'vue';
62
- import type { SkNavBarKind } from './types';
62
+ import type { SkNavBarKind, SkNavBarSize } from './types';
63
63
  import type { ComponentCustomColors } from '@/types';
64
64
  import { useCustomColors } from '@/composables/useCustomColors';
65
- import { NAVBAR_KIND_KEY } from './context';
65
+ import { NAVBAR_KIND_KEY, NAVBAR_SIZE_KEY } from './context';
66
66
 
67
67
  //------------------------------------------------------------------------------------------------------------------
68
68
 
@@ -83,6 +83,14 @@
83
83
  * @default true
84
84
  */
85
85
  sticky ?: boolean;
86
+
87
+ /**
88
+ * Default size for descendant components (buttons, dropdowns, sidebar toggles, etc.) that
89
+ * read the navbar context. Picks a size that fits the navbar's 4rem height without
90
+ * overflowing. Descendants can still override with their own `size` prop.
91
+ * @default 'md'
92
+ */
93
+ size ?: SkNavBarSize;
86
94
  }
87
95
 
88
96
  //------------------------------------------------------------------------------------------------------------------
@@ -90,11 +98,13 @@
90
98
  const props = withDefaults(defineProps<SkNavBarComponentProps>(), {
91
99
  kind: 'neutral',
92
100
  sticky: true,
101
+ size: 'md',
93
102
  });
94
103
 
95
- // Expose the navbar's kind to descendants so components like SkPageSidebarToggle can default
96
- // to the surrounding navbar's color scheme without the user wiring it manually.
104
+ // Expose the navbar's kind and size to descendants so components like SkPageSidebarToggle and
105
+ // SkDropdown can default to the surrounding navbar's look without the user wiring it manually.
97
106
  provide(NAVBAR_KIND_KEY, toRef(() => props.kind));
107
+ provide(NAVBAR_SIZE_KEY, toRef(() => props.size));
98
108
 
99
109
  //------------------------------------------------------------------------------------------------------------------
100
110
 
@@ -1,16 +1,18 @@
1
1
  //----------------------------------------------------------------------------------------------------------------------
2
2
  // NavBar Context
3
3
  //
4
- // Injection key for descendants (e.g. SkPageSidebarToggle) that want to inherit the surrounding
5
- // navbar's kind without the user wiring it manually.
4
+ // Injection keys for descendants (e.g. SkPageSidebarToggle, SkDropdown) that want to inherit the
5
+ // surrounding navbar's kind/size without the user wiring it manually.
6
6
  //----------------------------------------------------------------------------------------------------------------------
7
7
 
8
8
  import type { InjectionKey, Ref } from 'vue';
9
9
 
10
+ import type { ComponentSize } from '@/types';
10
11
  import type { SkNavBarKind } from './types';
11
12
 
12
13
  //----------------------------------------------------------------------------------------------------------------------
13
14
 
14
15
  export const NAVBAR_KIND_KEY : InjectionKey<Ref<SkNavBarKind>> = Symbol('sk-navbar-kind');
16
+ export const NAVBAR_SIZE_KEY : InjectionKey<Ref<ComponentSize>> = Symbol('sk-navbar-size');
15
17
 
16
18
  //----------------------------------------------------------------------------------------------------------------------
@@ -2,7 +2,7 @@
2
2
  // NavBar Types
3
3
  //----------------------------------------------------------------------------------------------------------------------
4
4
 
5
- import type { ComponentKind } from '@/types';
5
+ import type { ComponentKind, ComponentSize } from '@/types';
6
6
 
7
7
  //----------------------------------------------------------------------------------------------------------------------
8
8
 
@@ -12,4 +12,9 @@ import type { ComponentKind } from '@/types';
12
12
  */
13
13
  export type SkNavBarKind = ComponentKind;
14
14
 
15
+ /**
16
+ * Size hint propagated to descendant controls (buttons, dropdowns, sidebar toggles).
17
+ */
18
+ export type SkNavBarSize = ComponentSize;
19
+
15
20
  //----------------------------------------------------------------------------------------------------------------------
@@ -279,6 +279,15 @@
279
279
  * @default false
280
280
  */
281
281
  flush ?: boolean;
282
+
283
+ /**
284
+ * When true, disables the bouncy overscroll / rubber-band effect on the page's scroll
285
+ * containers (main content, sidebar body, aside body). Applies `overscroll-behavior: none`
286
+ * to every scrollable region owned by SkPage. Note: the document-level bounce from scrolling
287
+ * the page itself (outside SkPage's containers) can only be killed on `html`/`body`.
288
+ * @default false
289
+ */
290
+ noBounce ?: boolean;
282
291
  }
283
292
 
284
293
  //------------------------------------------------------------------------------------------------------------------
@@ -296,6 +305,7 @@
296
305
  asideOpen: undefined,
297
306
  theme: undefined,
298
307
  flush: false,
308
+ noBounce: false,
299
309
  });
300
310
 
301
311
  const emit = defineEmits<{
@@ -535,6 +545,7 @@
535
545
  'sk-sidebar-drawer-active': isSidebarDrawerActive.value,
536
546
  'sk-aside-drawer-active': isAsideDrawerActive.value,
537
547
  'sk-flush': props.flush,
548
+ 'sk-no-bounce': props.noBounce,
538
549
  }));
539
550
 
540
551
  const customStyles = computed(() =>
@@ -131,7 +131,8 @@
131
131
  const customColorStyles = useCustomColors(
132
132
  'panel',
133
133
  toRef(() => props.baseColor),
134
- toRef(() => props.textColor)
134
+ toRef(() => props.textColor),
135
+ toRef(() => props.borderColor)
135
136
  );
136
137
 
137
138
  //------------------------------------------------------------------------------------------------------------------
@@ -3,8 +3,8 @@
3
3
  --------------------------------------------------------------------------------------------------------------------->
4
4
 
5
5
  <template>
6
- <ScrollAreaRoot :type="type" :class="classes" :style="customColorStyles">
7
- <ScrollAreaViewport class="sk-scroll-area-viewport">
6
+ <ScrollAreaRoot :type="type" :class="classes" :style="rootStyles">
7
+ <ScrollAreaViewport ref="viewportRef" class="sk-scroll-area-viewport">
8
8
  <slot />
9
9
  </ScrollAreaViewport>
10
10
  <ScrollAreaScrollbar
@@ -34,7 +34,7 @@
34
34
  <!--------------------------------------------------------------------------------------------------------------------->
35
35
 
36
36
  <script setup lang="ts">
37
- import { computed, toRef } from 'vue';
37
+ import { computed, onBeforeUnmount, onMounted, ref, toRef } from 'vue';
38
38
  import {
39
39
  ScrollAreaCorner,
40
40
  ScrollAreaRoot,
@@ -61,9 +61,10 @@
61
61
 
62
62
  /**
63
63
  * When true, fade the scrollable edges with a CSS mask so content visibly tapers
64
- * into/out of the viewport. Applies to whichever axis (or both) is scrollable.
64
+ * into/out of the viewport. Only edges with more content to scroll toward get the
65
+ * fade -- the top fade disappears at scrollTop=0, bottom fade at scroll end, etc.
65
66
  * Override the fade distance via the `--sk-scroll-area-fade` custom property
66
- * (default 1.5rem).
67
+ * (default 1rem).
67
68
  * @default false
68
69
  */
69
70
  fade ?: boolean;
@@ -94,6 +95,78 @@
94
95
  [`sk-${ props.orientation }`]: true,
95
96
  'sk-fade': props.fade,
96
97
  }));
98
+
99
+ //------------------------------------------------------------------------------------------------------------------
100
+ // Scroll-position-aware fade tracking
101
+ //------------------------------------------------------------------------------------------------------------------
102
+
103
+ const viewportRef = ref<InstanceType<typeof ScrollAreaViewport> | null>(null);
104
+
105
+ const fadeTop = ref(0);
106
+ const fadeBottom = ref(0);
107
+ const fadeLeft = ref(0);
108
+ const fadeRight = ref(0);
109
+
110
+ let scrollEl : HTMLElement | null = null;
111
+ let resizeObs : ResizeObserver | null = null;
112
+ let rafID : number | null = null;
113
+
114
+ function updateFades() : void
115
+ {
116
+ if(!scrollEl) { return; }
117
+ const { scrollTop, scrollLeft, scrollHeight, scrollWidth, clientHeight, clientWidth } = scrollEl;
118
+ const threshold = 1;
119
+ fadeTop.value = scrollTop > threshold ? 1 : 0;
120
+ fadeBottom.value = scrollHeight - scrollTop - clientHeight > threshold ? 1 : 0;
121
+ fadeLeft.value = scrollLeft > threshold ? 1 : 0;
122
+ fadeRight.value = scrollWidth - scrollLeft - clientWidth > threshold ? 1 : 0;
123
+ }
124
+
125
+ function scheduleUpdate() : void
126
+ {
127
+ if(rafID !== null) { return; }
128
+ rafID = requestAnimationFrame(() =>
129
+ {
130
+ rafID = null;
131
+ updateFades();
132
+ });
133
+ }
134
+
135
+ onMounted(() =>
136
+ {
137
+ const exposed = viewportRef.value as unknown as { viewportElement ?: HTMLElement } | null;
138
+ scrollEl = exposed?.viewportElement ?? null;
139
+ if(!scrollEl) { return; }
140
+
141
+ scrollEl.addEventListener('scroll', scheduleUpdate, { passive: true });
142
+ resizeObs = new ResizeObserver(scheduleUpdate);
143
+ resizeObs.observe(scrollEl);
144
+ for(const child of Array.from(scrollEl.children))
145
+ {
146
+ resizeObs.observe(child);
147
+ }
148
+ updateFades();
149
+ });
150
+
151
+ onBeforeUnmount(() =>
152
+ {
153
+ if(scrollEl) { scrollEl.removeEventListener('scroll', scheduleUpdate); }
154
+ resizeObs?.disconnect();
155
+ if(rafID !== null) { cancelAnimationFrame(rafID); }
156
+ });
157
+
158
+ const rootStyles = computed(() =>
159
+ {
160
+ const styles : Record<string, string> = { ...customColorStyles.value };
161
+ if(props.fade)
162
+ {
163
+ styles['--sk-scroll-fade-top'] = String(fadeTop.value);
164
+ styles['--sk-scroll-fade-bottom'] = String(fadeBottom.value);
165
+ styles['--sk-scroll-fade-left'] = String(fadeLeft.value);
166
+ styles['--sk-scroll-fade-right'] = String(fadeRight.value);
167
+ }
168
+ return styles;
169
+ });
97
170
  </script>
98
171
 
99
172
  <!--------------------------------------------------------------------------------------------------------------------->
@@ -6,13 +6,13 @@
6
6
  <TreeRoot
7
7
  :items="items"
8
8
  :get-key="getKey"
9
- :model-value="modelValue"
9
+ :model-value="rekaModelValue"
10
10
  :multiple="multiple"
11
11
  :propagate-select="propagateSelect"
12
12
  :default-expanded="expandedKeys"
13
13
  :class="classes"
14
14
  :style="customColorStyles"
15
- @update:model-value="$emit('update:modelValue', $event as T | T[])"
15
+ @update:model-value="$emit('update:modelValue', $event as unknown as T | T[])"
16
16
  >
17
17
  <template #default="{ flattenItems }">
18
18
  <slot :flatten-items="flattenItems" />
@@ -73,6 +73,11 @@
73
73
 
74
74
  //------------------------------------------------------------------------------------------------------------------
75
75
 
76
+ type RekaTreeValue = Record<string, unknown> | Record<string, unknown>[];
77
+ const rekaModelValue = computed(() => props.modelValue as unknown as RekaTreeValue);
78
+
79
+ //------------------------------------------------------------------------------------------------------------------
80
+
76
81
  function getAllKeys(items : T[]) : string[]
77
82
  {
78
83
  const keys : string[] = [];
@@ -4,117 +4,126 @@
4
4
 
5
5
  import { type Ref, computed } from 'vue';
6
6
 
7
+ //----------------------------------------------------------------------------------------------------------------------
8
+ // Kind name resolution
9
+ //----------------------------------------------------------------------------------------------------------------------
10
+
11
+ /**
12
+ * Kind names that the color props accept as shortcuts. Typing `baseColor="neon-pink"` resolves
13
+ * to `var(--sk-neon-pink-base)`; typing `borderColor="primary"` resolves to `var(--sk-primary-base)`.
14
+ * Any string not in this set is passed through verbatim as a CSS color value.
15
+ */
16
+ const KIND_NAMES = new Set<string>([
17
+ // Semantic
18
+ 'neutral',
19
+ 'primary',
20
+ 'accent',
21
+ 'info',
22
+ 'success',
23
+ 'warning',
24
+ 'danger',
25
+ // Color
26
+ 'boulder',
27
+ 'neon-blue',
28
+ 'light-blue',
29
+ 'neon-orange',
30
+ 'neon-purple',
31
+ 'neon-green',
32
+ 'neon-mint',
33
+ 'neon-pink',
34
+ 'yellow',
35
+ 'red',
36
+ ]);
37
+
38
+ type ColorPart = 'base' | 'text';
39
+
40
+ function resolveColor(value : string | undefined, part : ColorPart) : string | undefined
41
+ {
42
+ if(!value) { return undefined; }
43
+ const trimmed = value.trim();
44
+ if(KIND_NAMES.has(trimmed))
45
+ {
46
+ return `var(--sk-${ trimmed }-${ part })`;
47
+ }
48
+ return value;
49
+ }
50
+
51
+ function unwrap(input : Ref<string | undefined> | string | undefined) : string | undefined
52
+ {
53
+ return typeof input === 'string' ? input : input?.value;
54
+ }
55
+
7
56
  //----------------------------------------------------------------------------------------------------------------------
8
57
 
9
58
  /**
10
59
  * Composable for handling custom color props in components.
11
60
  *
12
- * This composable provides a consistent way to apply custom colors to components by generating
13
- * CSS variables that override the component's default color tokens. It supports any CSS color
14
- * format including hex, rgb, hsl, oklch, named colors, and CSS variables.
61
+ * Generates CSS custom properties that override a component's default color tokens. Props accept
62
+ * either a kind name (e.g. `"neon-pink"`, `"primary"`), which resolves to the matching
63
+ * `--sk-<kind>-base/-text` token, or any raw CSS color value (hex, rgb, oklch, named, or
64
+ * `var(...)`), which is passed through unchanged. This lets consumers use SleekSpace's palette
65
+ * by name without having to remember the token syntax.
15
66
  *
16
67
  * Works with any component that follows the CSS variable naming convention:
17
68
  * - `--sk-{componentName}-color-base` for the base/background color
18
69
  * - `--sk-{componentName}-fg` for the foreground/text color
70
+ * - `--sk-{componentName}-border-base` / `--sk-{componentName}-border-color` for the border (optional)
19
71
  *
20
- * @param componentName - The component name used in CSS variable naming
21
- * (e.g., 'button', 'panel', 'my-custom-component')
22
- * @param baseColor - The base/background color (any CSS color value, including CSS variables)
23
- * @param textColor - Optional foreground/text color. If not provided, falls back to `--sk-neutral-text`
72
+ * @param componentName - The component name used in CSS variable naming (e.g., 'button', 'panel')
73
+ * @param baseColor - Kind name or CSS color value for the base/background color
74
+ * @param textColor - Kind name or CSS color value for the foreground/text color. Falls back to
75
+ * `--sk-neutral-text` when only `baseColor` is provided.
76
+ * @param borderColor - Optional kind name or CSS color value for the border. When provided,
77
+ * both `--sk-<component>-border-base` and `--sk-<component>-border-color`
78
+ * are emitted so the border takes precedence over any kind's default.
24
79
  *
25
80
  * @returns Computed style object with CSS variables ready to bind to a component's style attribute
26
81
  *
27
- * @example Basic usage with base color only
82
+ * @example Kind name shortcut
28
83
  * ```vue
29
- * <script setup>
30
- * import { useCustomColors } from '@/composables/useCustomColors';
31
- *
32
- * const props = defineProps<{ baseColor?: string }>();
33
- * const customColors = useCustomColors('button', toRef(() => props.baseColor), undefined);
34
- * </script>
35
- *
36
- * <template>
37
- * <button :style="customColors">Click me</button>
38
- * </template>
84
+ * <SkPanel base-color="neon-pink" text-color="primary" border-color="neon-purple">...</SkPanel>
39
85
  * ```
40
86
  *
41
- * @example With both base and text colors
87
+ * @example Raw CSS values
42
88
  * ```vue
43
- * <SkButton base-color="oklch(0.7 0.25 300)" text-color="white">
44
- * Custom Purple Button
45
- * </SkButton>
89
+ * <SkButton base-color="oklch(0.7 0.25 300)" text-color="white">Click me</SkButton>
46
90
  * ```
47
- *
48
- * @example Using CSS variables
49
- * ```vue
50
- * <SkPanel base-color="var(--my-custom-color)" text-color="var(--my-text-color)">
51
- * Content
52
- * </SkPanel>
53
- * ```
54
- *
55
- * @example Custom component
56
- * ```vue
57
- * <script setup>
58
- * import { useCustomColors } from '@/composables/useCustomColors';
59
- *
60
- * const props = defineProps<{ baseColor?: string; textColor?: string }>();
61
- * const customColors = useCustomColors('my-widget', toRef(() => props.baseColor), toRef(() => props.textColor));
62
- * </script>
63
- *
64
- * <template>
65
- * <div class="my-widget" :style="customColors">
66
- * <!-- Will generate: --sk-my-widget-color-base and --sk-my-widget-fg -->
67
- * </div>
68
- * </template>
69
- * ```
70
- *
71
- * Generated CSS variables:
72
- * - `--sk-{componentName}-color-base` - The base color for backgrounds and accents
73
- * - `--sk-{componentName}-fg` - The foreground/text color
74
- *
75
- * @remarks
76
- * - If `textColor` is not provided, components will use `--sk-neutral-text` from the active theme
77
- * - For best contrast, always provide `textColor` when using custom `baseColor`
78
- * - The generated CSS variables integrate with the component's existing token system
79
- * - Works with any component name - no need to register components beforehand
80
91
  */
81
92
  export function useCustomColors(
82
93
  componentName : string,
83
94
  baseColor : Ref<string | undefined> | string | undefined,
84
- textColor : Ref<string | undefined> | string | undefined
95
+ textColor : Ref<string | undefined> | string | undefined,
96
+ borderColor ?: Ref<string | undefined> | string | undefined
85
97
  ) : Ref<Record<string, string>>
86
98
  {
87
99
  return computed(() =>
88
100
  {
89
101
  const styles : Record<string, string> = {};
90
102
 
91
- // Resolve refs to values
92
- const baseColorValue = typeof baseColor === 'string' ? baseColor : baseColor?.value;
93
- const textColorValue = typeof textColor === 'string' ? textColor : textColor?.value;
103
+ const baseResolved = resolveColor(unwrap(baseColor), 'base');
104
+ const textResolved = resolveColor(unwrap(textColor), 'text');
105
+ const borderResolved = resolveColor(unwrap(borderColor), 'base');
94
106
 
95
- // Only apply custom colors if baseColor is provided
96
- if(!baseColorValue)
107
+ if(baseResolved)
97
108
  {
98
- return styles;
99
- }
100
-
101
- // Set the base color CSS variable
102
- const baseVarName = `--sk-${ componentName }-color-base`;
103
- styles[baseVarName] = baseColorValue;
109
+ styles[`--sk-${ componentName }-color-base`] = baseResolved;
104
110
 
105
- // Set or calculate text color
106
- const fgVarName = `--sk-${ componentName }-fg`;
107
-
108
- if(textColorValue)
111
+ // Keep historical contract: when a caller sets baseColor without textColor, fall back
112
+ // to --sk-neutral-text so text remains legible on arbitrary custom backgrounds.
113
+ styles[`--sk-${ componentName }-fg`] = textResolved ?? 'var(--sk-neutral-text)';
114
+ }
115
+ else if(textResolved)
109
116
  {
110
- // Use provided text color
111
- styles[fgVarName] = textColorValue;
117
+ styles[`--sk-${ componentName }-fg`] = textResolved;
112
118
  }
113
- else
119
+
120
+ if(borderResolved)
114
121
  {
115
- // Fallback to the theme's default text color
116
- // Users should provide textColor for optimal contrast
117
- styles[fgVarName] = 'var(--sk-neutral-text)';
122
+ // Set both tokens so the override wins regardless of which one the component's
123
+ // SCSS consults (`--*-border-base` usually drives decorations; `--*-border-color`
124
+ // drives the actual border).
125
+ styles[`--sk-${ componentName }-border-base`] = borderResolved;
126
+ styles[`--sk-${ componentName }-border-color`] = borderResolved;
118
127
  }
119
128
 
120
129
  return styles;
@@ -2,8 +2,6 @@
2
2
  // Portal Context Composable Tests
3
3
  //----------------------------------------------------------------------------------------------------------------------
4
4
 
5
- /* eslint-disable vue/one-component-per-file */
6
-
7
5
  import { describe, expect, it } from 'vitest';
8
6
  import { defineComponent, h, inject, provide, ref } from 'vue';
9
7
  import { mount } from '@vue/test-utils';