@xleddyl/443-components-vue 0.8.5 → 0.9.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 +26 -10
- package/dist/components/templates/DashboardLayout.vue +1 -1
- package/dist/components/templates/SettingsModal.vue +41 -6
- package/dist/components/templates/SettingsModal.vue.d.ts +5 -1
- package/dist/components/templates/types.d.ts +9 -0
- package/dist/styles/layout/app-shell.css +43 -0
- package/dist/styles/overlays/command.css +1 -1
- package/dist/styles/templates/dashboard-layout.css +23 -0
- package/dist/styles/templates/settings-modal.css +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,10 +100,27 @@ Vite 8 minifies CSS with Lightning CSS. Set `build.cssTarget` to browsers that s
|
|
|
100
100
|
|
|
101
101
|
## Theming
|
|
102
102
|
|
|
103
|
-
Tokens live on `.k-root`, never on `:root`. Override them on a root element:
|
|
103
|
+
Tokens live on `.k-root`, never on `:root`. Override them on a root element: host CSS, Root props or inline style.
|
|
104
|
+
|
|
105
|
+
The brand of an app is a custom theme: a class on the outermost root, with the token overrides in host CSS. Keep the brand values in this one place, not in Root props.
|
|
106
|
+
|
|
107
|
+
```vue
|
|
108
|
+
<KRoot class="theme-acme">...</KRoot>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```css
|
|
112
|
+
.k-root.theme-acme {
|
|
113
|
+
--k-accent: #ff4d00;
|
|
114
|
+
--k-accent-contrast: #ffffff;
|
|
115
|
+
--k-font-sans: 'Inter', sans-serif;
|
|
116
|
+
--k-radius-scale: 0.75;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Use Root props for values that change at runtime (an accent that the user selects, a preview, a demo):
|
|
104
121
|
|
|
105
122
|
```vue
|
|
106
|
-
<KRoot accent="
|
|
123
|
+
<KRoot :accent="userAccent" theme="dark">
|
|
107
124
|
```
|
|
108
125
|
|
|
109
126
|
Input tokens, declared only on the outermost root so nested roots inherit them: `--k-accent`, `--k-accent-contrast`, `--k-font-sans`, `--k-font-mono`, `--k-font-size`, `--k-font-features`, `--k-line-height`, `--k-radius-scale`. Every other token (surfaces, text, lines, accent shades, status colors, radii, shadows, motion) is derived per root, so a nested root with its own `theme` or `accent` re-themes a region. The full list is in [CONVENTIONS.md](./CONVENTIONS.md#tokens).
|
|
@@ -169,14 +186,10 @@ import '@fontsource-variable/funnel-display'
|
|
|
169
186
|
import '@fontsource-variable/geist-mono'
|
|
170
187
|
```
|
|
171
188
|
|
|
172
|
-
Presets set the input tokens (accent, accent contrast and accent text, fonts, font features, radius scale) and the surface, line and text tokens.
|
|
173
|
-
|
|
174
|
-
```vue
|
|
175
|
-
<KRoot accent="#ff4d00" accent-contrast="#ffffff" :radius-scale="0.5" theme="dark">...</KRoot>
|
|
176
|
-
```
|
|
189
|
+
Presets set the input tokens (accent, accent contrast and accent text, fonts, font features, radius scale) and the surface, line and text tokens. Unlayered host CSS, Root props and inline style still win, so a custom theme on top of a preset is a few lines of host CSS (see [Theming](#theming)):
|
|
177
190
|
|
|
178
191
|
```css
|
|
179
|
-
.k-root.
|
|
192
|
+
.k-root.theme-acme {
|
|
180
193
|
--k-accent: #ff4d00;
|
|
181
194
|
--k-radius-scale: 0.5;
|
|
182
195
|
}
|
|
@@ -184,6 +197,8 @@ Presets set the input tokens (accent, accent contrast and accent text, fonts, fo
|
|
|
184
197
|
|
|
185
198
|
Semantic color props are always called `tone` and accept `'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'danger'` (type `Tone`).
|
|
186
199
|
|
|
200
|
+
Buttons show `icon` before the label and `trailingIcon` after it. Put action icons (`plus`, `trash`, `pencil`) in `icon`. Put direction icons (`arrow-right`, `chevron-right`, `arrow-top-right-on-square`) in `trailingIcon`: `<KButton trailing-icon="arrow-right">Continue</KButton>`.
|
|
201
|
+
|
|
187
202
|
## Scoping
|
|
188
203
|
|
|
189
204
|
- The reset only matches `.k-root` and its descendants, wrapped in `:where()` (zero specificity). Markup outside a root keeps browser defaults.
|
|
@@ -452,7 +467,7 @@ Ready defaults to start a dashboard project: an app frame, an auth frame and pag
|
|
|
452
467
|
| `breadcrumbs` | | Rendered in the desktop topbar. `#topbar` replaces them, `#actions` adds buttons on the right. |
|
|
453
468
|
| `search` | `false` | `true` adds a CommandPalette with every navigation item, an array adds your own command groups, an async function `(query, signal) => CommandGroup<T>[]` searches on your server (item `data` reaches `#search-item` typed) (matching navigation items come first unless `searchNavigation` is `false`). `#search` replaces it. |
|
|
454
469
|
| `searchProps` | | Options for the built-in CommandPalette: `minQueryLength`, `debounce`, `placeholder`, `label`, `emptyText` (a string or `(query) => string`), `minQueryText`, `errorText`, `loadingText`, `resultsText`, `notice`, `recentKey`, footer labels. Slots `#search-item`, `#search-empty`, `#search-group-label` and `#search-after-results` reach the palette. |
|
|
455
|
-
| `themeSwitch` | `
|
|
470
|
+
| `themeSwitch` | `false` | `'sidebar'`, `'topbar'` or `false`. The default keeps the theme choice only in the Appearance section of the settings modal. With `accountAction="menu"` there is no settings modal, so set `'sidebar'` or `'topbar'`. |
|
|
456
471
|
| `collapsible`, `v-model:collapsed`, `storageKey` | `true`, `'k-sidebar-collapsed'` | Icon rail on desktop. Persisted in a cookie under Nuxt (rendered on the server, no flash) or `localStorage` elsewhere. `false` disables persistence. |
|
|
457
472
|
| `shortcut` | `'mod+b'` | Toggles the rail on desktop and the drawer on mobile. `searchShortcut` defaults to `mod+k`. |
|
|
458
473
|
| `v-model:open` | `false` | Mobile drawer (below 768px of shell width): scrim, focus moves into the drawer, the content is `inert`, Escape and the scrim close it and focus returns to the burger. |
|
|
@@ -494,6 +509,7 @@ A large dialog (60rem) with a section nav on the left and the content on the rig
|
|
|
494
509
|
- Built-in sections, used when no `#section-<id>` slot fills them: `account` (profile card, your `#account-fields`, a sign out button) and `appearance` (`AppearanceSettings`: theme cards light, dark, system and preset cards; `presets` limits or hides them).
|
|
495
510
|
- `@sign-out` adds a Sign out item at the bottom of the nav and in the account section.
|
|
496
511
|
- Labels: `title` (accessible name of the dialog, default `Settings`), `closeLabel`, `navLabel`, `signOutLabel`, `accountLabel`, `accountText`, `appearanceLabel`, `appearanceText`, `sessionLabel`, `sessionText`, `themeLabel`, `themeText`, `presetLabel`, `presetText`.
|
|
512
|
+
- Subpages: `#section-<id>` and `#actions-<id>` get `page`, `pages`, `openPage({ id, label, description? })` and `back()`. With a page open, the header shows a breadcrumb from the section to the page, and the title and description of the page; a click on a breadcrumb goes back to it. `v-model:pages` binds the stack (it also works unbound). Changing section clears it. Render the detail when `page` is set, the list otherwise.
|
|
497
513
|
- `hide-session` hides the Session group (sign out) in the account section; the Sign out item in the nav stays. `beforeClose` works as on Modal.
|
|
498
514
|
- Accessibility: native modal `<dialog>` (focus trap, Escape, focus returns to the trigger), the nav is a labelled `nav` of buttons with `aria-current` and `aria-controls`, the content is a `section` labelled by its heading.
|
|
499
515
|
|
|
@@ -589,7 +605,7 @@ export default defineAppConfig({
|
|
|
589
605
|
],
|
|
590
606
|
signOutTo: '/logout',
|
|
591
607
|
},
|
|
592
|
-
dashboard: { variant: 'inset', search: true
|
|
608
|
+
dashboard: { variant: 'inset', search: true },
|
|
593
609
|
auth: { variant: 'split', footer: 'Acme Inc.', asideTitle: 'Run the store.' },
|
|
594
610
|
},
|
|
595
611
|
})
|
|
@@ -189,7 +189,7 @@ const props = defineProps({
|
|
|
189
189
|
searchPlaceholder: { type: String, required: false },
|
|
190
190
|
searchShortcut: { type: [String, Boolean], required: false, default: "mod+k" },
|
|
191
191
|
searchNavigationLabel: { type: String, required: false },
|
|
192
|
-
themeSwitch: { type: [String, Boolean], required: false, default:
|
|
192
|
+
themeSwitch: { type: [String, Boolean], required: false, default: false },
|
|
193
193
|
collapsible: { type: Boolean, required: false, default: true },
|
|
194
194
|
storageKey: { type: [String, Boolean], required: false, default: "k-sidebar-collapsed" },
|
|
195
195
|
shortcut: { type: [String, Boolean], required: false, default: "mod+b" },
|
|
@@ -98,15 +98,20 @@
|
|
|
98
98
|
>
|
|
99
99
|
<header class="k-settings-modal-header">
|
|
100
100
|
<div class="k-settings-modal-heading">
|
|
101
|
+
<Breadcrumbs
|
|
102
|
+
v-if="currentPage"
|
|
103
|
+
class="k-settings-modal-breadcrumbs"
|
|
104
|
+
:items="breadcrumbItems"
|
|
105
|
+
/>
|
|
101
106
|
<h2 :id="`${baseId}-title`" class="k-settings-modal-title">
|
|
102
|
-
{{ active.label }}
|
|
107
|
+
{{ currentPage?.label ?? active.label }}
|
|
103
108
|
</h2>
|
|
104
|
-
<p v-if="
|
|
105
|
-
{{
|
|
109
|
+
<p v-if="headingDescription" class="k-settings-modal-description">
|
|
110
|
+
{{ headingDescription }}
|
|
106
111
|
</p>
|
|
107
112
|
</div>
|
|
108
113
|
<div v-if="$slots[`actions-${active.id}`]" class="k-settings-modal-actions">
|
|
109
|
-
<slot :name="`actions-${active.id}`"
|
|
114
|
+
<slot :name="`actions-${active.id}`" v-bind="slotProps(active, close)" />
|
|
110
115
|
</div>
|
|
111
116
|
</header>
|
|
112
117
|
<div
|
|
@@ -117,7 +122,7 @@
|
|
|
117
122
|
'is-overflow-bottom': contentEdges.overflowBottom.value
|
|
118
123
|
}"
|
|
119
124
|
>
|
|
120
|
-
<slot :name="`section-${active.id}`"
|
|
125
|
+
<slot :name="`section-${active.id}`" v-bind="slotProps(active, close)">
|
|
121
126
|
<template v-if="active.id === 'account' && account">
|
|
122
127
|
<div class="k-settings-modal-account">
|
|
123
128
|
<Avatar
|
|
@@ -171,7 +176,7 @@
|
|
|
171
176
|
</template>
|
|
172
177
|
|
|
173
178
|
<script setup>
|
|
174
|
-
import { computed, useId, useTemplateRef } from "vue";
|
|
179
|
+
import { computed, useId, useTemplateRef, watch } from "vue";
|
|
175
180
|
import { THEME_PRESETS } from "../../composables/usePreset";
|
|
176
181
|
import { useLocale } from "../../composables/useLocale";
|
|
177
182
|
import { useLinkResolver } from "../../internal/core/link";
|
|
@@ -181,6 +186,7 @@ import Icon from "../core/Icon.vue";
|
|
|
181
186
|
import Avatar from "../data/Avatar.vue";
|
|
182
187
|
import Badge from "../data/Badge.vue";
|
|
183
188
|
import Modal from "../overlays/Modal.vue";
|
|
189
|
+
import Breadcrumbs from "../layout/Breadcrumbs.vue";
|
|
184
190
|
import AppearanceSettings from "./AppearanceSettings.vue";
|
|
185
191
|
import SettingsGroup from "./SettingsGroup.vue";
|
|
186
192
|
const props = defineProps({
|
|
@@ -209,6 +215,7 @@ defineSlots();
|
|
|
209
215
|
const emit = defineEmits(["open", "close", "afterLeave"]);
|
|
210
216
|
const open = defineModel("open", { type: Boolean, ...{ default: false } });
|
|
211
217
|
const section = defineModel("section", { type: String });
|
|
218
|
+
const pages = defineModel("pages", { type: Array, ...{ default: () => [] } });
|
|
212
219
|
const { t } = useLocale();
|
|
213
220
|
const titleText = computed(() => props.title ?? t("settings.title"));
|
|
214
221
|
const closeText = computed(() => props.closeLabel ?? t("settings.close"));
|
|
@@ -259,6 +266,34 @@ const activeId = computed(() => {
|
|
|
259
266
|
return selectable.value[0]?.id;
|
|
260
267
|
});
|
|
261
268
|
const active = computed(() => sectionList.value.find((item) => item.id === activeId.value));
|
|
269
|
+
const currentPage = computed(() => pages.value.at(-1));
|
|
270
|
+
const headingDescription = computed(
|
|
271
|
+
() => currentPage.value ? currentPage.value.description : active.value?.description
|
|
272
|
+
);
|
|
273
|
+
function goTo(depth) {
|
|
274
|
+
pages.value = pages.value.slice(0, depth);
|
|
275
|
+
}
|
|
276
|
+
function openPage(page) {
|
|
277
|
+
pages.value = [...pages.value, page];
|
|
278
|
+
}
|
|
279
|
+
function back() {
|
|
280
|
+
goTo(pages.value.length - 1);
|
|
281
|
+
}
|
|
282
|
+
const breadcrumbItems = computed(() => [
|
|
283
|
+
{ label: active.value?.label ?? "", onClick: () => goTo(0) },
|
|
284
|
+
...pages.value.map((page, index) => ({
|
|
285
|
+
label: page.label,
|
|
286
|
+
onClick: index < pages.value.length - 1 ? () => goTo(index + 1) : void 0
|
|
287
|
+
}))
|
|
288
|
+
]);
|
|
289
|
+
function slotProps(item, close) {
|
|
290
|
+
return { section: item, close, page: currentPage.value, pages: pages.value, openPage, back };
|
|
291
|
+
}
|
|
292
|
+
watch(activeId, () => goTo(0));
|
|
293
|
+
watch(
|
|
294
|
+
() => pages.value.length,
|
|
295
|
+
() => contentBody.value?.scrollTo({ top: 0 })
|
|
296
|
+
);
|
|
262
297
|
function select(id) {
|
|
263
298
|
section.value = id;
|
|
264
299
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ThemePreset } from '../../composables/usePreset';
|
|
2
2
|
import type { DialogBeforeClose } from '../overlays/types';
|
|
3
|
-
import type { SettingsModalAccount, SettingsModalSection, SettingsSectionSlotProps } from './types';
|
|
3
|
+
import type { SettingsModalAccount, SettingsModalPage, SettingsModalSection, SettingsSectionSlotProps } from './types';
|
|
4
4
|
type __VLS_Slots = {
|
|
5
5
|
'account-fields'?: (props: {
|
|
6
6
|
account: SettingsModalAccount;
|
|
@@ -33,6 +33,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
33
33
|
beforeClose?: DialogBeforeClose;
|
|
34
34
|
open?: boolean | undefined;
|
|
35
35
|
section?: string;
|
|
36
|
+
pages?: SettingsModalPage[];
|
|
36
37
|
key?: PropertyKey | undefined;
|
|
37
38
|
loading?: boolean;
|
|
38
39
|
prefix?: "k-modal" | "k-drawer" | undefined;
|
|
@@ -249,6 +250,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
249
250
|
"update:open": (value: boolean) => any;
|
|
250
251
|
afterLeave: () => any;
|
|
251
252
|
"update:section": (value: string | undefined) => any;
|
|
253
|
+
"update:pages": (value: SettingsModalPage[]) => any;
|
|
252
254
|
}, string, import("vue").PublicProps, Readonly<{
|
|
253
255
|
sections?: SettingsModalSection[];
|
|
254
256
|
account?: SettingsModalAccount;
|
|
@@ -272,6 +274,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
272
274
|
beforeClose?: DialogBeforeClose;
|
|
273
275
|
open?: boolean | undefined;
|
|
274
276
|
section?: string;
|
|
277
|
+
pages?: SettingsModalPage[];
|
|
275
278
|
key?: PropertyKey | undefined;
|
|
276
279
|
loading?: boolean;
|
|
277
280
|
prefix?: "k-modal" | "k-drawer" | undefined;
|
|
@@ -488,6 +491,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
|
|
|
488
491
|
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
489
492
|
onAfterLeave?: (() => any) | undefined;
|
|
490
493
|
"onUpdate:section"?: ((value: string | undefined) => any) | undefined;
|
|
494
|
+
"onUpdate:pages"?: ((value: SettingsModalPage[]) => any) | undefined;
|
|
491
495
|
}>, {
|
|
492
496
|
sections: SettingsModalSection[];
|
|
493
497
|
presets: false | readonly ThemePreset[];
|
|
@@ -30,9 +30,18 @@ export interface SettingsModalSection {
|
|
|
30
30
|
badge?: string | number;
|
|
31
31
|
disabled?: boolean;
|
|
32
32
|
}
|
|
33
|
+
export interface SettingsModalPage {
|
|
34
|
+
id: string;
|
|
35
|
+
label: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
}
|
|
33
38
|
export interface SettingsSectionSlotProps {
|
|
34
39
|
section: SettingsModalSection;
|
|
35
40
|
close: () => void;
|
|
41
|
+
page: SettingsModalPage | undefined;
|
|
42
|
+
pages: SettingsModalPage[];
|
|
43
|
+
openPage: (page: SettingsModalPage) => void;
|
|
44
|
+
back: () => void;
|
|
36
45
|
}
|
|
37
46
|
export interface SettingsModalAccount {
|
|
38
47
|
name: string;
|
|
@@ -203,6 +203,49 @@ html.k-root body {
|
|
|
203
203
|
transform: rotate(180deg);
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
@media (hover: hover) {
|
|
207
|
+
.k-shell.is-collapsed .k-sidebar-header:has(> .k-sidebar-brand:not(:empty)) {
|
|
208
|
+
position: relative;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.k-shell.is-collapsed
|
|
212
|
+
.k-sidebar-header:has(> .k-sidebar-brand:not(:empty))
|
|
213
|
+
> .k-sidebar-brand,
|
|
214
|
+
.k-shell.is-collapsed
|
|
215
|
+
.k-sidebar-header:has(> .k-sidebar-brand:not(:empty))
|
|
216
|
+
> .k-sidebar-collapse {
|
|
217
|
+
transition: opacity var(--k-duration) var(--k-ease);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.k-shell.is-collapsed
|
|
221
|
+
.k-sidebar-header:has(> .k-sidebar-brand:not(:empty))
|
|
222
|
+
> .k-sidebar-collapse {
|
|
223
|
+
position: absolute;
|
|
224
|
+
top: 50%;
|
|
225
|
+
left: 50%;
|
|
226
|
+
translate: -50% -50%;
|
|
227
|
+
opacity: 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.k-shell.is-collapsed
|
|
231
|
+
.k-sidebar-header:has(> .k-sidebar-brand:not(:empty)):is(
|
|
232
|
+
:hover,
|
|
233
|
+
:has(> .k-sidebar-collapse:focus-visible)
|
|
234
|
+
)
|
|
235
|
+
> .k-sidebar-brand {
|
|
236
|
+
opacity: 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.k-shell.is-collapsed
|
|
240
|
+
.k-sidebar-header:has(> .k-sidebar-brand:not(:empty)):is(
|
|
241
|
+
:hover,
|
|
242
|
+
:has(> .k-sidebar-collapse:focus-visible)
|
|
243
|
+
)
|
|
244
|
+
> .k-sidebar-collapse {
|
|
245
|
+
opacity: 1;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
206
249
|
.k-shell.is-collapsed .k-navlink {
|
|
207
250
|
justify-content: center;
|
|
208
251
|
padding-inline: 0;
|
|
@@ -55,7 +55,30 @@
|
|
|
55
55
|
@container k-shell (min-width: 768px) {
|
|
56
56
|
.k-shell.is-collapsed .k-dashboard-search.k-command-trigger {
|
|
57
57
|
justify-content: center;
|
|
58
|
+
min-width: 0;
|
|
59
|
+
height: 1.875rem;
|
|
58
60
|
padding: 0;
|
|
61
|
+
color: var(--k-text-dimmed);
|
|
62
|
+
background: transparent;
|
|
63
|
+
border-color: transparent;
|
|
64
|
+
box-shadow: none;
|
|
65
|
+
transition:
|
|
66
|
+
background-color var(--k-duration) var(--k-ease),
|
|
67
|
+
color var(--k-duration) var(--k-ease),
|
|
68
|
+
scale var(--k-duration) var(--k-ease);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.k-shell.is-collapsed .k-dashboard-search.k-command-trigger:hover {
|
|
72
|
+
color: var(--k-text-muted);
|
|
73
|
+
background: var(--k-hover);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.k-shell.is-collapsed .k-dashboard-search.k-command-trigger:active {
|
|
77
|
+
scale: 0.985;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.k-shell.is-collapsed .k-dashboard-search.k-command-trigger:focus-visible {
|
|
81
|
+
box-shadow: var(--k-focus-ring);
|
|
59
82
|
}
|
|
60
83
|
|
|
61
84
|
.k-shell.is-collapsed .k-dashboard-search .k-command-trigger-text,
|
package/package.json
CHANGED