@flux-ui/application 3.0.0-next.39

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 (59) hide show
  1. package/README.md +16 -0
  2. package/dist/component/FluxApplication.vue.d.ts +28 -0
  3. package/dist/component/FluxApplicationContent.vue.d.ts +24 -0
  4. package/dist/component/FluxApplicationHero.vue.d.ts +26 -0
  5. package/dist/component/FluxApplicationMenu.vue.d.ts +29 -0
  6. package/dist/component/FluxApplicationMenuAccount.vue.d.ts +29 -0
  7. package/dist/component/FluxApplicationMenuContext.vue.d.ts +15 -0
  8. package/dist/component/FluxApplicationMenuContextStack.vue.d.ts +6 -0
  9. package/dist/component/FluxApplicationMenuPromo.vue.d.ts +22 -0
  10. package/dist/component/FluxApplicationMenuToggle.vue.d.ts +3 -0
  11. package/dist/component/FluxApplicationSection.vue.d.ts +26 -0
  12. package/dist/component/FluxApplicationSide.vue.d.ts +20 -0
  13. package/dist/component/FluxApplicationTop.vue.d.ts +29 -0
  14. package/dist/component/index.d.ts +12 -0
  15. package/dist/composable/index.d.ts +3 -0
  16. package/dist/composable/useApplicationContextMenu.d.ts +7 -0
  17. package/dist/composable/useApplicationContextRegistration.d.ts +2 -0
  18. package/dist/composable/useApplicationInjection.d.ts +2 -0
  19. package/dist/composable/useApplicationMenu.d.ts +26 -0
  20. package/dist/data/index.d.ts +29 -0
  21. package/dist/index.css +746 -0
  22. package/dist/index.d.ts +3 -0
  23. package/dist/index.js +4331 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/routing/useNamedRoutes.d.ts +14 -0
  26. package/dist/routing/useRoute.d.ts +8 -0
  27. package/dist/types.d.ts +13 -0
  28. package/package.json +71 -0
  29. package/src/component/FluxApplication.vue +144 -0
  30. package/src/component/FluxApplicationContent.vue +37 -0
  31. package/src/component/FluxApplicationHero.vue +34 -0
  32. package/src/component/FluxApplicationMenu.vue +73 -0
  33. package/src/component/FluxApplicationMenuAccount.vue +47 -0
  34. package/src/component/FluxApplicationMenuContext.vue +78 -0
  35. package/src/component/FluxApplicationMenuContextStack.vue +53 -0
  36. package/src/component/FluxApplicationMenuPromo.vue +23 -0
  37. package/src/component/FluxApplicationMenuToggle.vue +30 -0
  38. package/src/component/FluxApplicationSection.vue +40 -0
  39. package/src/component/FluxApplicationSide.vue +16 -0
  40. package/src/component/FluxApplicationTop.vue +74 -0
  41. package/src/component/index.ts +12 -0
  42. package/src/composable/index.ts +3 -0
  43. package/src/composable/useApplicationContextMenu.ts +19 -0
  44. package/src/composable/useApplicationContextRegistration.ts +25 -0
  45. package/src/composable/useApplicationInjection.ts +12 -0
  46. package/src/composable/useApplicationMenu.ts +79 -0
  47. package/src/css/component/Application.module.scss +88 -0
  48. package/src/css/component/ApplicationContent.module.scss +119 -0
  49. package/src/css/component/ApplicationHero.module.scss +17 -0
  50. package/src/css/component/ApplicationMenu.module.scss +421 -0
  51. package/src/css/component/ApplicationSection.module.scss +43 -0
  52. package/src/css/component/ApplicationSide.module.scss +21 -0
  53. package/src/css/component/ApplicationTop.module.scss +111 -0
  54. package/src/data/index.ts +38 -0
  55. package/src/index.ts +3 -0
  56. package/src/routing/useNamedRoutes.ts +34 -0
  57. package/src/routing/useRoute.ts +11 -0
  58. package/src/types.d.ts +13 -0
  59. package/tsconfig.json +7 -0
