@skewedaspect/sleekspace-ui 0.5.0 → 0.6.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 (41) hide show
  1. package/dist/components/Card/SkCard.vue.d.ts +13 -1
  2. package/dist/components/Panel/SkPanel.vue.d.ts +15 -1
  3. package/dist/components/Panel/types.d.ts +1 -0
  4. package/dist/components/Select/SkSelect.vue.d.ts +61 -0
  5. package/dist/components/Select/SkSelectItem.vue.d.ts +134 -0
  6. package/dist/components/Select/SkSelectSeparator.vue.d.ts +2 -0
  7. package/dist/components/Select/index.d.ts +4 -0
  8. package/dist/components/Select/types.d.ts +3 -0
  9. package/dist/components/Sidebar/SkSidebar.vue.d.ts +8 -1
  10. package/dist/components/Sidebar/types.d.ts +1 -0
  11. package/dist/components/Skeleton/SkSkeleton.vue.d.ts +1 -1
  12. package/dist/components/Theme/types.d.ts +18 -3
  13. package/dist/index.d.ts +7 -0
  14. package/dist/sleekspace-ui.css +494 -74
  15. package/dist/sleekspace-ui.es.js +2014 -283
  16. package/dist/sleekspace-ui.umd.js +2013 -282
  17. package/docs/guides/design-tokens/advanced.md +6 -1
  18. package/docs/guides/theming.md +11 -1
  19. package/package.json +1 -1
  20. package/src/components/Card/SkCard.vue +17 -1
  21. package/src/components/Panel/SkPanel.vue +29 -4
  22. package/src/components/Panel/types.ts +3 -0
  23. package/src/components/Select/SkSelect.vue +210 -0
  24. package/src/components/Select/SkSelectItem.vue +112 -0
  25. package/src/components/Select/SkSelectSeparator.vue +40 -0
  26. package/src/components/Select/index.ts +10 -0
  27. package/src/components/Select/types.ts +10 -0
  28. package/src/components/Sidebar/SkSidebar.vue +39 -2
  29. package/src/components/Sidebar/types.ts +2 -0
  30. package/src/components/Theme/types.ts +20 -3
  31. package/src/global.d.ts +2 -0
  32. package/src/index.ts +10 -0
  33. package/src/styles/components/_button.scss +45 -9
  34. package/src/styles/components/_card.scss +45 -9
  35. package/src/styles/components/_index.scss +1 -0
  36. package/src/styles/components/_listbox.scss +1 -0
  37. package/src/styles/components/_panel.scss +119 -13
  38. package/src/styles/components/_select.scss +439 -0
  39. package/src/styles/components/_sidebar.scss +83 -4
  40. package/src/styles/themes/README.md +6 -2
  41. package/web-types.json +148 -1
@@ -31,7 +31,12 @@ Add new color primitives in `_foundation-colors.scss`:
31
31
  1. Create `src/styles/themes/_mytheme.scss`
32
32
  2. Define all 7 semantic kinds with the `[data-scheme="mytheme"]` selector
33
33
  3. Import in `themes/index.scss`: `@forward 'mytheme';`
34
- 4. Add to TypeScript types: `export type SkThemeName = 'greyscale' | 'colorful' | 'mytheme';`
34
+ 4. Register the TypeScript type via module augmentation:
35
+ ```ts
36
+ declare module '@skewedaspect/sleekspace-ui' {
37
+ interface SkThemeNameMap { mytheme : true; }
38
+ }
39
+ ```
35
40
  5. Use: `<SkTheme theme="mytheme">`
36
41
 
37
42
  ### Component Token Overrides
@@ -265,7 +265,17 @@ Apply it with `SkTheme` or `SkPage`:
265
265
  </SkPage>
