@sewadah/selaras 0.2.0 → 0.3.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.
- package/README.md +36 -7
- package/dist/module.json +1 -1
- package/dist/module.mjs +31 -15
- package/dist/runtime/components/Autocomplete.vue +3 -1
- package/dist/runtime/components/Badge.d.vue.ts +1 -1
- package/dist/runtime/components/Badge.vue.d.ts +1 -1
- package/dist/runtime/components/Calendar.d.vue.ts +69 -0
- package/dist/runtime/components/Calendar.vue +163 -0
- package/dist/runtime/components/Calendar.vue.d.ts +69 -0
- package/dist/runtime/components/Callout.vue +12 -1
- package/dist/runtime/components/CodeGroup.vue +13 -3
- package/dist/runtime/components/CodeTree.d.vue.ts +2 -0
- package/dist/runtime/components/CodeTree.vue +52 -1
- package/dist/runtime/components/CodeTree.vue.d.ts +2 -0
- package/dist/runtime/components/ColorPicker.d.vue.ts +8 -0
- package/dist/runtime/components/ColorPicker.vue +66 -45
- package/dist/runtime/components/ColorPicker.vue.d.ts +8 -0
- package/dist/runtime/components/Column.js +1 -1
- package/dist/runtime/components/CommandPalette.d.vue.ts +3 -1
- package/dist/runtime/components/CommandPalette.vue +5 -0
- package/dist/runtime/components/CommandPalette.vue.d.ts +3 -1
- package/dist/runtime/components/ContentNavigation.d.vue.ts +8 -1
- package/dist/runtime/components/ContentNavigation.vue +9 -2
- package/dist/runtime/components/ContentNavigation.vue.d.ts +8 -1
- package/dist/runtime/components/ContentToc.vue +14 -7
- package/dist/runtime/components/ContextMenu.d.vue.ts +8 -1
- package/dist/runtime/components/ContextMenu.vue +16 -4
- package/dist/runtime/components/ContextMenu.vue.d.ts +8 -1
- package/dist/runtime/components/DatePicker.d.vue.ts +10 -3
- package/dist/runtime/components/DatePicker.vue +24 -16
- package/dist/runtime/components/DatePicker.vue.d.ts +10 -3
- package/dist/runtime/components/Dropdown.d.vue.ts +33 -6
- package/dist/runtime/components/Dropdown.vue +143 -11
- package/dist/runtime/components/Dropdown.vue.d.ts +33 -6
- package/dist/runtime/components/FileTree.d.vue.ts +2 -0
- package/dist/runtime/components/FileTree.vue +8 -4
- package/dist/runtime/components/FileTree.vue.d.ts +2 -0
- package/dist/runtime/components/FileUpload.vue +1 -1
- package/dist/runtime/components/FormField.d.vue.ts +4 -0
- package/dist/runtime/components/FormField.vue +19 -4
- package/dist/runtime/components/FormField.vue.d.ts +4 -0
- package/dist/runtime/components/Input.vue +3 -2
- package/dist/runtime/components/Kbd.vue +12 -2
- package/dist/runtime/components/NavigationMenu.d.vue.ts +10 -10
- package/dist/runtime/components/NavigationMenu.vue +13 -7
- package/dist/runtime/components/NavigationMenu.vue.d.ts +10 -10
- package/dist/runtime/components/NavigationMenuAccordionItem.vue +5 -1
- package/dist/runtime/components/NavigationMenuFlyoutList.vue +7 -4
- package/dist/runtime/components/PinInput.vue +1 -1
- package/dist/runtime/components/Popover.d.vue.ts +8 -2
- package/dist/runtime/components/Popover.vue +9 -4
- package/dist/runtime/components/Popover.vue.d.ts +8 -2
- package/dist/runtime/components/ProsePre.d.vue.ts +6 -2
- package/dist/runtime/components/ProsePre.vue +11 -4
- package/dist/runtime/components/ProsePre.vue.d.ts +6 -2
- package/dist/runtime/components/Select.vue +3 -1
- package/dist/runtime/components/SplitterPanel.d.vue.ts +2 -2
- package/dist/runtime/components/SplitterPanel.vue.d.ts +2 -2
- package/dist/runtime/components/Table.vue +95 -26
- package/dist/runtime/components/Tabs.d.vue.ts +44 -4
- package/dist/runtime/components/Tabs.vue +171 -18
- package/dist/runtime/components/Tabs.vue.d.ts +44 -4
- package/dist/runtime/components/Textarea.vue +3 -2
- package/dist/runtime/components/Timeline.d.vue.ts +19 -0
- package/dist/runtime/components/Timeline.vue +99 -0
- package/dist/runtime/components/Timeline.vue.d.ts +19 -0
- package/dist/runtime/components/Toast.d.vue.ts +10 -1
- package/dist/runtime/components/Toast.vue +94 -8
- package/dist/runtime/components/Toast.vue.d.ts +10 -1
- package/dist/runtime/components/Tooltip.d.vue.ts +8 -2
- package/dist/runtime/components/Tooltip.vue +9 -4
- package/dist/runtime/components/Tooltip.vue.d.ts +8 -2
- package/dist/runtime/composables/use-form-field.d.ts +1 -0
- package/dist/runtime/composables/use-table.d.ts +3 -0
- package/dist/runtime/composables/use-table.js +10 -4
- package/dist/runtime/composables/use-toast.js +5 -1
- package/dist/runtime/default-colors.css +1 -1
- package/dist/runtime/directives/hotkey.d.ts +13 -0
- package/dist/runtime/directives/hotkey.js +107 -0
- package/dist/runtime/directives/index.d.ts +2 -0
- package/dist/runtime/directives/index.js +2 -0
- package/dist/runtime/directives/mask.d.ts +30 -0
- package/dist/runtime/directives/mask.js +165 -0
- package/dist/runtime/internal/ComboboxSelectBase.d.vue.ts +29 -25
- package/dist/runtime/internal/ComboboxSelectBase.vue +104 -20
- package/dist/runtime/internal/ComboboxSelectBase.vue.d.ts +29 -25
- package/dist/runtime/internal/ComboboxSelectBody.d.vue.ts +16 -11
- package/dist/runtime/internal/ComboboxSelectBody.vue +49 -10
- package/dist/runtime/internal/ComboboxSelectBody.vue.d.ts +16 -11
- package/dist/runtime/internal/DatePickerCalendarBody.vue +1 -1
- package/dist/runtime/internal/DatePickerRangeCalendarBody.vue +1 -1
- package/dist/runtime/internal/ShortcutHint.d.vue.ts +6 -0
- package/dist/runtime/internal/ShortcutHint.vue +13 -0
- package/dist/runtime/internal/ShortcutHint.vue.d.ts +6 -0
- package/dist/runtime/internal/ToastItemRenderer.d.vue.ts +9 -1
- package/dist/runtime/internal/ToastItemRenderer.vue +55 -8
- package/dist/runtime/internal/ToastItemRenderer.vue.d.ts +9 -1
- package/dist/runtime/internal/combobox-select.js +2 -1
- package/dist/runtime/internal/programmatic-services.d.ts +2 -0
- package/dist/runtime/internal/programmatic-services.js +8 -0
- package/dist/runtime/internal/toast-stack.d.ts +36 -0
- package/dist/runtime/internal/toast-stack.js +36 -0
- package/dist/runtime/structural.css +1 -1
- package/dist/runtime/table.d.ts +1 -1
- package/dist/runtime/theme/alert-dialog.js +1 -1
- package/dist/runtime/theme/avatar.js +7 -7
- package/dist/runtime/theme/badge.js +2 -2
- package/dist/runtime/theme/calendar.d.ts +86 -0
- package/dist/runtime/theme/calendar.js +19 -0
- package/dist/runtime/theme/callout.d.ts +0 -12
- package/dist/runtime/theme/callout.js +8 -8
- package/dist/runtime/theme/checkbox.js +1 -1
- package/dist/runtime/theme/code-tree.js +1 -1
- package/dist/runtime/theme/command-palette.js +1 -1
- package/dist/runtime/theme/content-navigation.d.ts +3 -0
- package/dist/runtime/theme/content-navigation.js +1 -0
- package/dist/runtime/theme/date-picker.js +1 -1
- package/dist/runtime/theme/drawer.js +1 -1
- package/dist/runtime/theme/dropdown.d.ts +21 -0
- package/dist/runtime/theme/dropdown.js +15 -2
- package/dist/runtime/theme/file-tree.js +1 -1
- package/dist/runtime/theme/file-upload.js +2 -2
- package/dist/runtime/theme/form-field.d.ts +18 -0
- package/dist/runtime/theme/form-field.js +6 -1
- package/dist/runtime/theme/icon.js +1 -1
- package/dist/runtime/theme/input-number.js +1 -1
- package/dist/runtime/theme/input.js +1 -1
- package/dist/runtime/theme/modal.js +1 -1
- package/dist/runtime/theme/pin-input.js +1 -1
- package/dist/runtime/theme/progress.js +2 -2
- package/dist/runtime/theme/prose.d.ts +6 -0
- package/dist/runtime/theme/prose.js +2 -0
- package/dist/runtime/theme/radio-group.js +4 -4
- package/dist/runtime/theme/rating.js +1 -1
- package/dist/runtime/theme/select.js +2 -2
- package/dist/runtime/theme/separator.js +1 -1
- package/dist/runtime/theme/slideover.js +1 -1
- package/dist/runtime/theme/slider.js +2 -2
- package/dist/runtime/theme/splitter-resize-handle.js +1 -1
- package/dist/runtime/theme/switch.js +1 -1
- package/dist/runtime/theme/table.d.ts +27 -0
- package/dist/runtime/theme/table.js +11 -2
- package/dist/runtime/theme/tabs.d.ts +87 -3
- package/dist/runtime/theme/tabs.js +46 -22
- package/dist/runtime/theme/textarea.js +1 -1
- package/dist/runtime/theme/timeline.d.ts +251 -0
- package/dist/runtime/theme/timeline.js +84 -0
- package/dist/runtime/theme/toast.d.ts +92 -0
- package/dist/runtime/theme/toast.js +29 -7
- package/dist/runtime/theme-config.d.ts +12 -1
- package/dist/runtime/types/index.d.ts +6 -1
- package/dist/runtime/utils/arrow.d.ts +21 -0
- package/dist/runtime/utils/arrow.js +10 -0
- package/dist/runtime/utils/color-registry.d.ts +3 -1
- package/dist/runtime/utils/color-registry.js +4 -0
- package/dist/runtime/utils/file-icons.d.ts +8 -0
- package/dist/runtime/utils/file-icons.js +133 -0
- package/dist/runtime/utils/functional-tokens.d.ts +1 -1
- package/dist/runtime/utils/functional-tokens.js +1 -1
- package/dist/runtime/utils/icons.d.ts +2 -0
- package/dist/runtime/utils/icons.js +2 -0
- package/dist/runtime/utils/messages.d.ts +6 -0
- package/dist/runtime/utils/messages.js +6 -0
- package/dist/runtime/utils/navigation-menu.d.ts +10 -0
- package/dist/runtime/utils/navigation-menu.js +3 -0
- package/dist/runtime/utils/overlay.d.ts +17 -0
- package/dist/runtime/utils/overlay.js +7 -0
- package/dist/runtime/utils/seed-colors.d.ts +2 -1
- package/dist/runtime/utils/seed-colors.js +24 -16
- package/dist/runtime/utils/select-contracts.d.ts +7 -1
- package/dist/runtime/utils/table-columns.js +1 -1
- package/dist/runtime/utils/table-contracts.d.ts +9 -1
- package/dist/runtime/utils/timeline-contracts.d.ts +42 -0
- package/dist/runtime/utils/timeline-contracts.js +0 -0
- package/dist/tokens/default-colors.tokens.json +100 -100
- package/package.json +21 -9
package/README.md
CHANGED
|
@@ -96,6 +96,26 @@ Every component is now auto-imported and ready to use:
|
|
|
96
96
|
</SButton>
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
## Theming
|
|
100
|
+
|
|
101
|
+
Selaras is CSS-first for design values.
|
|
102
|
+
|
|
103
|
+
For normal theme changes, override the public `--selaras-*` CSS variables or
|
|
104
|
+
the Selaras foundation palettes in Tailwind `@theme`. You do not need
|
|
105
|
+
`nuxt.config.ts` to change built-in roles such as `primary`, `warning`,
|
|
106
|
+
`danger`, or `neutral`.
|
|
107
|
+
|
|
108
|
+
Use `selaras.theme.colors` in `nuxt.config.ts` when you must register a new
|
|
109
|
+
role name, such as `tertiary`, or when you intentionally want to author a
|
|
110
|
+
complete build-time recipe.
|
|
111
|
+
|
|
112
|
+
`defineColorFromSeed()` is an optional accessibility-oriented generator. The
|
|
113
|
+
supplied color remains the exact resting solid fill; Selaras derives readable
|
|
114
|
+
content, interaction states, text, borders, focus, and essential indicators
|
|
115
|
+
around it. Use `defineColor()` when you need to author every leaf yourself.
|
|
116
|
+
|
|
117
|
+
See the full [Theming guide](./docs/content/overview/theming.md).
|
|
118
|
+
|
|
99
119
|
## Contribution
|
|
100
120
|
|
|
101
121
|
<details>
|
|
@@ -105,14 +125,14 @@ Every component is now auto-imported and ready to use:
|
|
|
105
125
|
# Install dependencies
|
|
106
126
|
npm install
|
|
107
127
|
|
|
108
|
-
# Generate type stubs
|
|
109
|
-
npm run
|
|
128
|
+
# Generate type stubs and prepare the docs site
|
|
129
|
+
npm run docs:prepare
|
|
110
130
|
|
|
111
|
-
# Develop with the
|
|
112
|
-
npm run dev
|
|
131
|
+
# Develop with the docs site (documentation + component showcase)
|
|
132
|
+
npm run docs:dev
|
|
113
133
|
|
|
114
|
-
#
|
|
115
|
-
npm run
|
|
134
|
+
# Generate the static docs site
|
|
135
|
+
npm run docs:generate
|
|
116
136
|
|
|
117
137
|
# Type-check
|
|
118
138
|
npm run test:types
|
|
@@ -128,8 +148,17 @@ Every component is now auto-imported and ready to use:
|
|
|
128
148
|
# Requires Bun for the temporary consumer's dependency installation
|
|
129
149
|
npm run test:compat
|
|
130
150
|
|
|
131
|
-
# Release new
|
|
151
|
+
# Release new versions (selaras, docs layer, or both)
|
|
132
152
|
npm run release
|
|
153
|
+
npm run release:selaras
|
|
154
|
+
npm run release:docs
|
|
155
|
+
|
|
156
|
+
# Each package has its own version stream, so one run can bump them at
|
|
157
|
+
# different levels - Selaras major alongside a docs patch, for example.
|
|
158
|
+
# The release workflow exposes per-package bump/version inputs for this.
|
|
159
|
+
# Locally, drive each half through its own env:
|
|
160
|
+
RELEASE_BUMP=major npm run release:selaras
|
|
161
|
+
RELEASE_BUMP=patch npm run release:docs
|
|
133
162
|
```
|
|
134
163
|
|
|
135
164
|
</details>
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -6,19 +6,19 @@ import { builtinColorNames, createColorRegistry, generateColorRoleCss } from '..
|
|
|
6
6
|
const foundationPaletteByRole = {
|
|
7
7
|
primary: "indigo",
|
|
8
8
|
secondary: "plum",
|
|
9
|
-
success: "
|
|
9
|
+
success: "green",
|
|
10
10
|
info: "blue",
|
|
11
|
-
warning: "
|
|
11
|
+
warning: "yellow",
|
|
12
12
|
danger: "red",
|
|
13
13
|
neutral: "gray"
|
|
14
14
|
};
|
|
15
15
|
const fillShadesByRole = {
|
|
16
16
|
primary: { light: [500, 600, 700], dark: [400, 300, 200] },
|
|
17
17
|
secondary: { light: [600, 700, 800], dark: [400, 300, 200] },
|
|
18
|
-
success: { light: [
|
|
18
|
+
success: { light: [500, 600, 700], dark: [500, 400, 300] },
|
|
19
19
|
info: { light: [600, 700, 800], dark: [500, 400, 300] },
|
|
20
|
-
warning: { light: [
|
|
21
|
-
danger: { light: [600, 700
|
|
20
|
+
warning: { light: [500, 600, 700], dark: [500, 400, 300] },
|
|
21
|
+
danger: { light: [500, 600, 700], dark: [500, 400, 300] },
|
|
22
22
|
neutral: { light: [950, 900, 800], dark: [200, 100, 50] }
|
|
23
23
|
};
|
|
24
24
|
function createBuiltinColorRegistry(classPrefix) {
|
|
@@ -26,29 +26,35 @@ function createBuiltinColorRegistry(classPrefix) {
|
|
|
26
26
|
const recipes = Object.fromEntries(builtinColorNames.map((role) => {
|
|
27
27
|
const mode = (dark) => {
|
|
28
28
|
const neutral = role === "neutral";
|
|
29
|
-
const
|
|
29
|
+
const brightIntent = role === "success" || role === "warning" || role === "danger";
|
|
30
30
|
const modeName = dark ? "dark" : "light";
|
|
31
31
|
const [fillShade, hoverShade, pressedShade] = fillShadesByRole[role][modeName];
|
|
32
|
-
const textShade =
|
|
32
|
+
const textShade = brightIntent ? 800 : role === "info" ? 700 : fillShade;
|
|
33
33
|
const fill = foundation(role, fillShade);
|
|
34
|
-
const hover = foundation(role, hoverShade);
|
|
35
|
-
const pressed = foundation(role, pressedShade);
|
|
34
|
+
const hover = brightIntent ? `color-mix(in oklab, ${fill} ${!dark && role === "danger" ? 98 : 94}%, ${dark ? "white" : "black"})` : foundation(role, hoverShade);
|
|
35
|
+
const pressed = brightIntent ? `color-mix(in oklab, ${fill} ${!dark && role === "danger" ? 96 : 88}%, ${dark ? "white" : "black"})` : foundation(role, pressedShade);
|
|
36
36
|
const text = foundation(role, neutral ? dark ? 50 : 950 : dark ? 300 : textShade);
|
|
37
|
-
const subtle = neutral ? foundation(role, dark ? 900 : 100) : dark ? `color-mix(in oklab, ${fill} 24%, var(--selaras-resolved-surface-default))` : foundation(role, 100);
|
|
37
|
+
const subtle = neutral ? foundation(role, dark ? 900 : 100) : dark ? `color-mix(in oklab, ${fill} ${brightIntent ? 16 : 24}%, var(--selaras-resolved-surface-default))` : foundation(role, brightIntent ? 50 : 100);
|
|
38
|
+
const indicatorShade = dark ? 400 : role === "success" || role === "warning" ? 700 : role === "danger" ? 500 : fillShade;
|
|
39
|
+
const indicator = neutral ? foundation(role, 500) : foundation(role, indicatorShade);
|
|
38
40
|
return {
|
|
39
41
|
fill,
|
|
40
42
|
fillHover: hover,
|
|
41
43
|
fillPressed: pressed,
|
|
42
|
-
|
|
44
|
+
// The vivid light-mode intent fills intentionally use white text.
|
|
45
|
+
// Dark mode restores the shared dark foreground on its lighter fills.
|
|
46
|
+
// Consumers can override onFill in their own recipe.
|
|
47
|
+
onFill: brightIntent && !dark ? "#ffffff" : foundation("neutral", dark ? 950 : 25),
|
|
48
|
+
indicator,
|
|
43
49
|
subtle,
|
|
44
|
-
subtleHover: dark ? `color-mix(in oklab, ${foundation(role, 500)} 32%, var(--selaras-resolved-surface-default))` : `color-mix(in oklab, ${subtle} 94%, ${text})`,
|
|
45
|
-
subtlePressed: dark ? `color-mix(in oklab, ${foundation(role, 500)} 40%, var(--selaras-resolved-surface-default))` : `color-mix(in oklab, ${subtle} 90%, ${text})`,
|
|
50
|
+
subtleHover: brightIntent ? `color-mix(in oklab, ${subtle} 96%, ${text})` : dark ? `color-mix(in oklab, ${foundation(role, 500)} 32%, var(--selaras-resolved-surface-default))` : `color-mix(in oklab, ${subtle} 94%, ${text})`,
|
|
51
|
+
subtlePressed: brightIntent ? `color-mix(in oklab, ${subtle} 92%, ${text})` : dark ? `color-mix(in oklab, ${foundation(role, 500)} 40%, var(--selaras-resolved-surface-default))` : `color-mix(in oklab, ${subtle} 90%, ${text})`,
|
|
46
52
|
onSubtle: text,
|
|
47
53
|
text,
|
|
48
54
|
textHover: foundation(role, neutral ? dark ? 100 : 900 : dark ? 200 : Math.min(textShade + 100, 950)),
|
|
49
55
|
textPressed: foundation(role, neutral ? dark ? 200 : 800 : dark ? 100 : Math.min(textShade + 200, 950)),
|
|
50
|
-
border:
|
|
51
|
-
focus: text
|
|
56
|
+
border: indicator,
|
|
57
|
+
focus: brightIntent ? indicator : text
|
|
52
58
|
};
|
|
53
59
|
};
|
|
54
60
|
return [role, { light: mode(false), dark: mode(true) }];
|
|
@@ -132,6 +138,16 @@ const module$1 = defineNuxtModule({
|
|
|
132
138
|
from: resolver.resolve("./runtime/directives/ripple"),
|
|
133
139
|
meta: { vueDirective: true }
|
|
134
140
|
});
|
|
141
|
+
addImports({
|
|
142
|
+
name: "vMask",
|
|
143
|
+
from: resolver.resolve("./runtime/directives/mask"),
|
|
144
|
+
meta: { vueDirective: true }
|
|
145
|
+
});
|
|
146
|
+
addImports({
|
|
147
|
+
name: "vHotkey",
|
|
148
|
+
from: resolver.resolve("./runtime/directives/hotkey"),
|
|
149
|
+
meta: { vueDirective: true }
|
|
150
|
+
});
|
|
135
151
|
addTemplate({
|
|
136
152
|
filename: "selaras-class-prefix.mjs",
|
|
137
153
|
getContents: () => `export const classPrefix = ${JSON.stringify(options.classPrefix ?? null)}
|
|
@@ -30,7 +30,9 @@ const props = defineProps({
|
|
|
30
30
|
searchTerm: { type: String, required: false },
|
|
31
31
|
resetSearchTermOnBlur: { type: Boolean, required: false },
|
|
32
32
|
resetSearchTermOnSelect: { type: Boolean, required: false },
|
|
33
|
-
arrow: { type: Boolean, required: false },
|
|
33
|
+
arrow: { type: [Boolean, Object], required: false },
|
|
34
|
+
positioning: { type: Object, required: false },
|
|
35
|
+
portal: { type: [Boolean, String], required: false, skipCheck: true, default: void 0 },
|
|
34
36
|
adaptive: { type: Boolean, required: false },
|
|
35
37
|
ui: { type: Object, required: false }
|
|
36
38
|
});
|
|
@@ -8,7 +8,7 @@ export interface BadgeProps {
|
|
|
8
8
|
label?: string;
|
|
9
9
|
icon?: string;
|
|
10
10
|
trailingIcon?: string;
|
|
11
|
-
/** A status circle. Inline dots use the badge foreground; standalone dots use the role
|
|
11
|
+
/** A status circle. Inline dots use the badge foreground; standalone dots use the role indicator. Supply an accessible name when no label is visible. */
|
|
12
12
|
dot?: boolean;
|
|
13
13
|
color?: ColorRole;
|
|
14
14
|
variant?: BadgeVariants['variant'];
|
|
@@ -8,7 +8,7 @@ export interface BadgeProps {
|
|
|
8
8
|
label?: string;
|
|
9
9
|
icon?: string;
|
|
10
10
|
trailingIcon?: string;
|
|
11
|
-
/** A status circle. Inline dots use the badge foreground; standalone dots use the role
|
|
11
|
+
/** A status circle. Inline dots use the badge foreground; standalone dots use the role indicator. Supply an accessible name when no label is visible. */
|
|
12
12
|
dot?: boolean;
|
|
13
13
|
color?: ColorRole;
|
|
14
14
|
variant?: BadgeVariants['variant'];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { DateValue } from '@internationalized/date';
|
|
2
|
+
import type { CalendarThemeSlots } from '../theme/calendar.js';
|
|
3
|
+
import type { ColorRole } from '../utils/color-registry.js';
|
|
4
|
+
import type { UiProp } from '../utils/ui.js';
|
|
5
|
+
type __VLS_Slots = {
|
|
6
|
+
'day'?: (props: CalendarDaySlotProps) => any;
|
|
7
|
+
/** Renders beside the date trigger within its grid cell. Safe for event links/buttons. */
|
|
8
|
+
'day-details'?: (props: {
|
|
9
|
+
date: DateValue;
|
|
10
|
+
month: DateValue;
|
|
11
|
+
}) => any;
|
|
12
|
+
'footer'?: () => any;
|
|
13
|
+
};
|
|
14
|
+
export interface CalendarProps {
|
|
15
|
+
modelValue?: DateValue;
|
|
16
|
+
defaultValue?: DateValue;
|
|
17
|
+
/** The displayed month when controlled independently of the selected date. */
|
|
18
|
+
placeholder?: DateValue;
|
|
19
|
+
defaultPlaceholder?: DateValue;
|
|
20
|
+
minValue?: DateValue;
|
|
21
|
+
maxValue?: DateValue;
|
|
22
|
+
isDateDisabled?: (date: DateValue) => boolean;
|
|
23
|
+
isDateUnavailable?: (date: DateValue) => boolean;
|
|
24
|
+
locale?: string;
|
|
25
|
+
numberOfMonths?: number;
|
|
26
|
+
pagedNavigation?: boolean;
|
|
27
|
+
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
28
|
+
weekdayFormat?: 'narrow' | 'short' | 'long';
|
|
29
|
+
fixedWeeks?: boolean;
|
|
30
|
+
disableDaysOutsideCurrentView?: boolean;
|
|
31
|
+
preventDeselect?: boolean;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
readonly?: boolean;
|
|
34
|
+
initialFocus?: boolean;
|
|
35
|
+
calendarLabel?: string;
|
|
36
|
+
color?: ColorRole;
|
|
37
|
+
activeColor?: ColorRole;
|
|
38
|
+
ui?: UiProp<CalendarThemeSlots>;
|
|
39
|
+
}
|
|
40
|
+
export interface CalendarEmits {
|
|
41
|
+
'update:modelValue': [value: DateValue | undefined];
|
|
42
|
+
'update:placeholder': [value: DateValue];
|
|
43
|
+
}
|
|
44
|
+
export interface CalendarDaySlotProps {
|
|
45
|
+
date: DateValue;
|
|
46
|
+
month: DateValue;
|
|
47
|
+
dayValue: string;
|
|
48
|
+
selected: boolean;
|
|
49
|
+
today: boolean;
|
|
50
|
+
disabled: boolean;
|
|
51
|
+
unavailable: boolean;
|
|
52
|
+
outsideView: boolean;
|
|
53
|
+
outsideVisibleView: boolean;
|
|
54
|
+
}
|
|
55
|
+
declare const __VLS_base: import("vue").DefineComponent<CalendarProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
56
|
+
"update:modelValue": (value: DateValue | undefined) => any;
|
|
57
|
+
"update:placeholder": (value: DateValue) => any;
|
|
58
|
+
}, string, import("vue").PublicProps, Readonly<CalendarProps> & Readonly<{
|
|
59
|
+
"onUpdate:modelValue"?: ((value: DateValue | undefined) => any) | undefined;
|
|
60
|
+
"onUpdate:placeholder"?: ((value: DateValue) => any) | undefined;
|
|
61
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
62
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
63
|
+
declare const _default: typeof __VLS_export;
|
|
64
|
+
export default _default;
|
|
65
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
66
|
+
new (): {
|
|
67
|
+
$slots: S;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import {
|
|
3
|
+
CalendarCell,
|
|
4
|
+
CalendarCellTrigger,
|
|
5
|
+
CalendarGrid,
|
|
6
|
+
CalendarGridBody,
|
|
7
|
+
CalendarGridHead,
|
|
8
|
+
CalendarGridRow,
|
|
9
|
+
CalendarHeadCell,
|
|
10
|
+
CalendarHeader,
|
|
11
|
+
CalendarHeading,
|
|
12
|
+
CalendarNext,
|
|
13
|
+
CalendarPrev,
|
|
14
|
+
CalendarRoot
|
|
15
|
+
} from "reka-ui";
|
|
16
|
+
import { computed, getCurrentInstance, useId, useSlots } from "vue";
|
|
17
|
+
import { useIcons } from "../composables/use-icons";
|
|
18
|
+
import { useMessages } from "../composables/use-messages";
|
|
19
|
+
import { calendarTheme } from "../theme/calendar";
|
|
20
|
+
import { resolveRegisteredColorRole } from "../utils/registered-colors";
|
|
21
|
+
import { resolveSlot, useComponentTheme, useRootProps } from "../utils/ui";
|
|
22
|
+
import Button from "./Button.vue";
|
|
23
|
+
defineOptions({ inheritAttrs: false });
|
|
24
|
+
const props = defineProps({
|
|
25
|
+
modelValue: { type: Object, required: false },
|
|
26
|
+
defaultValue: { type: Object, required: false },
|
|
27
|
+
placeholder: { type: Object, required: false },
|
|
28
|
+
defaultPlaceholder: { type: Object, required: false },
|
|
29
|
+
minValue: { type: Object, required: false },
|
|
30
|
+
maxValue: { type: Object, required: false },
|
|
31
|
+
isDateDisabled: { type: Function, required: false },
|
|
32
|
+
isDateUnavailable: { type: Function, required: false },
|
|
33
|
+
locale: { type: String, required: false },
|
|
34
|
+
numberOfMonths: { type: Number, required: false },
|
|
35
|
+
pagedNavigation: { type: Boolean, required: false },
|
|
36
|
+
weekStartsOn: { type: Number, required: false },
|
|
37
|
+
weekdayFormat: { type: String, required: false },
|
|
38
|
+
fixedWeeks: { type: Boolean, required: false },
|
|
39
|
+
disableDaysOutsideCurrentView: { type: Boolean, required: false },
|
|
40
|
+
preventDeselect: { type: Boolean, required: false },
|
|
41
|
+
disabled: { type: Boolean, required: false },
|
|
42
|
+
readonly: { type: Boolean, required: false },
|
|
43
|
+
initialFocus: { type: Boolean, required: false },
|
|
44
|
+
calendarLabel: { type: String, required: false },
|
|
45
|
+
color: { type: null, required: false, default: "neutral" },
|
|
46
|
+
activeColor: { type: null, required: false, default: "primary" },
|
|
47
|
+
ui: { type: Object, required: false }
|
|
48
|
+
});
|
|
49
|
+
const emit = defineEmits(["update:modelValue", "update:placeholder"]);
|
|
50
|
+
defineSlots();
|
|
51
|
+
const slots = useSlots();
|
|
52
|
+
const instance = getCurrentInstance();
|
|
53
|
+
const detailsId = useId();
|
|
54
|
+
const icons = useIcons();
|
|
55
|
+
const messages = useMessages();
|
|
56
|
+
const theme = useComponentTheme("calendar", calendarTheme);
|
|
57
|
+
const ui = computed(() => theme.value());
|
|
58
|
+
const color = computed(() => resolveRegisteredColorRole(props.color, "neutral"));
|
|
59
|
+
const activeColor = computed(() => resolveRegisteredColorRole(props.activeColor, "primary"));
|
|
60
|
+
const rootModelValue = computed(() => {
|
|
61
|
+
const vnodeProps = instance.vnode.props ?? {};
|
|
62
|
+
const isControlled = Object.hasOwn(vnodeProps, "modelValue") || Object.hasOwn(vnodeProps, "model-value");
|
|
63
|
+
return isControlled && props.modelValue === void 0 ? null : props.modelValue;
|
|
64
|
+
});
|
|
65
|
+
const rootProps = useRootProps(() => ui.value.root, () => props.ui?.root);
|
|
66
|
+
const headerProps = computed(() => resolveSlot(ui.value.header, props.ui?.header));
|
|
67
|
+
const headingProps = computed(() => resolveSlot(ui.value.heading, props.ui?.heading));
|
|
68
|
+
const gridsProps = computed(() => resolveSlot(ui.value.grids, props.ui?.grids));
|
|
69
|
+
const gridProps = computed(() => resolveSlot(ui.value.grid, props.ui?.grid));
|
|
70
|
+
const gridHeadProps = computed(() => resolveSlot(ui.value.gridHead, props.ui?.gridHead));
|
|
71
|
+
const headCellProps = computed(() => resolveSlot(ui.value.headCell, props.ui?.headCell));
|
|
72
|
+
const cellProps = computed(() => resolveSlot(ui.value.cell, props.ui?.cell));
|
|
73
|
+
const dayButtonProps = computed(() => resolveSlot(ui.value.dayButton, props.ui?.dayButton));
|
|
74
|
+
const dayDetailsProps = computed(() => resolveSlot(ui.value.dayDetails, props.ui?.dayDetails));
|
|
75
|
+
const footerProps = computed(() => resolveSlot(ui.value.footer, props.ui?.footer));
|
|
76
|
+
const navButtonUi = { leadingIcon: "rtl:-scale-x-100" };
|
|
77
|
+
const dayDetailsId = (month, date) => `${detailsId}-${month.toString()}-${date.toString()}`;
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<template>
|
|
81
|
+
<CalendarRoot
|
|
82
|
+
:model-value="rootModelValue"
|
|
83
|
+
:default-value="defaultValue"
|
|
84
|
+
:placeholder="placeholder"
|
|
85
|
+
:default-placeholder="defaultPlaceholder"
|
|
86
|
+
:min-value="minValue"
|
|
87
|
+
:max-value="maxValue"
|
|
88
|
+
:is-date-disabled="isDateDisabled"
|
|
89
|
+
:is-date-unavailable="isDateUnavailable"
|
|
90
|
+
:locale="locale"
|
|
91
|
+
:number-of-months="numberOfMonths"
|
|
92
|
+
:paged-navigation="pagedNavigation"
|
|
93
|
+
:week-starts-on="weekStartsOn"
|
|
94
|
+
:weekday-format="weekdayFormat"
|
|
95
|
+
:fixed-weeks="fixedWeeks"
|
|
96
|
+
:disable-days-outside-current-view="disableDaysOutsideCurrentView"
|
|
97
|
+
:prevent-deselect="preventDeselect"
|
|
98
|
+
:disabled="disabled"
|
|
99
|
+
:readonly="readonly"
|
|
100
|
+
:initial-focus="initialFocus"
|
|
101
|
+
:calendar-label="calendarLabel"
|
|
102
|
+
:data-selaras-color="activeColor"
|
|
103
|
+
v-bind="rootProps"
|
|
104
|
+
@update:model-value="(value) => emit('update:modelValue', value)"
|
|
105
|
+
@update:placeholder="(value) => emit('update:placeholder', value)"
|
|
106
|
+
>
|
|
107
|
+
<template #default="{ grid, weekDays }">
|
|
108
|
+
<CalendarHeader v-bind="headerProps">
|
|
109
|
+
<CalendarPrev as-child>
|
|
110
|
+
<Button variant="ghost" :color="color" size="sm" :icon="icons.chevronLeft" :aria-label="messages.previousMonth" :ui="navButtonUi" />
|
|
111
|
+
</CalendarPrev>
|
|
112
|
+
<CalendarHeading v-bind="headingProps" />
|
|
113
|
+
<CalendarNext as-child>
|
|
114
|
+
<Button variant="ghost" :color="color" size="sm" :icon="icons.chevronRight" :aria-label="messages.nextMonth" :ui="navButtonUi" />
|
|
115
|
+
</CalendarNext>
|
|
116
|
+
</CalendarHeader>
|
|
117
|
+
|
|
118
|
+
<div v-bind="gridsProps">
|
|
119
|
+
<CalendarGrid v-for="month in grid" :key="month.value.toString()" v-bind="gridProps">
|
|
120
|
+
<CalendarGridHead v-bind="gridHeadProps">
|
|
121
|
+
<CalendarGridRow>
|
|
122
|
+
<CalendarHeadCell v-for="(day, index) in weekDays" :key="index" v-bind="headCellProps">
|
|
123
|
+
{{ day }}
|
|
124
|
+
</CalendarHeadCell>
|
|
125
|
+
</CalendarGridRow>
|
|
126
|
+
</CalendarGridHead>
|
|
127
|
+
<CalendarGridBody>
|
|
128
|
+
<CalendarGridRow v-for="(week, weekIndex) in month.rows" :key="weekIndex">
|
|
129
|
+
<CalendarCell v-for="date in week" :key="date.toString()" :date="date" v-bind="cellProps">
|
|
130
|
+
<CalendarCellTrigger
|
|
131
|
+
v-slot="dayState"
|
|
132
|
+
:day="date"
|
|
133
|
+
:month="month.value"
|
|
134
|
+
as-child
|
|
135
|
+
>
|
|
136
|
+
<Button
|
|
137
|
+
:variant="dayState.selected ? 'solid' : 'ghost'"
|
|
138
|
+
:color="dayState.selected ? activeColor : color"
|
|
139
|
+
size="sm"
|
|
140
|
+
square
|
|
141
|
+
:disabled="dayState.disabled"
|
|
142
|
+
:aria-describedby="slots['day-details'] ? dayDetailsId(month.value, date) : void 0"
|
|
143
|
+
v-bind="dayButtonProps"
|
|
144
|
+
>
|
|
145
|
+
<slot name="day" :date="date" :month="month.value" v-bind="dayState">
|
|
146
|
+
{{ dayState.dayValue }}
|
|
147
|
+
</slot>
|
|
148
|
+
</Button>
|
|
149
|
+
</CalendarCellTrigger>
|
|
150
|
+
<div v-if="slots['day-details']" :id="dayDetailsId(month.value, date)" v-bind="dayDetailsProps">
|
|
151
|
+
<slot name="day-details" :date="date" :month="month.value" />
|
|
152
|
+
</div>
|
|
153
|
+
</CalendarCell>
|
|
154
|
+
</CalendarGridRow>
|
|
155
|
+
</CalendarGridBody>
|
|
156
|
+
</CalendarGrid>
|
|
157
|
+
</div>
|
|
158
|
+
<div v-if="slots.footer" v-bind="footerProps">
|
|
159
|
+
<slot name="footer" />
|
|
160
|
+
</div>
|
|
161
|
+
</template>
|
|
162
|
+
</CalendarRoot>
|
|
163
|
+
</template>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { DateValue } from '@internationalized/date';
|
|
2
|
+
import type { CalendarThemeSlots } from '../theme/calendar.js';
|
|
3
|
+
import type { ColorRole } from '../utils/color-registry.js';
|
|
4
|
+
import type { UiProp } from '../utils/ui.js';
|
|
5
|
+
type __VLS_Slots = {
|
|
6
|
+
'day'?: (props: CalendarDaySlotProps) => any;
|
|
7
|
+
/** Renders beside the date trigger within its grid cell. Safe for event links/buttons. */
|
|
8
|
+
'day-details'?: (props: {
|
|
9
|
+
date: DateValue;
|
|
10
|
+
month: DateValue;
|
|
11
|
+
}) => any;
|
|
12
|
+
'footer'?: () => any;
|
|
13
|
+
};
|
|
14
|
+
export interface CalendarProps {
|
|
15
|
+
modelValue?: DateValue;
|
|
16
|
+
defaultValue?: DateValue;
|
|
17
|
+
/** The displayed month when controlled independently of the selected date. */
|
|
18
|
+
placeholder?: DateValue;
|
|
19
|
+
defaultPlaceholder?: DateValue;
|
|
20
|
+
minValue?: DateValue;
|
|
21
|
+
maxValue?: DateValue;
|
|
22
|
+
isDateDisabled?: (date: DateValue) => boolean;
|
|
23
|
+
isDateUnavailable?: (date: DateValue) => boolean;
|
|
24
|
+
locale?: string;
|
|
25
|
+
numberOfMonths?: number;
|
|
26
|
+
pagedNavigation?: boolean;
|
|
27
|
+
weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
28
|
+
weekdayFormat?: 'narrow' | 'short' | 'long';
|
|
29
|
+
fixedWeeks?: boolean;
|
|
30
|
+
disableDaysOutsideCurrentView?: boolean;
|
|
31
|
+
preventDeselect?: boolean;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
readonly?: boolean;
|
|
34
|
+
initialFocus?: boolean;
|
|
35
|
+
calendarLabel?: string;
|
|
36
|
+
color?: ColorRole;
|
|
37
|
+
activeColor?: ColorRole;
|
|
38
|
+
ui?: UiProp<CalendarThemeSlots>;
|
|
39
|
+
}
|
|
40
|
+
export interface CalendarEmits {
|
|
41
|
+
'update:modelValue': [value: DateValue | undefined];
|
|
42
|
+
'update:placeholder': [value: DateValue];
|
|
43
|
+
}
|
|
44
|
+
export interface CalendarDaySlotProps {
|
|
45
|
+
date: DateValue;
|
|
46
|
+
month: DateValue;
|
|
47
|
+
dayValue: string;
|
|
48
|
+
selected: boolean;
|
|
49
|
+
today: boolean;
|
|
50
|
+
disabled: boolean;
|
|
51
|
+
unavailable: boolean;
|
|
52
|
+
outsideView: boolean;
|
|
53
|
+
outsideVisibleView: boolean;
|
|
54
|
+
}
|
|
55
|
+
declare const __VLS_base: import("vue").DefineComponent<CalendarProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
56
|
+
"update:modelValue": (value: DateValue | undefined) => any;
|
|
57
|
+
"update:placeholder": (value: DateValue) => any;
|
|
58
|
+
}, string, import("vue").PublicProps, Readonly<CalendarProps> & Readonly<{
|
|
59
|
+
"onUpdate:modelValue"?: ((value: DateValue | undefined) => any) | undefined;
|
|
60
|
+
"onUpdate:placeholder"?: ((value: DateValue) => any) | undefined;
|
|
61
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
62
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
63
|
+
declare const _default: typeof __VLS_export;
|
|
64
|
+
export default _default;
|
|
65
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
66
|
+
new (): {
|
|
67
|
+
$slots: S;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
@@ -24,6 +24,13 @@ const iconName = computed(() => {
|
|
|
24
24
|
return void 0;
|
|
25
25
|
return props.icon ?? icons.value[DEFAULT_ICON[props.type ?? "note"]];
|
|
26
26
|
});
|
|
27
|
+
const colorForType = {
|
|
28
|
+
note: "info",
|
|
29
|
+
tip: "success",
|
|
30
|
+
warning: "warning",
|
|
31
|
+
danger: "danger"
|
|
32
|
+
};
|
|
33
|
+
const effectiveColor = computed(() => colorForType[props.type ?? "note"]);
|
|
27
34
|
const theme = useComponentTheme("callout", calloutTheme);
|
|
28
35
|
const ui = computed(() => theme.value({ type: props.type }));
|
|
29
36
|
const rootProps = useRootProps(() => ui.value.root, () => props.ui?.root);
|
|
@@ -33,7 +40,7 @@ const titleProps = computed(() => resolveSlot(ui.value.title, props.ui?.title));
|
|
|
33
40
|
</script>
|
|
34
41
|
|
|
35
42
|
<template>
|
|
36
|
-
<div v-bind="rootProps" role="note">
|
|
43
|
+
<div v-bind="rootProps" data-selaras-callout :data-selaras-color="effectiveColor" role="note">
|
|
37
44
|
<Icon v-if="iconName" :name="iconName" v-bind="iconProps" />
|
|
38
45
|
<div v-bind="contentProps">
|
|
39
46
|
<p v-if="title || $slots.title" v-bind="titleProps">
|
|
@@ -45,3 +52,7 @@ const titleProps = computed(() => resolveSlot(ui.value.title, props.ui?.title));
|
|
|
45
52
|
</div>
|
|
46
53
|
</div>
|
|
47
54
|
</template>
|
|
55
|
+
|
|
56
|
+
<style scoped>
|
|
57
|
+
[data-selaras-callout]:before{-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude}
|
|
58
|
+
</style>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { cloneVNode, Comment, computed, Fragment, ref, Text, useSlots } from "vue";
|
|
3
3
|
import { useMessages } from "../composables/use-messages";
|
|
4
|
+
import { resolveFileIcon } from "../utils/file-icons";
|
|
5
|
+
import ProsePre from "./ProsePre.vue";
|
|
4
6
|
import Tabs from "./Tabs.vue";
|
|
5
7
|
const slots = useSlots();
|
|
6
8
|
const messages = useMessages();
|
|
@@ -22,15 +24,23 @@ function labelFor(child, index) {
|
|
|
22
24
|
const props = child.props ?? {};
|
|
23
25
|
return props.filename || props.language || messages.value.codeTabFallback(index + 1);
|
|
24
26
|
}
|
|
27
|
+
function iconFor(child) {
|
|
28
|
+
const props = child.props ?? {};
|
|
29
|
+
return props.icon ?? resolveFileIcon(props.language, props.filename);
|
|
30
|
+
}
|
|
31
|
+
function cloneForTab(child) {
|
|
32
|
+
return cloneVNode(child, child.type === ProsePre ? { showHeader: false } : void 0);
|
|
33
|
+
}
|
|
25
34
|
const tabItems = computed(() => children.value.map((child, index) => ({
|
|
26
35
|
label: labelFor(child, index),
|
|
27
|
-
value: String(index)
|
|
36
|
+
value: String(index),
|
|
37
|
+
icon: iconFor(child)
|
|
28
38
|
})));
|
|
29
39
|
const activeTab = ref("0");
|
|
30
40
|
</script>
|
|
31
41
|
|
|
32
42
|
<template>
|
|
33
|
-
<Tabs v-model="activeTab" :items="tabItems">
|
|
43
|
+
<Tabs v-model="activeTab" :items="tabItems" variant="pill">
|
|
34
44
|
<template v-for="(child, index) in children" :key="index" #[String(index)]>
|
|
35
45
|
<!--
|
|
36
46
|
cloneVNode is required, not optional: this vnode was captured from
|
|
@@ -40,7 +50,7 @@ const activeTab = ref("0");
|
|
|
40
50
|
hydration-shape mismatch (Fragment vs text) the first time this
|
|
41
51
|
panel mounts. Cloning gives it a fresh identity for its new spot.
|
|
42
52
|
-->
|
|
43
|
-
<component :is="() =>
|
|
53
|
+
<component :is="() => cloneForTab(child)" />
|
|
44
54
|
</template>
|
|
45
55
|
</Tabs>
|
|
46
56
|
</template>
|
|
@@ -4,6 +4,8 @@ import type { FileTreeNode } from './FileTree.vue.js';
|
|
|
4
4
|
export interface CodeTreeFile extends FileTreeNode {
|
|
5
5
|
/** The file's content - read for a leaf node once selected, ignored for a directory. */
|
|
6
6
|
code?: string;
|
|
7
|
+
/** Overrides the language inferred from the filename. */
|
|
8
|
+
language?: string;
|
|
7
9
|
children?: CodeTreeFile[];
|
|
8
10
|
}
|
|
9
11
|
export interface CodeTreeProps {
|
|
@@ -21,6 +21,56 @@ function findFirstFile(nodes) {
|
|
|
21
21
|
return void 0;
|
|
22
22
|
}
|
|
23
23
|
const selected = ref(findFirstFile(props.items));
|
|
24
|
+
const highlightedCode = ref("");
|
|
25
|
+
const languageByExtension = {
|
|
26
|
+
bash: "bash",
|
|
27
|
+
cjs: "javascript",
|
|
28
|
+
css: "css",
|
|
29
|
+
htm: "html",
|
|
30
|
+
html: "html",
|
|
31
|
+
js: "javascript",
|
|
32
|
+
jsx: "jsx",
|
|
33
|
+
json: "json",
|
|
34
|
+
jsonc: "jsonc",
|
|
35
|
+
md: "markdown",
|
|
36
|
+
mjs: "javascript",
|
|
37
|
+
mts: "typescript",
|
|
38
|
+
sh: "bash",
|
|
39
|
+
ts: "typescript",
|
|
40
|
+
tsx: "tsx",
|
|
41
|
+
vue: "vue",
|
|
42
|
+
yaml: "yaml",
|
|
43
|
+
yml: "yaml"
|
|
44
|
+
};
|
|
45
|
+
function languageFor(file) {
|
|
46
|
+
if (file.language)
|
|
47
|
+
return file.language;
|
|
48
|
+
const extension = file.name.split(".").pop()?.toLowerCase() ?? "";
|
|
49
|
+
return languageByExtension[extension] ?? "text";
|
|
50
|
+
}
|
|
51
|
+
watch(selected, async (file, _previous, onCleanup) => {
|
|
52
|
+
let stale = false;
|
|
53
|
+
onCleanup(() => {
|
|
54
|
+
stale = true;
|
|
55
|
+
});
|
|
56
|
+
highlightedCode.value = "";
|
|
57
|
+
if (!file?.code)
|
|
58
|
+
return;
|
|
59
|
+
try {
|
|
60
|
+
const { codeToHtml } = await import("shiki");
|
|
61
|
+
const html = await codeToHtml(file.code, {
|
|
62
|
+
lang: languageFor(file),
|
|
63
|
+
themes: { light: "github-light", dark: "github-dark" },
|
|
64
|
+
defaultColor: false
|
|
65
|
+
});
|
|
66
|
+
if (stale)
|
|
67
|
+
return;
|
|
68
|
+
highlightedCode.value = html.match(/<code(?:\s[^>]*)?>([\s\S]*?)<\/code>/)?.[1] ?? "";
|
|
69
|
+
} catch {
|
|
70
|
+
if (!stale)
|
|
71
|
+
highlightedCode.value = "";
|
|
72
|
+
}
|
|
73
|
+
}, { immediate: true });
|
|
24
74
|
watch(() => props.items, () => {
|
|
25
75
|
selected.value = findFirstFile(props.items);
|
|
26
76
|
});
|
|
@@ -28,6 +78,7 @@ const messages = useMessages();
|
|
|
28
78
|
const theme = useComponentTheme("codeTree", codeTreeTheme);
|
|
29
79
|
const ui = computed(() => theme.value());
|
|
30
80
|
const rootProps = useRootProps(() => ui.value.root, () => props.ui?.root);
|
|
81
|
+
const codeMode = computed(() => rootProps.value["data-selaras-mode"]);
|
|
31
82
|
const treeProps = computed(() => resolveSlot(ui.value.tree, props.ui?.tree));
|
|
32
83
|
const contentProps = computed(() => resolveSlot(ui.value.content, props.ui?.content));
|
|
33
84
|
const preProps = computed(() => resolveSlot(ui.value.pre, props.ui?.pre));
|
|
@@ -47,7 +98,7 @@ const fileTreeUi = { root: "rounded-none border-0 bg-transparent p-0" };
|
|
|
47
98
|
/>
|
|
48
99
|
</div>
|
|
49
100
|
<div v-if="selected" v-bind="contentProps">
|
|
50
|
-
<pre v-bind="preProps">{{ selected.code }}</pre>
|
|
101
|
+
<pre v-bind="preProps"><code v-if="highlightedCode" class="selaras-code-tree-code" :data-selaras-code-mode="codeMode" v-html="highlightedCode" /><code v-else>{{ selected.code }}</code></pre>
|
|
51
102
|
</div>
|
|
52
103
|
<div v-else v-bind="emptyProps">
|
|
53
104
|
{{ messages.selectFile }}
|
|
@@ -4,6 +4,8 @@ import type { FileTreeNode } from './FileTree.vue.js';
|
|
|
4
4
|
export interface CodeTreeFile extends FileTreeNode {
|
|
5
5
|
/** The file's content - read for a leaf node once selected, ignored for a directory. */
|
|
6
6
|
code?: string;
|
|
7
|
+
/** Overrides the language inferred from the filename. */
|
|
8
|
+
language?: string;
|
|
7
9
|
children?: CodeTreeFile[];
|
|
8
10
|
}
|
|
9
11
|
export interface CodeTreeProps {
|