@@ -0,0 +1,14 @@
1
+ import { ComputedRef, Ref } from 'vue';
2
+ import { RouteRecordNormalized } from 'vue-router';
3
+ /**
4
+ * Returns **all** matched route records that expose a named view with
5
+ * the given `name`, paired with their depth in `route.matched`. Powers
6
+ * `<FluxApplicationMenuContextStack>` so every level of a nested route
7
+ * tree can render its own menu component (instead of vue-router's
8
+ * default behaviour of only rendering the deepest one).
9
+ */
10
+ export default function (nameRef: Ref<string> | string): ComputedRef<NamedRouteMatch[]>;
11
+ export type NamedRouteMatch = {
12
+ readonly depth: number;
13
+ readonly record: RouteRecordNormalized;
14
+ };
@@ -0,0 +1,8 @@
1
+ import { RouteLocationNormalizedLoaded } from 'vue-router';
2
+ /**
3
+ * Internal alias for vue-router's `useRoute`. Exists so the rest of the
4
+ * package can import a single, controlled `useRoute` symbol — making it
5
+ * trivial to swap in a wrapper later (e.g. modal-aware variants) without
6
+ * touching every call site.
7
+ */
8
+ export default function (): RouteLocationNormalizedLoaded;
@@ -0,0 +1,13 @@
1
+ declare module '*.module.css' {
2
+ const content: Record<string, string>;
3
+ export default content;
4
+ }
5
+
6
+ declare module '*.module.scss' {
7
+ const content: Record<string, string>;
8
+ export default content;
9
+ }
10
+
11
+ declare module '*.svg' {
12
+ export = string;
13
+ }
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@flux-ui/application",
3
+ "description": "Contains components to create applications with Flux UI.",
4
+ "version": "3.0.0-next.39",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "funding": "https://github.com/sponsors/basmilius",
8
+ "homepage": "https://flux-ui.dev",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/basmilius/flux.git",
12
+ "directory": "packages/application"
13
+ },
14
+ "keywords": [
15
+ "ui library",
16
+ "component library",
17
+ "design system",
18
+ "vue",
19
+ "vue 3",
20
+ "ui",
21
+ "components",
22
+ "flux",
23
+ "application"
24
+ ],
25
+ "scripts": {
26
+ "build": "vue-tsc && vite build",
27
+ "dev": "vite build --watch --mode development"
28
+ },
29
+ "engines": {
30
+ "node": ">=23"
31
+ },
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "default": "./dist/index.js"
36
+ },
37
+ "./style.css": "./dist/index.css"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "provenance": true
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "src",
46
+ "tsconfig.json"
47
+ ],
48
+ "main": "./dist/index.js",
49
+ "module": "./dist/index.js",
50
+ "types": "./dist/index.d.ts",
51
+ "typings": "./dist/index.d.ts",
52
+ "sideEffects": false,
53
+ "dependencies": {
54
+ "@flux-ui/components": "3.0.0-next.39",
55
+ "@flux-ui/internals": "3.0.0-next.39",
56
+ "clsx": "^2.1.1",
57
+ "vue": "^3.6.0-beta.10",
58
+ "vue-router": "^5.0.6"
59
+ },
60
+ "devDependencies": {
61
+ "@basmilius/vite-preset": "^3.19.0",
62
+ "@flux-ui/types": "3.0.0-next.39",
63
+ "@types/node": "^25.6.0",
64
+ "@vitejs/plugin-vue": "^6.0.6",
65
+ "@vue/tsconfig": "^0.9.1",
66
+ "sass-embedded": "^1.99.0",
67
+ "typescript": "^6.0.3",
68
+ "vite": "^8.0.10",
69
+ "vue-tsc": "^3.2.7"
70
+ }
71
+ }
@@ -0,0 +1,144 @@
1
+ <template>
2
+ <div :class="$style.application">
3
+ <slot name="menu"/>
4
+
5
+ <div :class="$style.applicationBody">
6
+ <slot/>
7
+ </div>
8
+
9
+ <slot name="side"/>
10
+
11
+ <button
12
+ type="button"
13
+ aria-label="Close menu"
14
+ :class="$style.applicationMenuBackdrop"
15
+ @click="isMenuCollapsed = true"/>
16
+ </div>
17
+ </template>
18
+
19
+ <script
20
+ lang="ts"
21
+ setup>
22
+ import { useRemembered } from '@flux-ui/internals';
23
+ import { computed, onUnmounted, provide, ref, shallowRef, toRef, type VNode, watch } from 'vue';
24
+ import { type FluxApplicationContextInfo, FluxApplicationInjectionKey, type FluxApplicationLayout } from '../data';
25
+ import useNamedRoutes from '../routing/useNamedRoutes';
26
+ import useRoute from '../routing/useRoute';
27
+ import $style from '../css/component/Application.module.scss';
28
+
29
+ const {
30
+ contextMenuName = 'menu',
31
+ showDesktopMenuToggle = false
32
+ } = defineProps<{
33
+ readonly contextMenuName?: string;
34
+ readonly showDesktopMenuToggle?: boolean;
35
+ }>();
36
+
37
+ defineSlots<{
38
+ default(): VNode[];
39
+ menu(): VNode[];
40
+ side(): VNode[];
41
+ }>();
42
+
43
+ const route = useRoute();
44
+ const matchedMenuRoutes = useNamedRoutes(toRef(() => contextMenuName));
45
+
46
+ const isMenuCollapsed = useRemembered('application-menu-collapsed', true);
47
+ const layout = ref<FluxApplicationLayout>('default');
48
+
49
+ const totalLevels = computed(() => 1 + matchedMenuRoutes.value.length);
50
+ const viewIndex = ref(0);
51
+
52
+ function clampViewIndex(target: number): number {
53
+ if (target < 0) {
54
+ return 0;
55
+ }
56
+ const max = totalLevels.value - 1;
57
+ if (target > max) {
58
+ return max;
59
+ }
60
+ return target;
61
+ }
62
+
63
+ function goToLevel(index: number): void {
64
+ viewIndex.value = clampViewIndex(index);
65
+ }
66
+
67
+ function goToMain(): void {
68
+ viewIndex.value = 0;
69
+ }
70
+
71
+ function goToCurrent(): void {
72
+ viewIndex.value = totalLevels.value - 1;
73
+ }
74
+
75
+ function goToParent(): void {
76
+ viewIndex.value = clampViewIndex(viewIndex.value - 1);
77
+ }
78
+
79
+ function goToChild(): void {
80
+ viewIndex.value = clampViewIndex(viewIndex.value + 1);
81
+ }
82
+
83
+ const contextStack = shallowRef<FluxApplicationContextInfo[]>([]);
84
+ const contexts = computed(() => contextStack.value);
85
+ const activeContext = computed(() => contextStack.value.at(-1));
86
+
87
+ function pushContext(info: FluxApplicationContextInfo): void {
88
+ contextStack.value = [...contextStack.value, info];
89
+ }
90
+
91
+ function removeContext(id: symbol): void {
92
+ contextStack.value = contextStack.value.filter(entry => entry.id !== id);
93
+ }
94
+
95
+ provide(FluxApplicationInjectionKey, {
96
+ activeContext,
97
+ contexts,
98
+ isMenuCollapsed,
99
+ layout,
100
+ showDesktopMenuToggle: toRef(() => showDesktopMenuToggle),
101
+ totalLevels,
102
+ viewIndex,
103
+ goToChild,
104
+ goToCurrent,
105
+ goToLevel,
106
+ goToMain,
107
+ goToParent,
108
+ pushContext,
109
+ removeContext
110
+ });
111
+
112
+ watch(() => route.fullPath, () => {
113
+ viewIndex.value = totalLevels.value - 1;
114
+ });
115
+
116
+ watch(totalLevels, (next) => {
117
+ viewIndex.value = clampViewIndex(viewIndex.value);
118
+
119
+ // On initial route resolve, totalLevels jumps from 1 to its
120
+ // real value. Snap to the deepest level so the user lands on
121
+ // the right context without seeing a slide animation.
122
+ if (viewIndex.value === 0 && next > 1) {
123
+ viewIndex.value = next - 1;
124
+ }
125
+ }, {immediate: true});
126
+
127
+ watch(isMenuCollapsed, collapsed => {
128
+ if (typeof document === 'undefined') {
129
+ return;
130
+ }
131
+
132
+ if (collapsed) {
133
+ delete document.documentElement.dataset.applicationMenuOpen;
134
+ } else {
135
+ document.documentElement.dataset.applicationMenuOpen = '';
136
+ }
137
+ }, {immediate: true});
138
+
139
+ onUnmounted(() => {
140
+ if (typeof document !== 'undefined') {
141
+ delete document.documentElement.dataset.applicationMenuOpen;
142
+ }
143
+ });
144
+ </script>
@@ -0,0 +1,37 @@
1
+ <template>
2
+ <main
3
+ :class="clsx(
4
+ layout === 'default' && $style.applicationContentDefault,
5
+ layout === 'dashboard' && $style.applicationContentDashboard,
6
+ layout === 'full' && $style.applicationContentFull,
7
+ layout === 'medium' && $style.applicationContentMedium,
8
+ layout === 'narrow' && $style.applicationContentNarrow
9
+ )"
10
+ aria-label="Application Content">
11
+ <slot/>
12
+ </main>
13
+ </template>
14
+
15
+ <script
16
+ lang="ts"
17
+ setup>
18
+ import clsx from 'clsx';
19
+ import { useApplicationInjection } from '../composable';
20
+ import type { FluxApplicationLayout } from '../data';
21
+ import $style from '../css/component/ApplicationContent.module.scss';
22
+ import { watch } from 'vue';
23
+
24
+ const {
25
+ layout = 'default'
26
+ } = defineProps<{
27
+ readonly layout?: FluxApplicationLayout;
28
+ }>();
29
+
30
+ defineSlots<{
31
+ default(): any;
32
+ }>();
33
+
34
+ const {layout: layoutRef} = useApplicationInjection();
35
+
36
+ watch(() => layout, () => layoutRef.value = layout, {immediate: true});
37
+ </script>
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <header :class="$style.applicationHero">
3
+ <slot name="start"/>
4
+
5
+ <div :class="$style.applicationHeroBody">
6
+ <h1>{{ title }}</h1>
7
+
8
+ <p
9
+ v-if="subtitle"
10
+ :class="$style.applicationHeroSubtitle">
11
+ {{ subtitle }}
12
+ </p>
13
+ </div>
14
+
15
+ <slot name="end"/>
16
+ </header>
17
+ </template>
18
+
19
+ <script
20
+ lang="ts"
21
+ setup>
22
+ import type { VNode } from 'vue';
23
+ import $style from '../css/component/ApplicationHero.module.scss';
24
+
25
+ defineProps<{
26
+ readonly title: string;
27
+ readonly subtitle?: string;
28
+ }>();
29
+
30
+ defineSlots<{
31
+ start?(): VNode;
32
+ end?(): VNode;
33
+ }>();
34
+ </script>
@@ -0,0 +1,73 @@
1
+ <template>
2
+ <aside
3
+ :class="$style.applicationMenu"
4
+ :data-collapsed="isMenuCollapsed ? '' : undefined"
5
+ :data-collapsible="showDesktopMenuToggle ? '' : undefined">
6
+ <FluxMenu
7
+ v-if="slots.header"
8
+ :class="$style.applicationMenuHeader">
9
+ <slot name="header"/>
10
+ </FluxMenu>
11
+
12
+ <div :class="$style.applicationMenuStage">
13
+ <div
14
+ :class="$style.applicationMenuTrack"
15
+ :style="{'--view-index': viewIndex}">
16
+ <FluxMenu :class="$style.applicationMenuPanel">
17
+ <slot/>
18
+ </FluxMenu>
19
+
20
+ <slot name="context"/>
21
+ </div>
22
+ </div>
23
+
24
+ <nav
25
+ v-if="showPageIndicator && totalLevels > 1"
26
+ :class="$style.applicationMenuPageIndicator"
27
+ aria-label="Menu levels">
28
+ <button
29
+ v-for="level in totalLevels"
30
+ :key="level - 1"
31
+ type="button"
32
+ :class="[
33
+ $style.applicationMenuPageIndicatorDot,
34
+ (level - 1) === viewIndex && $style.applicationMenuPageIndicatorDotActive
35
+ ]"
36
+ :aria-current="(level - 1) === viewIndex ? 'true' : undefined"
37
+ :aria-label="(level - 1) === 0 ? 'Hoofdmenu' : `Niveau ${level - 1}`"
38
+ @click="goToLevel(level - 1)"/>
39
+ </nav>
40
+
41
+ <FluxMenu
42
+ v-if="slots.footer"
43
+ :class="$style.applicationMenuFooter"
44
+ :data-hidden="!isMainMenuVisible ? '' : undefined">
45
+ <slot name="footer"/>
46
+ </FluxMenu>
47
+ </aside>
48
+ </template>
49
+
50
+ <script
51
+ lang="ts"
52
+ setup>
53
+ import { FluxMenu } from '@flux-ui/components';
54
+ import { type VNode } from 'vue';
55
+ import { useApplicationInjection, useApplicationMenu } from '../composable';
56
+ import $style from '../css/component/ApplicationMenu.module.scss';
57
+
58
+ const {
59
+ showPageIndicator = true
60
+ } = defineProps<{
61
+ readonly showPageIndicator?: boolean;
62
+ }>();
63
+
64
+ const slots = defineSlots<{
65
+ default(): VNode;
66
+ context?(): VNode;
67
+ footer?(): VNode;
68
+ header?(): VNode;
69
+ }>();
70
+
71
+ const {isMenuCollapsed, showDesktopMenuToggle} = useApplicationInjection();
72
+ const {goToLevel, isMainMenuVisible, totalLevels, viewIndex} = useApplicationMenu();
73
+ </script>
@@ -0,0 +1,47 @@
1
+ <template>
2
+ <FluxFlyout is-auto-width>
3
+ <template #opener="{open}">
4
+ <FluxMenuItem
5
+ :class="slots.switcher ? $style.applicationMenuAccountSwitcher : $style.applicationMenuAccount"
6
+ :icon-leading="icon"
7
+ :icon-trailing="slots.switcher ? 'angle-down' : undefined"
8
+ :image-alt="imageAlt"
9
+ :image-src="imageSrc"
10
+ :label="label"
11
+ @click="slots.switcher && open()">
12
+ <template
13
+ v-if="slots.avatar"
14
+ #before>
15
+ <slot name="avatar"/>
16
+ </template>
17
+ </FluxMenuItem>
18
+ </template>
19
+
20
+ <template v-if="slots.switcher">
21
+ <FluxPane>
22
+ <slot name="switcher"/>
23
+ </FluxPane>
24
+ </template>
25
+ </FluxFlyout>
26
+ </template>
27
+
28
+ <script
29
+ lang="ts"
30
+ setup>
31
+ import { FluxFlyout, FluxMenuItem, FluxPane } from '@flux-ui/components';
32
+ import type { FluxIconName } from '@flux-ui/types';
33
+ import type { VNode } from 'vue';
34
+ import $style from '../css/component/ApplicationMenu.module.scss';
35
+
36
+ defineProps<{
37
+ readonly icon?: FluxIconName;
38
+ readonly imageAlt?: string;
39
+ readonly imageSrc?: string;
40
+ readonly label: string;
41
+ }>();
42
+
43
+ const slots = defineSlots<{
44
+ avatar?(): VNode;
45
+ switcher?(): VNode;
46
+ }>();
47
+ </script>
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <div :class="$style.applicationMenuContext">
3
+ <FluxSecondaryButton
4
+ icon-leading="angle-left"
5
+ size="small"
6
+ :tabindex="tabindex"
7
+ :href="canSlide ? undefined : href"
8
+ :rel="rel"
9
+ :target="target"
10
+ :to="canSlide ? undefined : to"
11
+ :type="canSlide ? 'button' : type"
12
+ @click="onBack"/>
13
+
14
+ <div :class="$style.applicationMenuContextContent">
15
+ <strong>{{ title }}</strong>
16
+ <span v-if="subtitle">{{ subtitle }}</span>
17
+ </div>
18
+ </div>
19
+ </template>
20
+
21
+ <script
22
+ lang="ts"
23
+ setup>
24
+ import { FluxSecondaryButton } from '@flux-ui/components';
25
+ import type { FluxPressableType, FluxTo } from '@flux-ui/types';
26
+ import { computed, inject } from 'vue';
27
+ import { matchedRouteKey } from 'vue-router';
28
+ import { FluxApplicationInjectionKey } from '../data';
29
+ import useApplicationContextRegistration from '../composable/useApplicationContextRegistration';
30
+ import $style from '../css/component/ApplicationMenu.module.scss';
31
+
32
+ const props = defineProps<{
33
+ readonly subtitle?: string;
34
+ readonly title: string;
35
+ readonly tabindex?: string | number;
36
+ readonly href?: string;
37
+ readonly rel?: string;
38
+ readonly target?: string;
39
+ readonly to?: FluxTo;
40
+ readonly entryTo?: FluxTo;
41
+ readonly type?: FluxPressableType;
42
+ }>();
43
+
44
+ const injection = inject(FluxApplicationInjectionKey, null);
45
+ const matchedRoute = inject(matchedRouteKey, null);
46
+
47
+ const canSlide = computed(() => {
48
+ if (!injection) {
49
+ return false;
50
+ }
51
+ return injection.viewIndex.value > 0;
52
+ });
53
+
54
+ const autoEntryTo = computed<FluxTo | undefined>(() => {
55
+ const record = matchedRoute?.value;
56
+
57
+ if (!record || typeof record.name !== 'string') {
58
+ return undefined;
59
+ }
60
+
61
+ return {name: record.name};
62
+ });
63
+
64
+ function onBack(): void {
65
+ if (canSlide.value && injection) {
66
+ injection.goToParent();
67
+ }
68
+ }
69
+
70
+ useApplicationContextRegistration(() => ({
71
+ title: props.title,
72
+ subtitle: props.subtitle,
73
+ to: props.to,
74
+ entryTo: props.entryTo ?? autoEntryTo.value,
75
+ href: props.href,
76
+ type: props.type
77
+ }));
78
+ </script>
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <TransitionGroup
3
+ :enter-active-class="$style.applicationMenuPanelEnterActive"
4
+ :enter-from-class="$style.applicationMenuPanelEnterFrom"
5
+ :leave-active-class="$style.applicationMenuPanelLeaveActive"
6
+ :leave-to-class="$style.applicationMenuPanelLeaveTo">
7
+ <FluxMenu
8
+ v-for="match in matches"
9
+ :key="match.record.path"
10
+ :class="$style.applicationMenuPanel">
11
+ <ScopedRouterView
12
+ :depth="match.depth"
13
+ :view-name="name"/>
14
+ </FluxMenu>
15
+ </TransitionGroup>
16
+ </template>
17
+
18
+ <script
19
+ lang="ts"
20
+ setup>
21
+ import { FluxMenu } from '@flux-ui/components';
22
+ import { defineComponent, h, provide, ref, toRef, type VNode } from 'vue';
23
+ import { RouterView, viewDepthKey } from 'vue-router';
24
+ import useNamedRoutes from '../routing/useNamedRoutes';
25
+ import $style from '../css/component/ApplicationMenu.module.scss';
26
+
27
+ const {
28
+ name = 'menu'
29
+ } = defineProps<{
30
+ readonly name?: string;
31
+ }>();
32
+
33
+ const matches = useNamedRoutes(toRef(() => name));
34
+
35
+ const ScopedRouterView = defineComponent({
36
+ name: 'FluxApplicationMenuScopedRouterView',
37
+ props: {
38
+ depth: {
39
+ type: Number,
40
+ required: true
41
+ },
42
+ viewName: {
43
+ type: String,
44
+ required: true
45
+ }
46
+ },
47
+ setup(props): () => VNode {
48
+ provide(viewDepthKey, ref(props.depth));
49
+
50
+ return () => h(RouterView, {name: props.viewName});
51
+ }
52
+ });
53
+ </script>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <div :class="$style.applicationMenuPromo">
3
+ <FluxIcon
4
+ v-if="icon"
5
+ :name="icon"/>
6
+
7
+ <div :class="$style.applicationMenuPromoContent">
8
+ <slot/>
9
+ </div>
10
+ </div>
11
+ </template>
12
+
13
+ <script
14
+ lang="ts"
15
+ setup>
16
+ import { FluxIcon } from '@flux-ui/components';
17
+ import type { FluxIconName } from '@flux-ui/types';
18
+ import $style from '../css/component/ApplicationMenu.module.scss';
19
+
20
+ defineProps<{
21
+ readonly icon?: FluxIconName;
22
+ }>();
23
+ </script>
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <FluxMenuItem
3
+ :class="$style.applicationMenuToggle"
4
+ @click="isMenuCollapsed = !isMenuCollapsed">
5
+ <template #before>
6
+ <svg
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ fill="none"
9
+ viewBox="0 0 18 18"
10
+ :class="$style.applicationMenuToggleIcon">
11
+ <path
12
+ fill-rule="evenodd"
13
+ d="M0 15V3a3 3 0 0 1 3-3h3a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a3.04 3.04 0 0 1-3-3M2 3a1 1 0 0 1 1-1h2v14H3a1 1 0 0 1-1-1z"
14
+ clip-rule="evenodd"/>
15
+
16
+ <path d="M16 15V3a1 1 0 0 0-1-1h-5a1 1 0 0 1 0-2h5c1.63.04 3 1.33 3 3v12a3.07 3.07 0 0 1-3 3h-5a1 1 0 1 1 0-2h5a1 1 0 0 0 1-1"/>
17
+ </svg>
18
+ </template>
19
+ </FluxMenuItem>
20
+ </template>
21
+
22
+ <script
23
+ lang="ts"
24
+ setup>
25
+ import { FluxMenuItem } from '@flux-ui/components';
26
+ import { useApplicationInjection } from '../composable';
27
+ import $style from '../css/component/ApplicationMenu.module.scss';
28
+
29
+ const {isMenuCollapsed} = useApplicationInjection();
30
+ </script>
@@ -0,0 +1,40 @@
1
+ <template>
2
+ <section :class="$style.applicationSection">
3
+ <header
4
+ v-if="title || info || slots.end"
5
+ :class="$style.applicationSectionHeader">
6
+ <h2 v-if="title">
7
+ {{ title }}
8
+ </h2>
9
+
10
+ <slot name="end"/>
11
+
12
+ <span
13
+ v-if="info"
14
+ :class="$style.applicationSectionInfo">
15
+ {{ info }}
16
+ </span>
17
+ </header>
18
+
19
+ <div :class="$style.applicationSectionContent">
20
+ <slot/>
21
+ </div>
22
+ </section>
23
+ </template>
24
+
25
+ <script
26
+ lang="ts"
27
+ setup>
28
+ import type { VNode } from 'vue';
29
+ import $style from '../css/component/ApplicationSection.module.scss';
30
+
31
+ defineProps<{
32
+ readonly title?: string;
33
+ readonly info?: string;
34
+ }>();
35
+
36
+ const slots = defineSlots<{
37
+ default(): VNode;
38
+ end?(): VNode;
39
+ }>();
40
+ </script>