@alfakit/vue 0.1.7 → 0.1.9
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/CHANGELOG.md +16 -0
- package/dist/components/actions/ActionModal.js.map +1 -1
- package/dist/components/actions/ActionModal.vue.d.ts.map +1 -1
- package/dist/components/actions/ActionModal.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/actions/ActionModal.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/form/FormBlock.js.map +1 -1
- package/dist/components/form/FormBlock.vue.d.ts.map +1 -1
- package/dist/components/form/FormBlock.vue_vue_type_script_setup_true_lang.js +79 -78
- package/dist/components/form/FormBlock.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/BottomNav.js.map +1 -1
- package/dist/components/panel/BottomNav.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/panel/BottomNav.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/KeyboardShortcuts.js.map +1 -1
- package/dist/components/panel/KeyboardShortcuts.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/panel/KeyboardShortcuts.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/NavGroup.js.map +1 -1
- package/dist/components/panel/NavGroup.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/panel/NavGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/NavItem.js.map +1 -1
- package/dist/components/panel/NavItem.vue.d.ts.map +1 -1
- package/dist/components/panel/NavItem.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/panel/NavItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/panel/NavSubItem.js.map +1 -1
- package/dist/components/panel/NavSubItem.vue.d.ts.map +1 -1
- package/dist/components/panel/NavSubItem.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/panel/NavSubItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ui/TooltipTrigger.js.map +1 -1
- package/dist/components/ui/TooltipTrigger.vue.d.ts +2 -2
- package/dist/components/ui/TooltipTrigger.vue.d.ts.map +1 -1
- package/dist/components/ui/TooltipTrigger.vue_vue_type_script_setup_true_lang.js +10 -7
- package/dist/components/ui/TooltipTrigger.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/eject-manifest.json +4 -2
- package/dist/index.js +2 -2
- package/dist/layouts/TopNavLayout.js.map +1 -1
- package/dist/layouts/TopNavLayout.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/layouts/TopNavLayout.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/layouts/TopNavMenu.js.map +1 -1
- package/dist/layouts/TopNavMenu.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/layouts/TopNavMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/standalone/AlfakitComponent.js.map +1 -1
- package/dist/standalone/AlfakitComponent.vue.d.ts.map +1 -1
- package/dist/standalone/AlfakitComponent.vue_vue_type_script_setup_true_lang.js +29 -24
- package/dist/standalone/AlfakitComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/standalone.css +10 -6
- package/dist/starter-kit/AlfakitStarterKitRoot.js.map +1 -1
- package/dist/starter-kit/AlfakitStarterKitRoot.vue.d.ts.map +1 -1
- package/dist/starter-kit/AlfakitStarterKitRoot.vue_vue_type_script_setup_true_lang.js +25 -21
- package/dist/starter-kit/AlfakitStarterKitRoot.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/starter-kit/index.d.ts +1 -1
- package/dist/starter-kit/index.d.ts.map +1 -1
- package/dist/starter-kit/index.js +2 -2
- package/dist/starter-kit/widgets/LanguageMenu.js +7 -0
- package/dist/starter-kit/widgets/LanguageMenu.js.map +1 -0
- package/dist/starter-kit/widgets/LanguageMenu.vue.d.ts +3 -0
- package/dist/starter-kit/widgets/LanguageMenu.vue.d.ts.map +1 -0
- package/dist/starter-kit/widgets/LanguageMenu.vue_vue_type_script_setup_true_lang.js +52 -0
- package/dist/starter-kit/widgets/LanguageMenu.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/starter-kit/widgets/LanguageSwitcherTrigger.js +7 -0
- package/dist/starter-kit/widgets/LanguageSwitcherTrigger.js.map +1 -0
- package/dist/starter-kit/widgets/LanguageSwitcherTrigger.vue.d.ts +11 -0
- package/dist/starter-kit/widgets/LanguageSwitcherTrigger.vue.d.ts.map +1 -0
- package/dist/starter-kit/widgets/LanguageSwitcherTrigger.vue_vue_type_script_setup_true_lang.js +32 -0
- package/dist/starter-kit/widgets/LanguageSwitcherTrigger.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/starter-kit/widgets/LoadedLanguageSwitcher.js +7 -0
- package/dist/starter-kit/widgets/LoadedLanguageSwitcher.js.map +1 -0
- package/dist/starter-kit/widgets/LoadedLanguageSwitcher.vue.d.ts +12 -0
- package/dist/starter-kit/widgets/LoadedLanguageSwitcher.vue.d.ts.map +1 -0
- package/dist/starter-kit/widgets/LoadedLanguageSwitcher.vue_vue_type_script_setup_true_lang.js +28 -0
- package/dist/starter-kit/widgets/LoadedLanguageSwitcher.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/starter-kit/widgets/index.d.ts +26 -0
- package/dist/starter-kit/widgets/index.d.ts.map +1 -1
- package/dist/starter-kit/widgets/index.js +19 -18
- package/dist/starter-kit/widgets/index.js.map +1 -1
- package/dist/starter-kit/widgets/loaded.d.ts +1 -0
- package/dist/starter-kit/widgets/loaded.d.ts.map +1 -1
- package/dist/starter-kit/widgets/loaded.js +5 -4
- package/dist/starter-kit/widgets/use-widgets.d.ts +2 -1
- package/dist/starter-kit/widgets/use-widgets.d.ts.map +1 -1
- package/dist/starter-kit/widgets/use-widgets.js +15 -15
- package/dist/starter-kit/widgets/use-widgets.js.map +1 -1
- package/package.json +2 -2
- package/src/components/actions/ActionModal.vue +2 -0
- package/src/components/form/FormBlock.vue +15 -2
- package/src/components/panel/BottomNav.vue +2 -2
- package/src/components/panel/KeyboardShortcuts.vue +1 -1
- package/src/components/panel/NavGroup.vue +3 -3
- package/src/components/panel/NavItem.vue +5 -1
- package/src/components/panel/NavSubItem.vue +5 -1
- package/src/components/ui/TooltipTrigger.vue +4 -1
- package/src/layouts/TopNavLayout.vue +2 -2
- package/src/layouts/TopNavMenu.vue +1 -1
- package/src/standalone/AlfakitComponent.vue +8 -1
- package/src/starter-kit/AlfakitStarterKitRoot.vue +6 -0
- package/src/starter-kit/index.ts +1 -0
- package/src/starter-kit/widgets/LanguageMenu.vue +46 -0
- package/src/starter-kit/widgets/LanguageSwitcherTrigger.vue +34 -0
- package/src/starter-kit/widgets/LoadedLanguageSwitcher.vue +27 -0
- package/src/starter-kit/widgets/index.ts +24 -2
- package/src/starter-kit/widgets/loaded.ts +1 -0
- package/src/starter-kit/widgets/use-widgets.ts +10 -5
|
@@ -3,7 +3,8 @@ import { type ComputedRef, type Ref } from 'vue';
|
|
|
3
3
|
import type { AlfakitContextValue } from '../../context';
|
|
4
4
|
/**
|
|
5
5
|
* The panel the loaded components render with, once its data (`widgets.endpoint`) has
|
|
6
|
-
* loaded: null until then.
|
|
6
|
+
* loaded: null until then. It is loaded per endpoint (a tenant switch) and language (a
|
|
7
|
+
* language switch), so the components follow a switch.
|
|
7
8
|
*/
|
|
8
9
|
export declare function useWidgetsPanel(source: Ref<StarterKitWidgets>): ComputedRef<PanelSchema | null>;
|
|
9
10
|
/** @internal The context of the loaded header components (browser only). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-widgets.d.ts","sourceRoot":"","sources":["../../../src/starter-kit/widgets/use-widgets.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"use-widgets.d.ts","sourceRoot":"","sources":["../../../src/starter-kit/widgets/use-widgets.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAwB,KAAK,WAAW,EAAE,KAAK,GAAG,EAAE,MAAM,KAAK,CAAA;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAgB,MAAM,eAAe,CAAA;AAKtE;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC,iBAAiB,CAAC,GAAG,WAAW,CAAC,WAAW,GAAG,IAAI,CAAC,CA4B/F;AAMD,4EAA4E;AAC5E,wBAAgB,qBAAqB,IAAI,mBAAmB,CAS3D"}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { ActionRunner as e, ComponentRegistry as t, Translator as n, loadStarterKitPanel as r, loadedStarterKitPanel as i,
|
|
2
|
-
import { computed as
|
|
1
|
+
import { ActionRunner as e, ComponentRegistry as t, Translator as n, loadStarterKitPanel as r, loadedStarterKitPanel as i, starterKitPanelLocale as a, starterKitWidgetsPanel as o } from "@alfakit/core";
|
|
2
|
+
import { computed as s, ref as c, watch as l } from "vue";
|
|
3
3
|
//#region src/starter-kit/widgets/use-widgets.ts
|
|
4
|
-
function
|
|
5
|
-
let t =
|
|
6
|
-
return
|
|
7
|
-
let
|
|
8
|
-
|
|
9
|
-
}, { immediate: !0 }),
|
|
4
|
+
function u(e) {
|
|
5
|
+
let t = c(0);
|
|
6
|
+
return l(() => [e.value.widgets.endpoint, a(e.value)], ([e, n], i, a) => {
|
|
7
|
+
let o = !0;
|
|
8
|
+
a(() => o = !1), r(e, n).then(() => o && t.value++, () => {});
|
|
9
|
+
}, { immediate: !0 }), s(() => {
|
|
10
10
|
t.value;
|
|
11
|
-
let n = i(e.value.widgets.endpoint);
|
|
12
|
-
return n ?
|
|
11
|
+
let n = i(e.value.widgets.endpoint, a(e.value));
|
|
12
|
+
return n ? o(n, e.value) : null;
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
-
var
|
|
16
|
-
function
|
|
17
|
-
return
|
|
15
|
+
var d = null;
|
|
16
|
+
function f() {
|
|
17
|
+
return d ??= {
|
|
18
18
|
registry: new t(),
|
|
19
19
|
pages: {},
|
|
20
20
|
translator: new n(),
|
|
@@ -22,9 +22,9 @@ function d() {
|
|
|
22
22
|
action: "",
|
|
23
23
|
modal: ""
|
|
24
24
|
} })
|
|
25
|
-
},
|
|
25
|
+
}, d;
|
|
26
26
|
}
|
|
27
27
|
//#endregion
|
|
28
|
-
export {
|
|
28
|
+
export { u as useWidgetsPanel, f as widgetsAlfakitContext };
|
|
29
29
|
|
|
30
30
|
//# sourceMappingURL=use-widgets.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-widgets.js","names":["useWidgetsPanel","version","ref","watch","source","active","onCleanup","loadStarterKitPanel","
|
|
1
|
+
{"version":3,"file":"use-widgets.js","names":["useWidgetsPanel","version","ref","watch","source","starterKitPanelLocale","endpoint","locale","active","onCleanup","loadStarterKitPanel","computed","base","loadedStarterKitPanel","starterKitWidgetsPanel","widgetsContext","widgetsAlfakitContext","ComponentRegistry","Translator","ActionRunner"],"sources":["../../../src/starter-kit/widgets/use-widgets.ts"],"sourcesContent":["import {\n ActionRunner,\n ComponentRegistry,\n Translator,\n loadStarterKitPanel,\n loadedStarterKitPanel,\n starterKitPanelLocale,\n starterKitWidgetsPanel,\n type PanelSchema,\n type StarterKitWidgets,\n} from '@alfakit/core'\nimport { computed, ref, watch, type ComputedRef, type Ref } from 'vue'\nimport type { AlfakitContextValue, AnyComponent } from '../../context'\n\n// The loaded components' panel and context; `./index` (the eager triggers) must not import\n// this module, which pulls in the action runner.\n\n/**\n * The panel the loaded components render with, once its data (`widgets.endpoint`) has\n * loaded: null until then. It is loaded per endpoint (a tenant switch) and language (a\n * language switch), so the components follow a switch.\n */\nexport function useWidgetsPanel(source: Ref<StarterKitWidgets>): ComputedRef<PanelSchema | null> {\n const version = ref(0)\n\n watch(\n () => [source.value.widgets.endpoint, starterKitPanelLocale(source.value)] as const,\n ([endpoint, locale], _old, onCleanup) => {\n let active = true\n\n onCleanup(() => (active = false))\n\n void loadStarterKitPanel(endpoint, locale).then(\n () => active && version.value++,\n () => {},\n )\n },\n { immediate: true },\n )\n\n return computed(() => {\n void version.value\n\n const base = loadedStarterKitPanel(\n source.value.widgets.endpoint,\n starterKitPanelLocale(source.value),\n )\n\n return base ? starterKitWidgetsPanel(base, source.value) : null\n })\n}\n\n// One context for the browser session: its own translator (the panel's strings), an empty\n// registry (only `panel:icon` overrides are looked up here) and an idle action runner.\nlet widgetsContext: AlfakitContextValue | null = null\n\n/** @internal The context of the loaded header components (browser only). */\nexport function widgetsAlfakitContext(): AlfakitContextValue {\n widgetsContext ??= {\n registry: new ComponentRegistry<AnyComponent>(),\n pages: {},\n translator: new Translator(),\n actions: new ActionRunner({ endpoints: { action: '', modal: '' } }),\n }\n\n return widgetsContext\n}\n"],"mappings":";;;AAsBA,SAAgBA,EAAgB,GAAiE;CAC/F,IAAMC,IAAUC,EAAI,CAAC;CAiBrB,OAfAC,QACQ,CAACC,EAAO,MAAM,QAAQ,UAAUC,EAAsBD,EAAO,KAAK,CAAC,IACxE,CAACE,GAAUC,IAAS,GAAM,MAAc;EACvC,IAAIC,IAAS;EAIb,AAFAC,QAAiB,IAAS,EAAM,GAEhCC,EAAyBJ,GAAUC,CAAM,CAAC,CAAC,WACnCC,KAAU,EAAQ,eAClB,CAAC,CACT;CACF,GACA,EAAE,WAAW,GAAK,CACpB,GAEOG,QAAe;EACpB,EAAa;EAEb,IAAMC,IAAOC,EACXT,EAAO,MAAM,QAAQ,UACrBC,EAAsBD,EAAO,KAAK,CACpC;EAEA,OAAOQ,IAAOE,EAAuBF,GAAMR,EAAO,KAAK,IAAI;CAC7D,CAAC;AACH;AAIA,IAAIW,IAA6C;AAGjD,SAAgBC,IAA6C;CAQ3D,OAPA,MAAmB;EACjB,UAAU,IAAIC,EAAgC;EAC9C,OAAO,CAAC;EACR,YAAY,IAAIC,EAAW;EAC3B,SAAS,IAAIC,EAAa,EAAE,WAAW;GAAE,QAAQ;GAAI,OAAO;EAAG,EAAE,CAAC;CACpE,GAEOJ;AACT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfakit/vue",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Vue 3 + shadcn-vue renderer for Alfakit, the Laravel admin panel builder by Alfa Six.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alfa Six (https://alfasixbh.com)",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"reka-ui": "^2.11.0",
|
|
97
97
|
"tailwind-merge": "^3.7.0",
|
|
98
98
|
"vue-sonner": "^2.0.9",
|
|
99
|
-
"@alfakit/core": "0.1.
|
|
99
|
+
"@alfakit/core": "0.1.9"
|
|
100
100
|
},
|
|
101
101
|
"peerDependencies": {
|
|
102
102
|
"@inertiajs/vue3": "^3.0.0",
|
|
@@ -426,6 +426,8 @@ const hasBody = computed(() =>
|
|
|
426
426
|
cn(
|
|
427
427
|
schema.stickyHeader &&
|
|
428
428
|
'bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4',
|
|
429
|
+
// Room for the close button (X): a long heading would run under it.
|
|
430
|
+
schema.closeButton !== false && (schema.stickyHeader ? 'pe-12' : 'pe-6'),
|
|
429
431
|
)
|
|
430
432
|
"
|
|
431
433
|
>
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
FormStore,
|
|
5
5
|
applyFormRefresh,
|
|
6
6
|
formActionsVisible,
|
|
7
|
+
formIdPrefix,
|
|
7
8
|
formIdentity,
|
|
8
9
|
formRefreshFor,
|
|
9
10
|
isImplicitSubmit,
|
|
@@ -18,9 +19,16 @@ import {
|
|
|
18
19
|
wizardProgressStore,
|
|
19
20
|
} from '@alfakit/core'
|
|
20
21
|
import { router } from '@inertiajs/vue3'
|
|
21
|
-
import { computed, onBeforeUnmount, onMounted, onScopeDispose, watch } from 'vue'
|
|
22
|
+
import { computed, onBeforeUnmount, onMounted, onScopeDispose, useId, watch } from 'vue'
|
|
22
23
|
import { useStore } from '../../composables/use-store'
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
useActionRunner,
|
|
26
|
+
useAlfakitProps,
|
|
27
|
+
usePageSchema,
|
|
28
|
+
usePanel,
|
|
29
|
+
useStandalone,
|
|
30
|
+
useT,
|
|
31
|
+
} from '../../context'
|
|
24
32
|
import ActionList from '../actions/ActionList.vue'
|
|
25
33
|
import SchemaRenderer from '../schema/SchemaRenderer.vue'
|
|
26
34
|
import { showToast } from '../notifications/flash-toasts'
|
|
@@ -39,9 +47,14 @@ const runner = useActionRunner()
|
|
|
39
47
|
const t = useT()
|
|
40
48
|
const token = computed(() => page.value?.token ?? '')
|
|
41
49
|
|
|
50
|
+
// Several standalone components can share a page (and field names): each form gets its own
|
|
51
|
+
// field id prefix, so labels and errors point at their own inputs.
|
|
52
|
+
const standalone = useStandalone()
|
|
53
|
+
const instanceId = useId()
|
|
42
54
|
const store = new FormStore(props.block, {
|
|
43
55
|
token: token.value,
|
|
44
56
|
component: props.block.component,
|
|
57
|
+
idPrefix: standalone ? formIdPrefix(`form-${instanceId}`) : undefined,
|
|
45
58
|
refreshUrl: panel.value.endpoints.schemaRefresh,
|
|
46
59
|
optionsUrl: panel.value.endpoints.schemaOptions,
|
|
47
60
|
validateUrl: panel.value.endpoints.schemaValidate,
|
|
@@ -40,7 +40,7 @@ const url = (item: NavigationItemSchema): string => navigationItemUrl(item) ?? '
|
|
|
40
40
|
:aria-label="t('navigation', {}, 'Navigation')"
|
|
41
41
|
class="md:hidden"
|
|
42
42
|
>
|
|
43
|
-
<template v-for="item in items.primary" :key="item.label">
|
|
43
|
+
<template v-for="item in items.primary" :key="item.url ?? item.label">
|
|
44
44
|
<a
|
|
45
45
|
v-if="item.openInNewTab"
|
|
46
46
|
v-bind="item.extraAttributes ?? {}"
|
|
@@ -91,7 +91,7 @@ const url = (item: NavigationItemSchema): string => navigationItemUrl(item) ?? '
|
|
|
91
91
|
>
|
|
92
92
|
<Link
|
|
93
93
|
v-for="item in overflowLinks"
|
|
94
|
-
:key="item.label"
|
|
94
|
+
:key="item.url ?? item.label"
|
|
95
95
|
:href="url(item)"
|
|
96
96
|
:data-active="isNavigationItemActive(item) ? 'true' : undefined"
|
|
97
97
|
class="hover:bg-accent data-[active=true]:bg-accent flex min-h-11 items-center gap-3 rounded-md px-3 font-medium [&_svg]:size-4"
|
|
@@ -70,7 +70,7 @@ const registrations = computed(() =>
|
|
|
70
70
|
config.value
|
|
71
71
|
? JSON.stringify({
|
|
72
72
|
token: page.value?.token ?? '',
|
|
73
|
-
home: panel.value.url ?? null,
|
|
73
|
+
home: panel.value.dashboardUrl ?? panel.value.url ?? null,
|
|
74
74
|
commands: (panel.value.commandPalette?.commands ?? []).filter(
|
|
75
75
|
(command) => command.keyBindings?.length && !command.disabled,
|
|
76
76
|
),
|
|
@@ -25,7 +25,7 @@ const contentClass = () => cn(flyout() && 'group-data-[collapsible=icon]:hidden'
|
|
|
25
25
|
<SidebarGroup v-if="!props.group.label" v-bind="attributes()">
|
|
26
26
|
<SidebarGroupContent>
|
|
27
27
|
<SidebarMenu>
|
|
28
|
-
<NavItem v-for="item in props.group.items" :key="item.label" :item="item" />
|
|
28
|
+
<NavItem v-for="item in props.group.items" :key="item.url ?? item.label" :item="item" />
|
|
29
29
|
</SidebarMenu>
|
|
30
30
|
</SidebarGroupContent>
|
|
31
31
|
</SidebarGroup>
|
|
@@ -36,7 +36,7 @@ const contentClass = () => cn(flyout() && 'group-data-[collapsible=icon]:hidden'
|
|
|
36
36
|
</SidebarGroupLabel>
|
|
37
37
|
<SidebarGroupContent :class="contentClass()">
|
|
38
38
|
<SidebarMenu>
|
|
39
|
-
<NavItem v-for="item in props.group.items" :key="item.label" :item="item" />
|
|
39
|
+
<NavItem v-for="item in props.group.items" :key="item.url ?? item.label" :item="item" />
|
|
40
40
|
</SidebarMenu>
|
|
41
41
|
</SidebarGroupContent>
|
|
42
42
|
<NavGroupFlyout v-if="flyout()" :group="props.group" />
|
|
@@ -67,7 +67,7 @@ const contentClass = () => cn(flyout() && 'group-data-[collapsible=icon]:hidden'
|
|
|
67
67
|
<CollapsibleContent>
|
|
68
68
|
<SidebarGroupContent :class="contentClass()">
|
|
69
69
|
<SidebarMenu>
|
|
70
|
-
<NavItem v-for="item in props.group.items" :key="item.label" :item="item" />
|
|
70
|
+
<NavItem v-for="item in props.group.items" :key="item.url ?? item.label" :item="item" />
|
|
71
71
|
</SidebarMenu>
|
|
72
72
|
</SidebarGroupContent>
|
|
73
73
|
</CollapsibleContent>
|
|
@@ -63,7 +63,11 @@ const t = useT()
|
|
|
63
63
|
</CollapsibleTrigger>
|
|
64
64
|
<CollapsibleContent>
|
|
65
65
|
<SidebarMenuSub>
|
|
66
|
-
<NavSubItem
|
|
66
|
+
<NavSubItem
|
|
67
|
+
v-for="child in props.item.children"
|
|
68
|
+
:key="child.url ?? child.label"
|
|
69
|
+
:item="child"
|
|
70
|
+
/>
|
|
67
71
|
</SidebarMenuSub>
|
|
68
72
|
</CollapsibleContent>
|
|
69
73
|
</SidebarMenuItem>
|
|
@@ -65,7 +65,11 @@ const badgeClass = computed(() =>
|
|
|
65
65
|
</CollapsibleTrigger>
|
|
66
66
|
<CollapsibleContent>
|
|
67
67
|
<SidebarMenuSub>
|
|
68
|
-
<NavSubItem
|
|
68
|
+
<NavSubItem
|
|
69
|
+
v-for="child in props.item.children"
|
|
70
|
+
:key="child.url ?? child.label"
|
|
71
|
+
:item="child"
|
|
72
|
+
/>
|
|
69
73
|
</SidebarMenuSub>
|
|
70
74
|
</CollapsibleContent>
|
|
71
75
|
</Collapsible>
|
|
@@ -5,7 +5,10 @@ const props = defineProps<TooltipTriggerProps>()
|
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
7
|
<template>
|
|
8
|
-
|
|
8
|
+
<!-- With as-child the child keeps its own data-slot (an action's `button`). -->
|
|
9
|
+
<TooltipTrigger
|
|
10
|
+
v-bind="{ ...(props.asChild ? {} : { 'data-slot': 'tooltip-trigger' }), ...props }"
|
|
11
|
+
>
|
|
9
12
|
<slot />
|
|
10
13
|
</TooltipTrigger>
|
|
11
14
|
</template>
|
|
@@ -97,7 +97,7 @@ const linkClass =
|
|
|
97
97
|
</p>
|
|
98
98
|
<Link
|
|
99
99
|
v-for="item in group.items.flatMap((entry) => [entry, ...(entry.children ?? [])])"
|
|
100
|
-
:key="item.label"
|
|
100
|
+
:key="item.url ?? item.label"
|
|
101
101
|
:href="item.url ?? '#'"
|
|
102
102
|
:data-active="item.active ? 'true' : undefined"
|
|
103
103
|
class="hover:bg-accent data-[active=true]:bg-accent flex items-center gap-2 rounded-md px-2 py-2 font-medium [&_svg]:size-4"
|
|
@@ -133,7 +133,7 @@ const linkClass =
|
|
|
133
133
|
:items="group.items"
|
|
134
134
|
:active="group.items.some(isActive)"
|
|
135
135
|
/>
|
|
136
|
-
<template v-for="item in group.items" v-else :key="item.label">
|
|
136
|
+
<template v-for="item in group.items" v-else :key="item.url ?? item.label">
|
|
137
137
|
<TopNavMenu
|
|
138
138
|
v-if="item.children && item.children.length > 0"
|
|
139
139
|
:label="item.label"
|
|
@@ -36,7 +36,7 @@ const props = defineProps<{
|
|
|
36
36
|
<ChevronDownIcon class="opacity-60" />
|
|
37
37
|
</DropdownMenuTrigger>
|
|
38
38
|
<DropdownMenuContent align="start" class="min-w-48">
|
|
39
|
-
<DropdownMenuItem v-for="item in props.items" :key="item.label" as-child>
|
|
39
|
+
<DropdownMenuItem v-for="item in props.items" :key="item.url ?? item.label" as-child>
|
|
40
40
|
<Link
|
|
41
41
|
v-bind="item.extraAttributes ?? {}"
|
|
42
42
|
:href="item.url ?? '#'"
|
|
@@ -69,9 +69,16 @@ const poll = (target: string, interval: string | number | null | undefined) =>
|
|
|
69
69
|
state.poll(target, interval)
|
|
70
70
|
const refresh = (targets: string[]) => state.refresh(targets)
|
|
71
71
|
|
|
72
|
+
// Inside a panel page: the panel's registry and translator, but this component's own action
|
|
73
|
+
// runner, so its actions post to its own endpoints with its own token (not the panel's: 403).
|
|
72
74
|
const context: AlfakitContextValue = parent
|
|
73
75
|
? {
|
|
74
76
|
...parent,
|
|
77
|
+
actions: createStandaloneContext(
|
|
78
|
+
{ action: props.component.endpoints.action, modal: props.component.endpoints.modal },
|
|
79
|
+
props.components,
|
|
80
|
+
poll,
|
|
81
|
+
).actions,
|
|
75
82
|
registry: createStandaloneRegistry(props.components, parent.registry),
|
|
76
83
|
poll,
|
|
77
84
|
refresh,
|
|
@@ -201,7 +208,7 @@ onMounted(() => {
|
|
|
201
208
|
<slot name="footer" />
|
|
202
209
|
</div>
|
|
203
210
|
<ActionModals />
|
|
204
|
-
<AlfakitToaster v-if="ownsToaster" claimed />
|
|
211
|
+
<AlfakitToaster v-if="ownsToaster" claimed :dir="direction" />
|
|
205
212
|
</TooltipProvider>
|
|
206
213
|
</component>
|
|
207
214
|
</template>
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { usePage } from '@inertiajs/vue3'
|
|
9
9
|
import { onMounted, onUnmounted, watch } from 'vue'
|
|
10
10
|
import AlfakitRoot from '../AlfakitRoot.vue'
|
|
11
|
+
import DebugPanelSlot from '../components/panel/DebugPanelSlot.vue'
|
|
11
12
|
import { setToasterId, showToast } from '../components/notifications/flash-toasts'
|
|
12
13
|
import { provideAlfakit } from '../context'
|
|
13
14
|
import { starterKitContext, type StarterKitPageOptions } from './runtime'
|
|
@@ -66,6 +67,11 @@ onUnmounted(() => {
|
|
|
66
67
|
>
|
|
67
68
|
<AlfakitRoot :toaster-id="STARTER_KIT_TOASTER_ID">
|
|
68
69
|
<slot />
|
|
70
|
+
<!-- The debug panel's button lives in AlfaKit's top bar, which a starter kit doesn't
|
|
71
|
+
render: float it in the corner (nothing without `alfakitDebug`). -->
|
|
72
|
+
<div data-slot="starter-kit-debug-panel" class="fixed end-16 bottom-4 z-40">
|
|
73
|
+
<DebugPanelSlot />
|
|
74
|
+
</div>
|
|
69
75
|
</AlfakitRoot>
|
|
70
76
|
</div>
|
|
71
77
|
</template>
|
package/src/starter-kit/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { createStarterKitPage, type StarterKitPageOptions } from './starter-kit'
|
|
|
5
5
|
export { default as StarterKitContentLayout } from './StarterKitContentLayout.vue'
|
|
6
6
|
export {
|
|
7
7
|
AlfakitCopilot,
|
|
8
|
+
AlfakitLanguageSwitcher,
|
|
8
9
|
AlfakitNotifications,
|
|
9
10
|
AlfakitSearch,
|
|
10
11
|
AlfakitTenantSwitcher,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { takeRequestedStarterKitLanguages } from '@alfakit/core'
|
|
3
|
+
import { LanguagesIcon } from '@lucide/vue'
|
|
4
|
+
import { computed, onMounted, ref } from 'vue'
|
|
5
|
+
import LocaleOptions from '../../components/panel/LocaleOptions.vue'
|
|
6
|
+
import DropdownMenu from '../../components/ui/DropdownMenu.vue'
|
|
7
|
+
import DropdownMenuContent from '../../components/ui/DropdownMenuContent.vue'
|
|
8
|
+
import DropdownMenuTrigger from '../../components/ui/DropdownMenuTrigger.vue'
|
|
9
|
+
import { useLocalization, useT } from '../../context'
|
|
10
|
+
|
|
11
|
+
/** The loaded language button and menu, inside the widgets' context (`WidgetShell`). */
|
|
12
|
+
|
|
13
|
+
const t = useT()
|
|
14
|
+
const localization = useLocalization()
|
|
15
|
+
const open = ref(false)
|
|
16
|
+
|
|
17
|
+
// A click on the trigger before the menu loaded opens it once mounted (taken per mount: the
|
|
18
|
+
// header is a persistent layout, so the switcher around it outlives a language switch), a
|
|
19
|
+
// frame later, as the menu closes again when it opens while focus is on the replaced button.
|
|
20
|
+
onMounted(() => {
|
|
21
|
+
if (takeRequestedStarterKitLanguages()) requestAnimationFrame(() => (open.value = true))
|
|
22
|
+
})
|
|
23
|
+
const label = computed(() => t('change_language', {}, 'Change language'))
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<DropdownMenu v-model:open="open">
|
|
28
|
+
<DropdownMenuTrigger as-child>
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
data-slot="language-switcher-trigger"
|
|
32
|
+
:aria-label="label"
|
|
33
|
+
:title="label"
|
|
34
|
+
class="hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent focus-visible:ring-ring/50 inline-flex h-9 min-w-9 shrink-0 items-center justify-center gap-1.5 rounded-md px-2 text-sm font-medium outline-none focus-visible:ring-[3px]"
|
|
35
|
+
>
|
|
36
|
+
<LanguagesIcon class="size-4" aria-hidden="true" />
|
|
37
|
+
<span :lang="localization.tag" :dir="localization.direction" class="hidden sm:inline">{{
|
|
38
|
+
localization.label
|
|
39
|
+
}}</span>
|
|
40
|
+
</button>
|
|
41
|
+
</DropdownMenuTrigger>
|
|
42
|
+
<DropdownMenuContent align="end" class="min-w-40">
|
|
43
|
+
<LocaleOptions />
|
|
44
|
+
</DropdownMenuContent>
|
|
45
|
+
</DropdownMenu>
|
|
46
|
+
</template>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { requestStarterKitLanguages, type StarterKitWidgets } from '@alfakit/core'
|
|
3
|
+
import { LanguagesIcon } from '@lucide/vue'
|
|
4
|
+
import { computed } from 'vue'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The language button until its menu has loaded: the languages icon and the current
|
|
8
|
+
* language in its own name (from `sm`). A click is remembered and opens the menu once loaded.
|
|
9
|
+
*/
|
|
10
|
+
const props = defineProps<{ source: StarterKitWidgets }>()
|
|
11
|
+
|
|
12
|
+
const language = computed(() => props.source.widgets.language)
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<button
|
|
17
|
+
type="button"
|
|
18
|
+
data-slot="language-switcher-trigger"
|
|
19
|
+
:aria-label="language?.label"
|
|
20
|
+
aria-haspopup="menu"
|
|
21
|
+
:title="language?.label"
|
|
22
|
+
class="hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent focus-visible:ring-ring/50 inline-flex h-9 min-w-9 shrink-0 items-center justify-center gap-1.5 rounded-md px-2 text-sm font-medium outline-none focus-visible:ring-[3px]"
|
|
23
|
+
@click="requestStarterKitLanguages()"
|
|
24
|
+
>
|
|
25
|
+
<LanguagesIcon class="size-4" aria-hidden="true" />
|
|
26
|
+
<span
|
|
27
|
+
v-if="language"
|
|
28
|
+
:lang="language.current.tag"
|
|
29
|
+
:dir="language.current.direction"
|
|
30
|
+
class="hidden sm:inline"
|
|
31
|
+
>{{ language.current.label }}</span
|
|
32
|
+
>
|
|
33
|
+
</button>
|
|
34
|
+
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { showsLanguageSwitcher, type StarterKitWidgets } from '@alfakit/core'
|
|
3
|
+
import { computed, toRef } from 'vue'
|
|
4
|
+
import LanguageMenu from './LanguageMenu.vue'
|
|
5
|
+
import LanguageSwitcherTrigger from './LanguageSwitcherTrigger.vue'
|
|
6
|
+
import { useWidgetsPanel } from './use-widgets'
|
|
7
|
+
import WidgetShell from './WidgetShell.vue'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The panel's language menu (its languages in their own names, `LocaleOptions`): picking
|
|
11
|
+
* one posts to the panel's locale endpoint and reloads the page in it. Nothing without a
|
|
12
|
+
* language switcher with two or more languages.
|
|
13
|
+
*/
|
|
14
|
+
const props = defineProps<{ source: StarterKitWidgets }>()
|
|
15
|
+
|
|
16
|
+
const panel = useWidgetsPanel(toRef(props, 'source'))
|
|
17
|
+
const visible = computed(() => showsLanguageSwitcher(panel.value?.localization, 'auth'))
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<template v-if="panel">
|
|
22
|
+
<WidgetShell v-if="visible" :panel="panel">
|
|
23
|
+
<LanguageMenu />
|
|
24
|
+
</WidgetShell>
|
|
25
|
+
</template>
|
|
26
|
+
<LanguageSwitcherTrigger v-else :source="props.source" />
|
|
27
|
+
</template>
|
|
@@ -15,25 +15,27 @@ import {
|
|
|
15
15
|
} from 'vue'
|
|
16
16
|
import { lazyComponent } from '../../lib/lazy'
|
|
17
17
|
import CopilotTrigger from './CopilotTrigger.vue'
|
|
18
|
+
import LanguageSwitcherTrigger from './LanguageSwitcherTrigger.vue'
|
|
18
19
|
import NotificationsTrigger from './NotificationsTrigger.vue'
|
|
19
20
|
import SearchTrigger from './SearchTrigger.vue'
|
|
20
21
|
import TenantSwitcherTrigger from './TenantSwitcherTrigger.vue'
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Drop-in header components of starter kit mode (`Panel::starterKit()`): AlfaKit's search,
|
|
24
|
-
* notifications bell and
|
|
25
|
+
* notifications bell, tenant switcher and language switcher in the host's own header, on every page of the
|
|
25
26
|
* application. They read the shared `alfakitNavigation` prop, render a lightweight trigger
|
|
26
27
|
* straight away, and load their code and the panel data once mounted (never during SSR).
|
|
27
28
|
*
|
|
28
29
|
* ```vue
|
|
29
30
|
* <script setup lang="ts">
|
|
30
|
-
* import { AlfakitCopilot, AlfakitNotifications, AlfakitSearch } from '@alfakit/vue'
|
|
31
|
+
* import { AlfakitCopilot, AlfakitLanguageSwitcher, AlfakitNotifications, AlfakitSearch } from '@alfakit/vue'
|
|
31
32
|
* </script>
|
|
32
33
|
*
|
|
33
34
|
* <template>
|
|
34
35
|
* <AlfakitSearch />
|
|
35
36
|
* <AlfakitNotifications />
|
|
36
37
|
* <AlfakitCopilot />
|
|
38
|
+
* <AlfakitLanguageSwitcher />
|
|
37
39
|
* </template>
|
|
38
40
|
* ```
|
|
39
41
|
*/
|
|
@@ -66,6 +68,12 @@ const LoadedTenantSwitcher = lazyComponent(
|
|
|
66
68
|
TenantSwitcherTrigger,
|
|
67
69
|
)
|
|
68
70
|
|
|
71
|
+
const LoadedLanguageSwitcher = lazyComponent(
|
|
72
|
+
'AlfakitLoadedLanguageSwitcher',
|
|
73
|
+
() => import('./loaded').then((module) => module.LoadedLanguageSwitcher),
|
|
74
|
+
LanguageSwitcherTrigger,
|
|
75
|
+
)
|
|
76
|
+
|
|
69
77
|
const LoadedCopilot = lazyComponent(
|
|
70
78
|
'AlfakitLoadedCopilot',
|
|
71
79
|
() => import('./loaded').then((module) => module.LoadedCopilot),
|
|
@@ -159,3 +167,17 @@ export const AlfakitCopilot = headerWidget(
|
|
|
159
167
|
'copilot',
|
|
160
168
|
LoadedCopilot,
|
|
161
169
|
)
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The panel's language menu (`locales()`): the current language straight away, and each
|
|
173
|
+
* language in its own name once loaded. Picking one switches the panel's language (the
|
|
174
|
+
* session, the cookie and `persistLocaleUsing()`) and reloads the page, so AlfaKit pages and
|
|
175
|
+
* the header components show it; the host's own pages and `<html lang dir>` are the app's.
|
|
176
|
+
* Renders nothing for guests or with fewer than two languages.
|
|
177
|
+
*/
|
|
178
|
+
export const AlfakitLanguageSwitcher = headerWidget(
|
|
179
|
+
'AlfakitLanguageSwitcher',
|
|
180
|
+
'starter-kit-language-switcher',
|
|
181
|
+
'language',
|
|
182
|
+
LoadedLanguageSwitcher,
|
|
183
|
+
)
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Only loaded in the browser (`import('./loaded')`).
|
|
5
5
|
*/
|
|
6
6
|
export { default as LoadedCopilot } from './LoadedCopilot.vue'
|
|
7
|
+
export { default as LoadedLanguageSwitcher } from './LoadedLanguageSwitcher.vue'
|
|
7
8
|
export { default as LoadedNotifications } from './LoadedNotifications.vue'
|
|
8
9
|
export { default as LoadedSearch } from './LoadedSearch.vue'
|
|
9
10
|
export { default as LoadedTenantSwitcher } from './LoadedTenantSwitcher.vue'
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
Translator,
|
|
5
5
|
loadStarterKitPanel,
|
|
6
6
|
loadedStarterKitPanel,
|
|
7
|
+
starterKitPanelLocale,
|
|
7
8
|
starterKitWidgetsPanel,
|
|
8
9
|
type PanelSchema,
|
|
9
10
|
type StarterKitWidgets,
|
|
@@ -16,19 +17,20 @@ import type { AlfakitContextValue, AnyComponent } from '../../context'
|
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* The panel the loaded components render with, once its data (`widgets.endpoint`) has
|
|
19
|
-
* loaded: null until then.
|
|
20
|
+
* loaded: null until then. It is loaded per endpoint (a tenant switch) and language (a
|
|
21
|
+
* language switch), so the components follow a switch.
|
|
20
22
|
*/
|
|
21
23
|
export function useWidgetsPanel(source: Ref<StarterKitWidgets>): ComputedRef<PanelSchema | null> {
|
|
22
24
|
const version = ref(0)
|
|
23
25
|
|
|
24
26
|
watch(
|
|
25
|
-
() => source.value.widgets.endpoint,
|
|
26
|
-
(endpoint, _old, onCleanup) => {
|
|
27
|
+
() => [source.value.widgets.endpoint, starterKitPanelLocale(source.value)] as const,
|
|
28
|
+
([endpoint, locale], _old, onCleanup) => {
|
|
27
29
|
let active = true
|
|
28
30
|
|
|
29
31
|
onCleanup(() => (active = false))
|
|
30
32
|
|
|
31
|
-
void loadStarterKitPanel(endpoint).then(
|
|
33
|
+
void loadStarterKitPanel(endpoint, locale).then(
|
|
32
34
|
() => active && version.value++,
|
|
33
35
|
() => {},
|
|
34
36
|
)
|
|
@@ -39,7 +41,10 @@ export function useWidgetsPanel(source: Ref<StarterKitWidgets>): ComputedRef<Pan
|
|
|
39
41
|
return computed(() => {
|
|
40
42
|
void version.value
|
|
41
43
|
|
|
42
|
-
const base = loadedStarterKitPanel(
|
|
44
|
+
const base = loadedStarterKitPanel(
|
|
45
|
+
source.value.widgets.endpoint,
|
|
46
|
+
starterKitPanelLocale(source.value),
|
|
47
|
+
)
|
|
43
48
|
|
|
44
49
|
return base ? starterKitWidgetsPanel(base, source.value) : null
|
|
45
50
|
})
|