266
266
  ```
267
267
 
268
- > **TypeScript note:** The `theme` prop is typed as `SkThemeName` which is `'greyscale' | 'colorful'`. For custom theme names, cast the value: `:theme="'ocean' as any"`. If you're building a library that adds themes, you can augment the type via module declaration.
268
+ > **TypeScript note:** To get type-safe autocomplete for custom theme names, augment the `SkThemeNameMap` interface:
269
+ >
270
+ > ```ts
271
+ > declare module '@skewedaspect/sleekspace-ui' {
272
+ > interface SkThemeNameMap {
273
+ > ocean : true;
274
+ > }
275
+ > }
276
+ > ```
277
+ >
278
+ > This registers `'ocean'` as a valid `SkThemeName` throughout your project.
269
279
 
270
280
  ### Using Foundation Colors
271
281
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skewedaspect/sleekspace-ui",
3
- "version": "0.5.0",
3
+ "version": "0.6.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",
@@ -10,6 +10,8 @@
10
10
  :no-border="noBorder"
11
11
  :base-color="baseColor"
12
12
  :text-color="textColor"
13
+ :corners="corners"
14
+ :decoration-corner="decorationCorner"
13
15
  :class="classes"
14
16
  >
15
17
  <div v-if="title || $slots.header" class="sk-card-header" :style="headerStyles">
@@ -66,7 +68,7 @@
66
68
  // Types
67
69
  import type { ComponentCustomColors } from '@/types';
68
70
  import type { SkCardKind } from './types';
69
- import type { SkPanelSize } from '../Panel/types';
71
+ import type { SkPanelCorner, SkPanelSize } from '../Panel/types';
70
72
 
71
73
  // Components
72
74
  import SkPanel from '../Panel/SkPanel.vue';
@@ -136,6 +138,18 @@
136
138
  * @default false
137
139
  */
138
140
  scrollable ?: boolean;
141
+
142
+ /**
143
+ * Which corners receive the beveled cut. Passed through to the underlying SkPanel.
144
+ * @default ['bottom-right']
145
+ */
146
+ corners ?: SkPanelCorner[];
147
+
148
+ /**
149
+ * Which corner displays the decorative accent stripe. Passed through to SkPanel.
150
+ * @default 'bottom-right'
151
+ */
152
+ decorationCorner ?: SkPanelCorner;
139
153
  }
140
154
 
141
155
  //------------------------------------------------------------------------------------------------------------------
@@ -149,6 +163,8 @@
149
163
  title: undefined,
150
164
  headerColor: undefined,
151
165
  scrollable: false,
166
+ corners: () => [ 'bottom-right' ],
167
+ decorationCorner: 'bottom-right',
152
168
  });
153
169
 
154
170
  //------------------------------------------------------------------------------------------------------------------
@@ -40,7 +40,7 @@
40
40
 
41
41
  // Types
42
42
  import type { ComponentCustomColors } from '@/types';
43
- import type { SkPanelKind, SkPanelSize } from './types';
43
+ import type { SkPanelCorner, SkPanelKind, SkPanelSize } from './types';
44
44
 
45
45
  // Composables
46
46
  import { useCustomColors } from '@/composables/useCustomColors';
@@ -81,6 +81,20 @@
81
81
  * @default false
82
82
  */
83
83
  noBorder ?: boolean;
84
+
85
+ /**
86
+ * Which corners receive the beveled cut. Any combination of the four corners can be
87
+ * specified. Pass an empty array for no cut corners.
88
+ * @default ['bottom-right']
89
+ */
90
+ corners ?: SkPanelCorner[];
91
+
92
+ /**
93
+ * Which corner displays the decorative accent stripe. Must be one of the active
94
+ * `corners` for the decoration to be visible.
95
+ * @default 'bottom-right'
96
+ */
97
+ decorationCorner ?: SkPanelCorner;
84
98
  }
85
99
 
86
100
  //------------------------------------------------------------------------------------------------------------------
@@ -90,6 +104,8 @@
90
104
  size: 'md',
91
105
  showDecoration: true,
92
106
  noBorder: false,
107
+ corners: () => [ 'bottom-right' ],
108
+ decorationCorner: 'bottom-right',
93
109
  });
94
110
 
95
111
  //------------------------------------------------------------------------------------------------------------------
@@ -101,6 +117,11 @@
101
117
  [`sk-${ props.kind }`]: true,
102
118
  [`sk-${ props.size }`]: true,
103
119
  'sk-no-border': props.noBorder,
120
+ 'sk-cut-top-left': props.corners.includes('top-left'),
121
+ 'sk-cut-top-right': props.corners.includes('top-right'),
122
+ 'sk-cut-bottom-right': props.corners.includes('bottom-right'),
123
+ 'sk-cut-bottom-left': props.corners.includes('bottom-left'),
124
+ [`sk-decoration-${ props.decorationCorner }`]: true,
104
125
  };
105
126
  });
106
127
 
@@ -116,14 +137,18 @@
116
137
  //------------------------------------------------------------------------------------------------------------------
117
138
 
118
139
  // Control decoration visibility
119
- // Note: noBorder automatically disables decoration
140
+ // Hidden when: noBorder, showDecoration is false, or decorationCorner isn't in corners
120
141
  const decorationDisplay = computed(() =>
121
142
  {
122
- if(props.noBorder)
143
+ if(props.noBorder || !props.showDecoration)
144
+ {
145
+ return 'none';
146
+ }
147
+ if(!props.corners.includes(props.decorationCorner))
123
148
  {
124
149
  return 'none';
125
150
  }
126
- return props.showDecoration ? 'block' : 'none';
151
+ return 'block';
127
152
  });
128
153
  </script>
129
154
 
@@ -12,4 +12,7 @@ export type SkPanelKind = ComponentKind;
12
12
  // Panel sizes (only affects cut size, not padding or dimensions)
13
13
  export type SkPanelSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
14
14
 
15
+ // Which corners get the bevel cut
16
+ export type SkPanelCorner = 'top-left' | 'top-right' | 'bottom-right' | 'bottom-left';
17
+
15
18
  //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,210 @@
1
+ <!----------------------------------------------------------------------------------------------------------------------
2
+ - Select Component
3
+ --------------------------------------------------------------------------------------------------------------------->
4
+
5
+ <template>
6
+ <div :class="wrapperClasses" :style="customColorStyles">
7
+ <SelectRoot v-model="modelValue" :disabled="disabled">
8
+ <SelectTrigger :class="triggerClasses">
9
+ <SelectValue :placeholder="placeholder">
10
+ <template v-if="displayText">
11
+ {{ displayText }}
12
+ </template>
13
+ </SelectValue>
14
+ <svg
15
+ xmlns="http://www.w3.org/2000/svg"
16
+ viewBox="0 0 24 24"
17
+ fill="none"
18
+ stroke="currentColor"
19
+ stroke-width="2"
20
+ stroke-linecap="square"
21
+ stroke-linejoin="miter"
22
+ style="width: 1rem; height: 1rem; flex-shrink: 0;"
23
+ >
24
+ <polyline points="6 9 12 15 18 9" />
25
+ </svg>
26
+ </SelectTrigger>
27
+
28
+ <SelectPortal>
29
+ <SelectContent
30
+ :data-scheme="theme"
31
+ :class="contentClasses"
32
+ :style="customColorStyles"
33
+ position="popper"
34
+ side="bottom"
35
+ align="start"
36
+ :side-offset="4"
37
+ >
38
+ <SelectViewport>
39
+ <slot />
40
+ </SelectViewport>
41
+ </SelectContent>
42
+ </SelectPortal>
43
+ </SelectRoot>
44
+ </div>
45
+ </template>
46
+
47
+ <!--------------------------------------------------------------------------------------------------------------------->
48
+
49
+ <style lang="scss" scoped>
50
+ // Component styles are in /src/styles/components/_select.scss
51
+ </style>
52
+
53
+ <!--------------------------------------------------------------------------------------------------------------------->
54
+
55
+ <script setup lang="ts">
56
+ /**
57
+ * @component SkSelect
58
+ * @description A simple dropdown select for picking from predefined options. Unlike SkListbox which includes
59
+ * a search input for filtering, SkSelect provides a clean trigger button that opens a dropdown panel.
60
+ * Built on RekaUI's Select primitive with full keyboard navigation and portal rendering.
61
+ *
62
+ * @example
63
+ * ```vue
64
+ * <SkSelect v-model="selectedCountry" kind="primary" placeholder="Select a country...">
65
+ * <SkSelectItem value="us">United States</SkSelectItem>
66
+ * <SkSelectItem value="uk">United Kingdom</SkSelectItem>
67
+ * <SkSelectItem value="ca">Canada</SkSelectItem>
68
+ * </SkSelect>
69
+ * ```
70
+ *
71
+ * @slot default - SkSelectItem components representing the available options. Use SkSelectSeparator
72
+ * to create visual dividers between groups of options.
73
+ */
74
+
75
+ import { type ComputedRef, computed, inject, provide, reactive, toRef } from 'vue';
76
+ import {
77
+ SelectContent,
78
+ SelectPortal,
79
+ SelectRoot,
80
+ SelectTrigger,
81
+ SelectValue,
82
+ SelectViewport,
83
+ } from 'reka-ui';
84
+
85
+ // Types
86
+ import type { ComponentCustomColors } from '@/types';
87
+ import type { SkSelectKind, SkSelectSize } from './types';
88
+
89
+ // Composables
90
+ import { useCustomColors } from '@/composables/useCustomColors';
91
+ import { usePortalContext } from '@/composables/usePortalContext';
92
+
93
+ //------------------------------------------------------------------------------------------------------------------
94
+
95
+ export interface SkSelectComponentProps extends ComponentCustomColors
96
+ {
97
+ /**
98
+ * Semantic color kind that controls the trigger border, focus ring, and selected
99
+ * item highlight appearance. When used inside SkField, inherits the field's
100
+ * kind if not explicitly set.
101
+ * @default 'neutral' (or inherited from parent SkField)
102
+ */
103
+ kind ?: SkSelectKind;
104
+
105
+ /**
106
+ * Size of the trigger and dropdown content. Controls the trigger height,
107
+ * text size, and option item dimensions. Available sizes: 'sm' (small),
108
+ * 'md' (medium), 'lg' (large).
109
+ * @default 'md'
110
+ */
111
+ size ?: SkSelectSize;
112
+
113
+ /**
114
+ * Placeholder text displayed in the trigger when no option is selected.
115
+ * Use to guide users on what type of selection to make.
116
+ * @default 'Select...'
117
+ */
118
+ placeholder ?: string;
119
+
120
+ /**
121
+ * When true, the select is disabled and cannot be interacted with. The trigger
122
+ * is non-clickable and the dropdown cannot be opened. The component appears
123
+ * with reduced opacity and the cursor changes to not-allowed.
124
+ * @default false
125
+ */
126
+ disabled ?: boolean;
127
+ }
128
+
129
+ //------------------------------------------------------------------------------------------------------------------
130
+
131
+ const props = withDefaults(defineProps<SkSelectComponentProps>(), {
132
+ kind: undefined,
133
+ size: 'md',
134
+ placeholder: 'Select...',
135
+ disabled: false,
136
+ });
137
+
138
+ //------------------------------------------------------------------------------------------------------------------
139
+
140
+ /**
141
+ * The selected value. Use with `v-model` for two-way binding. The value corresponds
142
+ * to the `value` prop of the selected SkSelectItem.
143
+ */
144
+ const modelValue = defineModel<string>();
145
+
146
+ //------------------------------------------------------------------------------------------------------------------
147
+
148
+ // Handle portal context (theme injection/re-provision for nested portal components)
149
+ const { theme } = usePortalContext();
150
+
151
+ // Inject field kind from parent SkField
152
+ const fieldKind = inject<ComputedRef<SkSelectKind | undefined>>('field-kind', computed(() => undefined));
153
+
154
+ //------------------------------------------------------------------------------------------------------------------
155
+
156
+ // Item label registry -- items register their value→label on mount so we can display
157
+ // the selected text synchronously, avoiding the flash from SelectValue's async detection.
158
+ const itemLabels = reactive(new Map<string, string>());
159
+
160
+ provide('sk-select-register', (value : string, label : string) =>
161
+ {
162
+ itemLabels.set(value, label);
163
+ });
164
+
165
+ provide('sk-select-unregister', (value : string) =>
166
+ {
167
+ itemLabels.delete(value);
168
+ });
169
+
170
+ const displayText = computed(() =>
171
+ {
172
+ if(!modelValue.value) { return ''; }
173
+ return itemLabels.get(modelValue.value) || modelValue.value;
174
+ });
175
+
176
+ //------------------------------------------------------------------------------------------------------------------
177
+
178
+ const effectiveKind = computed(() => fieldKind.value || props.kind || 'neutral');
179
+
180
+ //------------------------------------------------------------------------------------------------------------------
181
+
182
+ const wrapperClasses = computed(() => ({
183
+ 'sk-select': true,
184
+ [`sk-${ effectiveKind.value }`]: true,
185
+ [`sk-${ props.size }`]: true,
186
+ }));
187
+
188
+ //------------------------------------------------------------------------------------------------------------------
189
+
190
+ const triggerClasses = computed(() => ({
191
+ 'sk-select-trigger': true,
192
+ }));
193
+
194
+ //------------------------------------------------------------------------------------------------------------------
195
+
196
+ const contentClasses = computed(() => ({
197
+ 'sk-select-content': true,
198
+ [`sk-${ effectiveKind.value }`]: true,
199
+ }));
200
+
201
+ //------------------------------------------------------------------------------------------------------------------
202
+
203
+ const customColorStyles = useCustomColors(
204
+ 'select',
205
+ toRef(() => props.baseColor),
206
+ toRef(() => props.textColor)
207
+ );
208
+ </script>
209
+
210
+ <!--------------------------------------------------------------------------------------------------------------------->
@@ -0,0 +1,112 @@
1
+ <!----------------------------------------------------------------------------------------------------------------------
2
+ - SelectItem Component
3
+ --------------------------------------------------------------------------------------------------------------------->
4
+
5
+ <template>
6
+ <SelectItem :class="classes" :value="value" :disabled="disabled">
7
+ <SelectItemText ref="textEl">
8
+ <slot />
9
+ </SelectItemText>
10
+ <SelectItemIndicator class="sk-select-item-indicator">
11
+ <svg
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ viewBox="0 0 24 24"
14
+ fill="none"
15
+ stroke="currentColor"
16
+ stroke-width="3"
17
+ stroke-linecap="square"
18
+ stroke-linejoin="miter"
19
+ style="width: 1rem; height: 1rem;"
20
+ >
21
+ <polyline points="20 6 9 17 4 12" />
22
+ </svg>
23
+ </SelectItemIndicator>
24
+ </SelectItem>
25
+ </template>
26
+
27
+ <!--------------------------------------------------------------------------------------------------------------------->
28
+
29
+ <style lang="scss" scoped>
30
+ // Component styles are in /src/styles/components/_select.scss
31
+ </style>
32
+
33
+ <!--------------------------------------------------------------------------------------------------------------------->
34
+
35
+ <script setup lang="ts">
36
+ /**
37
+ * @component SkSelectItem
38
+ * @description A selectable option within an SkSelect dropdown. When selected, the item displays a
39
+ * checkmark indicator and its value is set as the select's v-model. Built on RekaUI's SelectItem
40
+ * with keyboard navigation support.
41
+ *
42
+ * @example
43
+ * ```vue
44
+ * <SkSelect v-model="selected">
45
+ * <SkSelectItem value="option1">First Option</SkSelectItem>
46
+ * <SkSelectItem value="option2">Second Option</SkSelectItem>
47
+ * <SkSelectItem value="option3" disabled>Unavailable Option</SkSelectItem>
48
+ * </SkSelect>
49
+ * ```
50
+ *
51
+ * @slot default - The display content for this option. Can be plain text or rich content including
52
+ * icons, formatted text, or custom layouts. This is what users see in the dropdown.
53
+ */
54
+
55
+ import { computed, inject, onMounted, onUnmounted, useTemplateRef } from 'vue';
56
+ import { SelectItem, SelectItemIndicator, SelectItemText } from 'reka-ui';
57
+
58
+ //------------------------------------------------------------------------------------------------------------------
59
+
60
+ export interface SkSelectItemComponentProps
61
+ {
62
+ /**
63
+ * The value this option represents. When selected, the parent SkSelect's v-model
64
+ * will be set to this value. Must be a string and unique within the select.
65
+ */
66
+ value : string;
67
+
68
+ /**
69
+ * When true, this option is disabled and cannot be selected. The item appears
70
+ * with reduced opacity and is skipped during keyboard navigation.
71
+ * @default false
72
+ */
73
+ disabled ?: boolean;
74
+ }
75
+
76
+ //------------------------------------------------------------------------------------------------------------------
77
+
78
+ const props = withDefaults(defineProps<SkSelectItemComponentProps>(), {
79
+ disabled: false,
80
+ });
81
+
82
+ //------------------------------------------------------------------------------------------------------------------
83
+
84
+ const textEl = useTemplateRef<InstanceType<typeof SelectItemText>>('textEl');
85
+ const register = inject<(value : string, label : string) => void>('sk-select-register', undefined);
86
+ const unregister = inject<(value : string) => void>('sk-select-unregister', undefined);
87
+
88
+ onMounted(() =>
89
+ {
90
+ const el = (textEl.value as unknown as { $el ?: HTMLElement })?.$el;
91
+ if(register)
92
+ {
93
+ register(props.value, el?.textContent?.trim() || props.value);
94
+ }
95
+ });
96
+
97
+ onUnmounted(() =>
98
+ {
99
+ if(unregister)
100
+ {
101
+ unregister(props.value);
102
+ }
103
+ });
104
+
105
+ //------------------------------------------------------------------------------------------------------------------
106
+
107
+ const classes = computed(() => ({
108
+ 'sk-select-item': true,
109
+ }));
110
+ </script>
111
+
112
+ <!--------------------------------------------------------------------------------------------------------------------->
@@ -0,0 +1,40 @@
1
+ <!----------------------------------------------------------------------------------------------------------------------
2
+ - SelectSeparator Component
3
+ --------------------------------------------------------------------------------------------------------------------->
4
+
5
+ <template>
6
+ <SelectSeparator class="sk-select-separator" />
7
+ </template>
8
+
9
+ <!--------------------------------------------------------------------------------------------------------------------->
10
+
11
+ <style lang="scss" scoped>
12
+ // Component styles are in /src/styles/components/_select.scss
13
+ </style>
14
+
15
+ <!--------------------------------------------------------------------------------------------------------------------->
16
+
17
+ <script setup lang="ts">
18
+ /**
19
+ * @component SkSelectSeparator
20
+ * @description A visual divider for organizing options within an SkSelect dropdown. Use to create
21
+ * logical groups of related options without affecting selection behavior. Renders as a horizontal
22
+ * line between items with appropriate spacing.
23
+ *
24
+ * @example
25
+ * ```vue
26
+ * <SkSelect v-model="selected">
27
+ * <SkSelectItem value="recent1">Recent Document 1</SkSelectItem>
28
+ * <SkSelectItem value="recent2">Recent Document 2</SkSelectItem>
29
+ * <SkSelectSeparator />
30
+ * <SkSelectItem value="all">Browse All Documents...</SkSelectItem>
31
+ * </SkSelect>
32
+ * ```
33
+ *
34
+ * @slot - Not applicable. This component does not accept slot content.
35
+ */
36
+
37
+ import { SelectSeparator } from 'reka-ui';
38
+ </script>
39
+
40
+ <!--------------------------------------------------------------------------------------------------------------------->
@@ -0,0 +1,10 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Select Component Exports
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ export { default as SkSelect } from './SkSelect.vue';
6
+ export { default as SkSelectItem } from './SkSelectItem.vue';
7
+ export { default as SkSelectSeparator } from './SkSelectSeparator.vue';
8
+ export type { SkSelectKind, SkSelectSize } from './types';
9
+
10
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,10 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Select Component Types
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ import type { ComponentKind, ComponentSize } from '@/types';
6
+
7
+ export type SkSelectKind = ComponentKind;
8
+ export type SkSelectSize = ComponentSize;
9
+
10
+ //----------------------------------------------------------------------------------------------------------------------
@@ -3,11 +3,13 @@
3
3
  --------------------------------------------------------------------------------------------------------------------->
4
4
 
5
5
  <template>
6
- <aside :class="classes">
6
+ <aside :class="classes" :style="sidebarStyles">
7
7
  <SkPanel
8
8
  :kind="kind"
9
9
  :base-color="baseColor"
10
10
  :text-color="textColor"
11
+ :corners="panelCorners"
12
+ :decoration-corner="panelDecorationCorner"
11
13
  class="sk-sidebar-panel"
12
14
  >
13
15
  <div class="sk-panel-scroll-content">
@@ -49,8 +51,13 @@
49
51
  */
50
52
 
51
53
  import { computed } from 'vue';
52
- import type { SkSidebarKind } from './types';
54
+
55
+ // Types
56
+ import type { SkSidebarKind, SkSidebarSide } from './types';
53
57
  import type { ComponentCustomColors } from '@/types';
58
+ import type { SkPanelCorner } from '../Panel/types';
59
+
60
+ // Components
54
61
  import SkPanel from '../Panel/SkPanel.vue';
55
62
 
56
63
  //------------------------------------------------------------------------------------------------------------------
@@ -72,6 +79,13 @@
72
79
  * @default '180px'
73
80
  */
74
81
  width ?: string;
82
+
83
+ /**
84
+ * Which side of the layout the sidebar is placed on. Controls the direction of the
85
+ * panel bevel and the sidebar-item clip-path cuts so they mirror appropriately.
86
+ * @default 'left'
87
+ */
88
+ side ?: SkSidebarSide;
75
89
  }
76
90
 
77
91
  //------------------------------------------------------------------------------------------------------------------
@@ -79,17 +93,40 @@
79
93
  const props = withDefaults(defineProps<SkSidebarComponentProps>(), {
80
94
  kind: 'neutral',
81
95
  width: '180px',
96
+ side: 'left',
82
97
  });
83
98
 
84
99
  //------------------------------------------------------------------------------------------------------------------
85
100
 
101
+ const panelCorners = computed<SkPanelCorner[]>(() =>
102
+ {
103
+ return props.side === 'right' ? [ 'bottom-left' ] : [ 'bottom-right' ];
104
+ });
105
+
106
+ const panelDecorationCorner = computed<SkPanelCorner>(() =>
107
+ {
108
+ return props.side === 'right' ? 'bottom-left' : 'bottom-right';
109
+ });
110
+
86
111
  const classes = computed(() =>
87
112
  {
88
113
  return {
89
114
  'sk-sidebar': true,
90
115
  [`sk-${ props.kind }`]: true,
116
+ 'sk-sidebar-right': props.side === 'right',
91
117
  };
92
118
  });
119
+
120
+ // Bridge custom colors to sidebar item theming
121
+ const sidebarStyles = computed(() =>
122
+ {
123
+ const styles : Record<string, string> = {};
124
+ if(props.baseColor)
125
+ {
126
+ styles['--sk-sidebar-color-base'] = props.baseColor;
127
+ }
128
+ return styles;
129
+ });
93
130
  </script>
94
131
 
95
132
  <!--------------------------------------------------------------------------------------------------------------------->
@@ -1,3 +1,5 @@
1
1
  import type { ComponentKind } from '@/types';
2
2
 
3
3
  export type SkSidebarKind = ComponentKind;
4
+
5
+ export type SkSidebarSide = 'left' | 'right';