@xleddyl/443-components-vue 0.9.0 → 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 CHANGED
@@ -509,6 +509,7 @@ A large dialog (60rem) with a section nav on the left and the content on the rig
509
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).
510
510
  - `@sign-out` adds a Sign out item at the bottom of the nav and in the account section.
511
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.
512
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.
513
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.
514
515
 
@@ -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="active.description" class="k-settings-modal-description">
105
- {{ active.description }}
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}`" :section="active" :close="close" />
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}`" :section="active" :close="close">
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;
@@ -161,6 +161,10 @@
161
161
  min-width: 0;
162
162
  }
163
163
 
164
+ .k-settings-modal-breadcrumbs {
165
+ margin-bottom: 0.25rem;
166
+ }
167
+
164
168
  .k-settings-modal-title {
165
169
  font-size: 0.9375rem;
166
170
  font-weight: var(--k-font-weight-strong, 600);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xleddyl/443-components-vue",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Compact, scoped dashboard components for Vue and Nuxt",
5
5
  "license": "MIT",
6
6
  "author": "Edoardo Alberti (https://github.com/xleddyl)",