@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.
Files changed (100) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/components/actions/ActionModal.js.map +1 -1
  3. package/dist/components/actions/ActionModal.vue.d.ts.map +1 -1
  4. package/dist/components/actions/ActionModal.vue_vue_type_script_setup_true_lang.js +1 -1
  5. package/dist/components/actions/ActionModal.vue_vue_type_script_setup_true_lang.js.map +1 -1
  6. package/dist/components/form/FormBlock.js.map +1 -1
  7. package/dist/components/form/FormBlock.vue.d.ts.map +1 -1
  8. package/dist/components/form/FormBlock.vue_vue_type_script_setup_true_lang.js +79 -78
  9. package/dist/components/form/FormBlock.vue_vue_type_script_setup_true_lang.js.map +1 -1
  10. package/dist/components/panel/BottomNav.js.map +1 -1
  11. package/dist/components/panel/BottomNav.vue_vue_type_script_setup_true_lang.js +2 -2
  12. package/dist/components/panel/BottomNav.vue_vue_type_script_setup_true_lang.js.map +1 -1
  13. package/dist/components/panel/KeyboardShortcuts.js.map +1 -1
  14. package/dist/components/panel/KeyboardShortcuts.vue_vue_type_script_setup_true_lang.js +1 -1
  15. package/dist/components/panel/KeyboardShortcuts.vue_vue_type_script_setup_true_lang.js.map +1 -1
  16. package/dist/components/panel/NavGroup.js.map +1 -1
  17. package/dist/components/panel/NavGroup.vue_vue_type_script_setup_true_lang.js +3 -3
  18. package/dist/components/panel/NavGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
  19. package/dist/components/panel/NavItem.js.map +1 -1
  20. package/dist/components/panel/NavItem.vue.d.ts.map +1 -1
  21. package/dist/components/panel/NavItem.vue_vue_type_script_setup_true_lang.js +1 -1
  22. package/dist/components/panel/NavItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  23. package/dist/components/panel/NavSubItem.js.map +1 -1
  24. package/dist/components/panel/NavSubItem.vue.d.ts.map +1 -1
  25. package/dist/components/panel/NavSubItem.vue_vue_type_script_setup_true_lang.js +1 -1
  26. package/dist/components/panel/NavSubItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  27. package/dist/components/ui/TooltipTrigger.js.map +1 -1
  28. package/dist/components/ui/TooltipTrigger.vue.d.ts +2 -2
  29. package/dist/components/ui/TooltipTrigger.vue.d.ts.map +1 -1
  30. package/dist/components/ui/TooltipTrigger.vue_vue_type_script_setup_true_lang.js +10 -7
  31. package/dist/components/ui/TooltipTrigger.vue_vue_type_script_setup_true_lang.js.map +1 -1
  32. package/dist/eject-manifest.json +4 -2
  33. package/dist/index.js +2 -2
  34. package/dist/layouts/TopNavLayout.js.map +1 -1
  35. package/dist/layouts/TopNavLayout.vue_vue_type_script_setup_true_lang.js +2 -2
  36. package/dist/layouts/TopNavLayout.vue_vue_type_script_setup_true_lang.js.map +1 -1
  37. package/dist/layouts/TopNavMenu.js.map +1 -1
  38. package/dist/layouts/TopNavMenu.vue_vue_type_script_setup_true_lang.js +1 -1
  39. package/dist/layouts/TopNavMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  40. package/dist/standalone/AlfakitComponent.js.map +1 -1
  41. package/dist/standalone/AlfakitComponent.vue.d.ts.map +1 -1
  42. package/dist/standalone/AlfakitComponent.vue_vue_type_script_setup_true_lang.js +29 -24
  43. package/dist/standalone/AlfakitComponent.vue_vue_type_script_setup_true_lang.js.map +1 -1
  44. package/dist/standalone.css +10 -6
  45. package/dist/starter-kit/AlfakitStarterKitRoot.js.map +1 -1
  46. package/dist/starter-kit/AlfakitStarterKitRoot.vue.d.ts.map +1 -1
  47. package/dist/starter-kit/AlfakitStarterKitRoot.vue_vue_type_script_setup_true_lang.js +25 -21
  48. package/dist/starter-kit/AlfakitStarterKitRoot.vue_vue_type_script_setup_true_lang.js.map +1 -1
  49. package/dist/starter-kit/index.d.ts +1 -1
  50. package/dist/starter-kit/index.d.ts.map +1 -1
  51. package/dist/starter-kit/index.js +2 -2
  52. package/dist/starter-kit/widgets/LanguageMenu.js +7 -0
  53. package/dist/starter-kit/widgets/LanguageMenu.js.map +1 -0
  54. package/dist/starter-kit/widgets/LanguageMenu.vue.d.ts +3 -0
  55. package/dist/starter-kit/widgets/LanguageMenu.vue.d.ts.map +1 -0
  56. package/dist/starter-kit/widgets/LanguageMenu.vue_vue_type_script_setup_true_lang.js +52 -0
  57. package/dist/starter-kit/widgets/LanguageMenu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  58. package/dist/starter-kit/widgets/LanguageSwitcherTrigger.js +7 -0
  59. package/dist/starter-kit/widgets/LanguageSwitcherTrigger.js.map +1 -0
  60. package/dist/starter-kit/widgets/LanguageSwitcherTrigger.vue.d.ts +11 -0
  61. package/dist/starter-kit/widgets/LanguageSwitcherTrigger.vue.d.ts.map +1 -0
  62. package/dist/starter-kit/widgets/LanguageSwitcherTrigger.vue_vue_type_script_setup_true_lang.js +32 -0
  63. package/dist/starter-kit/widgets/LanguageSwitcherTrigger.vue_vue_type_script_setup_true_lang.js.map +1 -0
  64. package/dist/starter-kit/widgets/LoadedLanguageSwitcher.js +7 -0
  65. package/dist/starter-kit/widgets/LoadedLanguageSwitcher.js.map +1 -0
  66. package/dist/starter-kit/widgets/LoadedLanguageSwitcher.vue.d.ts +12 -0
  67. package/dist/starter-kit/widgets/LoadedLanguageSwitcher.vue.d.ts.map +1 -0
  68. package/dist/starter-kit/widgets/LoadedLanguageSwitcher.vue_vue_type_script_setup_true_lang.js +28 -0
  69. package/dist/starter-kit/widgets/LoadedLanguageSwitcher.vue_vue_type_script_setup_true_lang.js.map +1 -0
  70. package/dist/starter-kit/widgets/index.d.ts +26 -0
  71. package/dist/starter-kit/widgets/index.d.ts.map +1 -1
  72. package/dist/starter-kit/widgets/index.js +19 -18
  73. package/dist/starter-kit/widgets/index.js.map +1 -1
  74. package/dist/starter-kit/widgets/loaded.d.ts +1 -0
  75. package/dist/starter-kit/widgets/loaded.d.ts.map +1 -1
  76. package/dist/starter-kit/widgets/loaded.js +5 -4
  77. package/dist/starter-kit/widgets/use-widgets.d.ts +2 -1
  78. package/dist/starter-kit/widgets/use-widgets.d.ts.map +1 -1
  79. package/dist/starter-kit/widgets/use-widgets.js +15 -15
  80. package/dist/starter-kit/widgets/use-widgets.js.map +1 -1
  81. package/package.json +2 -2
  82. package/src/components/actions/ActionModal.vue +2 -0
  83. package/src/components/form/FormBlock.vue +15 -2
  84. package/src/components/panel/BottomNav.vue +2 -2
  85. package/src/components/panel/KeyboardShortcuts.vue +1 -1
  86. package/src/components/panel/NavGroup.vue +3 -3
  87. package/src/components/panel/NavItem.vue +5 -1
  88. package/src/components/panel/NavSubItem.vue +5 -1
  89. package/src/components/ui/TooltipTrigger.vue +4 -1
  90. package/src/layouts/TopNavLayout.vue +2 -2
  91. package/src/layouts/TopNavMenu.vue +1 -1
  92. package/src/standalone/AlfakitComponent.vue +8 -1
  93. package/src/starter-kit/AlfakitStarterKitRoot.vue +6 -0
  94. package/src/starter-kit/index.ts +1 -0
  95. package/src/starter-kit/widgets/LanguageMenu.vue +46 -0
  96. package/src/starter-kit/widgets/LanguageSwitcherTrigger.vue +34 -0
  97. package/src/starter-kit/widgets/LoadedLanguageSwitcher.vue +27 -0
  98. package/src/starter-kit/widgets/index.ts +24 -2
  99. package/src/starter-kit/widgets/loaded.ts +1 -0
  100. 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. A tenant switch (another endpoint) loads its own.
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,EAOL,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;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC,iBAAiB,CAAC,GAAG,WAAW,CAAC,WAAW,GAAG,IAAI,CAAC,CAyB/F;AAMD,4EAA4E;AAC5E,wBAAgB,qBAAqB,IAAI,mBAAmB,CAS3D"}
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, starterKitWidgetsPanel as a } from "@alfakit/core";
2
- import { computed as o, ref as s, watch as c } from "vue";
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 l(e) {
5
- let t = s(0);
6
- return c(() => e.value.widgets.endpoint, (e, n, i) => {
7
- let a = !0;
8
- i(() => a = !1), r(e).then(() => a && t.value++, () => {});
9
- }, { immediate: !0 }), o(() => {
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 ? a(n, e.value) : null;
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 u = null;
16
- function d() {
17
- return u ??= {
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
- }, u;
25
+ }, d;
26
26
  }
27
27
  //#endregion
28
- export { l as useWidgetsPanel, d as widgetsAlfakitContext };
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","endpoint","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 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. A tenant switch (another endpoint) loads its own.\n */\nexport function useWidgetsPanel(source: Ref<StarterKitWidgets>): ComputedRef<PanelSchema | null> {\n const version = ref(0)\n\n watch(\n () => source.value.widgets.endpoint,\n (endpoint, _old, onCleanup) => {\n let active = true\n\n onCleanup(() => (active = false))\n\n void loadStarterKitPanel(endpoint).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(source.value.widgets.endpoint)\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":";;;AAoBA,SAAgBA,EAAgB,GAAiE;CAC/F,IAAMC,IAAUC,EAAI,CAAC;CAiBrB,OAfAC,QACQC,EAAO,MAAM,QAAQ,WAC1B,GAAU,GAAM,MAAc;EAC7B,IAAIC,IAAS;EAIb,AAFAC,QAAiB,IAAS,EAAM,GAEhCC,EAAyBC,CAAQ,CAAC,CAAC,WAC3BH,KAAU,EAAQ,eAClB,CAAC,CACT;CACF,GACA,EAAE,WAAW,GAAK,CACpB,GAEOI,QAAe;EACpB,EAAa;EAEb,IAAMC,IAAOC,EAAsBP,EAAO,MAAM,QAAQ,QAAQ;EAEhE,OAAOM,IAAOE,EAAuBF,GAAMN,EAAO,KAAK,IAAI;CAC7D,CAAC;AACH;AAIA,IAAIS,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"}
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.7",
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.7"
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 { useActionRunner, useAlfakitProps, usePageSchema, usePanel, useT } from '../../context'
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 v-for="child in props.item.children" :key="child.label" :item="child" />
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 v-for="child in props.item.children" :key="child.label" :item="child" />
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
- <TooltipTrigger v-bind="props" data-slot="tooltip-trigger">
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>
@@ -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 tenant switcher in the host's own header, on every page of the
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. A tenant switch (another endpoint) loads its own.
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(source.value.widgets.endpoint)
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
  })