@flux-ui/dashboard 3.0.0-next.0 → 3.0.0-next.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@flux-ui/dashboard",
3
3
  "description": "Contains components to create dashboards with Flux UI.",
4
- "version": "3.0.0-next.0",
4
+ "version": "3.0.0-next.3",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/basmilius",
@@ -52,17 +52,17 @@
52
52
  "typings": "./dist/index.d.ts",
53
53
  "sideEffects": false,
54
54
  "dependencies": {
55
- "@flux-ui/components": "3.0.0-next.0",
56
- "@flux-ui/internals": "3.0.0-next.0",
55
+ "@flux-ui/components": "0.0.0",
56
+ "@flux-ui/internals": "0.0.0",
57
57
  "vue": "^3.5.13"
58
58
  },
59
59
  "devDependencies": {
60
- "@basmilius/vite-vue-preset": "^2.5.0",
61
- "@types/node": "^22.14.0",
60
+ "@basmilius/vite-vue-preset": "^3.0.0",
61
+ "@types/node": "^22.14.1",
62
62
  "@vitejs/plugin-vue": "^5.2.3",
63
- "sass-embedded": "^1.86.3",
63
+ "sass-embedded": "^1.87.0",
64
64
  "typescript": "^5.8.3",
65
- "vite": "^6.2.5",
66
- "vue-tsc": "^2.2.8"
65
+ "vite": "^6.3.2",
66
+ "vue-tsc": "^2.2.10"
67
67
  }
68
68
  }
@@ -1,14 +1,9 @@
1
1
  <template>
2
- <div :class="$style.dashboard">
2
+ <div :class="[$style.dashboard, isResizing && $style.isResizing]">
3
3
  <slot name="navigation"/>
4
-
5
4
  <slot name="menu"/>
6
-
7
- <div :class="$style.dashboardBody">
8
- <slot name="header"/>
9
- <slot/>
10
- </div>
11
-
5
+ <slot name="header"/>
6
+ <slot/>
12
7
  <slot name="side"/>
13
8
  </div>
14
9
  </template>
@@ -17,13 +12,38 @@
17
12
  lang="ts"
18
13
  setup>
19
14
  import { useRemembered } from '@flux-ui/internals';
20
- import { provide } from 'vue';
15
+ import { provide, ref, VNode, watch } from 'vue';
21
16
  import { FluxDashboardInjectionKey } from '$fluxDashboard/data';
22
17
  import $style from '$fluxDashboard/css/component/Dashboard.module.scss';
23
18
 
19
+ defineSlots<{
20
+ default(): VNode;
21
+ header(): VNode;
22
+ navigation(): VNode;
23
+ menu(): VNode;
24
+ side(): VNode;
25
+ }>();
26
+
27
+ const isMenuCollapsed = useRemembered('dashboard-menu-collapsed', true);
24
28
  const isNavigationCollapsed = useRemembered('dashboard-navigation-collapsed', true);
29
+ const isResizing = ref(false);
25
30
 
26
31
  provide(FluxDashboardInjectionKey, {
32
+ isMenuCollapsed,
27
33
  isNavigationCollapsed
28
34
  });
35
+
36
+ watch(isNavigationCollapsed, (_, __, onCleanup) => {
37
+ let timeout: NodeJS.Timeout;
38
+
39
+ function onResize(): void {
40
+ clearTimeout(timeout);
41
+ isResizing.value = true;
42
+ timeout = setTimeout(() => isResizing.value = false, 10);
43
+ }
44
+
45
+ window.addEventListener('resize', onResize, {passive: true});
46
+
47
+ onCleanup(() => window.removeEventListener('resize', onResize));
48
+ }, {immediate: true});
29
49
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <main :class="$style.dashboardContent">
2
+ <main :class="[$style.dashboardContent, !isMenuCollapsed && $style.dashboardContentCollapsed]">
3
3
  <slot/>
4
4
  </main>
5
5
  </template>
@@ -7,5 +7,8 @@
7
7
  <script
8
8
  lang="ts"
9
9
  setup>
10
- import $style from '$fluxDashboard/css/component/Dashboard.module.scss';
10
+ import { useDashboardInjection } from '$fluxDashboard/composable';
11
+ import $style from '$fluxDashboard/css/component/DashboardContent.module.scss';
12
+
13
+ const {isMenuCollapsed} = useDashboardInjection();
11
14
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <FluxDashboardTopBar>
2
+ <FluxDashboardTopBar :class="y > 0 ? $style.dashboardHeaderScrolled : $style.dashboardHeader">
3
3
  <slot name="start"/>
4
4
 
5
5
  <FluxIcon
@@ -20,11 +20,15 @@
20
20
  lang="ts"
21
21
  setup>
22
22
  import { FluxIcon, FluxSpacer } from '@flux-ui/components';
23
+ import { useScrollPosition } from '@flux-ui/internals';
23
24
  import type { FluxIconName } from '@flux-ui/types';
24
25
  import FluxDashboardTopBar from './FluxDashboardTopBar.vue';
26
+ import $style from '$fluxDashboard/css/component/DashboardTopBar.module.scss';
25
27
 
26
28
  defineProps<{
27
29
  readonly icon?: FluxIconName;
28
30
  readonly title?: string;
29
31
  }>();
32
+
33
+ const {y} = useScrollPosition();
30
34
  </script>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <aside :class="$style.dashboardMenu">
2
+ <aside :class="[$style.dashboardMenu, isMenuCollapsed && $style.dashboardMenuCollapsed]">
3
3
  <FluxDashboardTopBar>
4
4
  <slot name="top-bar-start"/>
5
5
 
@@ -25,11 +25,14 @@
25
25
  setup>
26
26
  import { FluxIcon, FluxSpacer } from '@flux-ui/components';
27
27
  import type { FluxIconName } from '@flux-ui/types';
28
+ import { useDashboardInjection } from '$fluxDashboard/composable';
28
29
  import FluxDashboardTopBar from './FluxDashboardTopBar.vue';
29
- import $style from '$fluxDashboard/css/component/Dashboard.module.scss';
30
+ import $style from '$fluxDashboard/css/component/DashboardPane.module.scss';
30
31
 
31
32
  defineProps<{
32
33
  readonly icon?: FluxIconName;
33
34
  readonly title: string;
34
35
  }>();
36
+
37
+ const {isMenuCollapsed} = useDashboardInjection();
35
38
  </script>
@@ -1,31 +1,44 @@
1
1
  <template>
2
- <div
3
- v-for="index of 2"
4
- :key="index"
5
- :class="isNavigationCollapsed ? $style.dashboardNavigationRoundingFixCollapsed : $style.dashboardNavigationRoundingFix"/>
6
-
7
2
  <nav
8
3
  v-bind="$attrs"
9
4
  :class="isNavigationCollapsed ? $style.dashboardNavigationCollapsed : $style.dashboardNavigation">
10
- <router-link
11
- v-if="slots.logo"
12
- :class="$style.dashboardNavigationLogo"
13
- :to="logoLocation || '/'">
14
- <slot
15
- name="logo"
16
- v-bind="{isNavigationCollapsed}"/>
17
- </router-link>
18
-
19
- <slot/>
5
+ <header :class="$style.dashboardNavigationHeader">
6
+ <FluxMenuItem
7
+ icon-leading="bars"
8
+ @click="isNavigationCollapsed = !isNavigationCollapsed"/>
9
+
10
+ <router-link
11
+ v-if="slots.logo"
12
+ :class="$style.dashboardNavigationLogo"
13
+ :to="logoLocation || '/'">
14
+ <slot
15
+ name="logo"
16
+ v-bind="{isNavigationCollapsed}"/>
17
+ </router-link>
18
+
19
+ <FluxMenuItem
20
+ icon-leading="ellipsis-h"
21
+ @click="isMenuCollapsed = !isMenuCollapsed"/>
22
+ </header>
23
+
24
+ <div
25
+ v-for="index of 2"
26
+ :key="index"
27
+ :class="$style.dashboardNavigationRoundingFix"/>
28
+
29
+ <main :class="$style.dashboardNavigationNav">
30
+ <slot/>
31
+ </main>
20
32
  </nav>
21
33
  </template>
22
34
 
23
35
  <script
24
36
  lang="ts"
25
37
  setup>
38
+ import { FluxMenuItem } from '@flux-ui/components';
26
39
  import type { FluxTo } from '@flux-ui/types';
27
40
  import { useDashboardInjection } from '$fluxDashboard/composable';
28
- import $style from '$fluxDashboard/css/component/Dashboard.module.scss';
41
+ import $style from '$fluxDashboard/css/component/DashboardNavigation.module.scss';
29
42
 
30
43
  defineOptions({
31
44
  inheritAttrs: false
@@ -40,5 +53,8 @@
40
53
  logo?(): any;
41
54
  }>();
42
55
 
43
- const {isNavigationCollapsed} = useDashboardInjection();
56
+ const {
57
+ isMenuCollapsed,
58
+ isNavigationCollapsed
59
+ } = useDashboardInjection();
44
60
  </script>
@@ -19,7 +19,7 @@
19
19
  setup>
20
20
  import { FluxSpacer } from '@flux-ui/components';
21
21
  import FluxDashboardTopBar from './FluxDashboardTopBar.vue';
22
- import $style from '$fluxDashboard/css/component/Dashboard.module.scss';
22
+ import $style from '$fluxDashboard/css/component/DashboardPane.module.scss';
23
23
 
24
24
  defineProps<{
25
25
  readonly title: string;
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <header :class="$style.dashboardTopBar">
2
+ <header :class="[$style.dashboardTopBar, !isMenuCollapsed && $style.dashboardTopBarCollapsed]">
3
3
  <slot/>
4
4
  </header>
5
5
  </template>
@@ -7,5 +7,8 @@
7
7
  <script
8
8
  lang="ts"
9
9
  setup>
10
- import $style from '$fluxDashboard/css/component/Dashboard.module.scss';
10
+ import { useDashboardInjection } from '$fluxDashboard/composable';
11
+ import $style from '$fluxDashboard/css/component/DashboardTopBar.module.scss';
12
+
13
+ const {isMenuCollapsed} = useDashboardInjection();
11
14
  </script>
@@ -1,323 +1,67 @@
1
- .dashboard {
2
- --dashboard-navigation-background: rgb(var(--primary-11));
3
-
4
- display: grid;
5
- min-height: 100vh;
6
- grid-template-columns: auto minmax(0, 1fr);
7
- background: rgb(var(--gray-0));
8
-
9
- &:has(> .dashboardMenu) {
10
- grid-template-columns: auto 300px minmax(0, 1fr);
11
- }
12
-
13
- &:has(> .dashboardSide) {
14
- grid-template-columns: auto minmax(0, 1fr) 330px;
15
- }
1
+ @use '../../../../components/src/css/mixin';
16
2
 
17
- &:has(> .dashboardMenu):has(> .dashboardSide) {
18
- grid-template-columns: auto 300px minmax(0, 1fr) 330px;
19
- }
20
-
21
- .notice.isFluid + .dashboardContent:not(:has(> .table:first-child)) {
22
- padding-top: 30px;
23
- }
24
-
25
- .notice.isFluid + .dashboardContent > .table:first-child {
26
- border-top: 0;
27
- }
3
+ :root {
4
+ --dashboard-background: rgb(var(--gray-0));
5
+ --dashboard-duration: 360ms;
6
+ --dashboard-navigation-background: rgb(var(--primary-11));
7
+ --dashboard-navigation-foreground: rgb(var(--primary-0));
28
8
  }
29
9
 
30
- [dark] .dashboard {
10
+ [dark] {
31
11
  --dashboard-navigation-background: color-mix(in srgb, rgb(var(--gray-0)), black 50%);
32
12
  }
33
13
 
34
- .dashboardBody {
35
- position: relative;
36
- display: flex;
37
- flex-flow: column;
38
- flex-grow: 1;
39
- }
40
-
41
- .dashboardContent {
42
- display: flex;
43
- padding: 0 30px;
44
- flex-flow: column;
45
- flex-grow: 1;
46
-
47
- > :is(.calendar, .table) {
48
- margin-left: -30px;
49
- margin-right: -30px;
50
- height: calc(100dvh - 84px);
51
- flex-grow: 1;
52
- }
53
-
54
- > .calendar {
55
- border-left: 0;
56
- border-right: 0;
57
- border-radius: 0;
58
-
59
- .calendarActions {
60
- padding-left: 30px;
61
- padding-right: 30px;
62
- }
63
- }
64
-
65
- > .table {
66
- border-top: 1px solid rgb(var(--gray-3));
67
-
68
- .tableCell:first-child .tableCellContent {
69
- padding-left: 30px;
70
- }
71
-
72
- .tableCell:last-child .tableCellContent {
73
- padding-right: 30px;
74
- }
75
- }
76
- }
77
-
78
- .dashboardPane {
79
- position: sticky;
80
- top: 0;
81
- height: 100dvh;
82
- flex-shrink: 0;
83
- background: rgb(var(--gray-1));
84
- overflow: auto;
85
-
86
- .menuSubHeader {
87
- background: linear-gradient(to bottom, rgb(var(--gray-1)) 75%, transparent);
88
- }
89
-
90
- .dashboardTopBar {
91
- background: rgb(var(--gray-1) / .9);
92
- }
93
-
94
- .filter {
95
- --background: rgb(var(--gray-1));
96
-
97
- max-height: calc(100dvh - 84px);
98
- margin-top: -9px;
99
- padding: 9px 18px 18px;
100
- width: 100%;
101
-
102
- .filterHeader {
103
- margin-left: -18px;
104
- margin-right: -18px;
105
- padding-left: 18px;
106
- padding-right: 18px;
107
- }
108
-
109
- .menu {
110
- font-size: 14px;
111
- }
112
-
113
- .menuItemCommand {
114
- font-size: 12px;
115
- }
116
-
117
- .menuItemIcon {
118
- font-size: 16px;
119
- }
120
-
121
- .menu > :where(.divider, .separator) {
122
- margin-left: -18px;
123
- margin-right: -18px;
124
- }
125
- }
14
+ body:has(.root > .dashboard) {
15
+ background: var(--dashboard-background);
126
16
  }
127
17
 
128
- .dashboardMenu {
129
- composes: dashboardPane;
130
-
131
- border-right: 1px solid rgb(var(--gray-2));
18
+ .dashboard {
19
+ min-height: 100dvh;
132
20
  }
133
21
 
134
- .dashboardMenuBody {
135
- padding: 0 18px 30px;
22
+ .isResizing, .isResizing * {
23
+ animation: none !important;
24
+ transition: none !important;
136
25
  }
137
26
 
138
- .dashboardSide {
139
- composes: dashboardPane;
140
-
141
- border-left: 1px solid rgb(var(--gray-2));
142
- }
27
+ @include mixin.breakpoint-up(lg) {
28
+ .dashboard {
29
+ display: grid;
30
+ grid-template-areas: 'menu header side' 'menu content side';
31
+ grid-template-columns: auto minmax(0, 1fr) auto;
32
+ grid-template-rows: auto minmax(0, 1fr);
33
+ transition: padding-left var(--dashboard-duration) var(--swift-out);
143
34
 
144
- .dashboardNavigation {
145
- position: sticky;
146
- display: flex;
147
- top: 0;
148
- height: 100dvh;
149
- width: 300px;
150
- padding: 15px;
151
- flex-flow: column;
152
- gap: 15px;
153
- background: var(--dashboard-navigation-background);
154
- transition: width 210ms var(--swift-out);
155
-
156
- .divider {
157
- margin: 3px 15px;
158
- }
159
-
160
- .dividerLine {
161
- background: rgb(var(--primary-10));
162
- }
163
-
164
- .menu {
165
- flex-grow: 1;
166
- }
167
-
168
- .menuItem {
169
- padding: 15px;
170
- color: rgb(var(--primary-0));
171
- overflow: hidden;
172
-
173
- @media (hover: hover) {
174
- &:hover {
175
- background: rgb(var(--primary-10));
176
- }
35
+ &:has(> .dashboardNavigation) {
36
+ padding-left: 300px;
177
37
  }
178
38
 
179
- &:active {
180
- background: rgb(var(--primary-9));
39
+ &:has(> .dashboardNavigationCollapsed) {
40
+ padding-left: 84px;
181
41
  }
182
- }
183
-
184
- .menuItemHighlighted {
185
- background: rgb(var(--primary-10) / .5);
186
- }
187
-
188
- .menuItemIcon {
189
- color: rgb(var(--primary-0));
190
- font-size: 24px;
191
- }
192
42
 
193
- .menuItemLabel {
194
- transition: opacity 210ms var(--swift-out);
195
- white-space: nowrap;
196
- }
197
- }
198
-
199
- [dark] .dashboardNavigation {
200
- .dividerLine {
201
- background: rgb(var(--gray-2));
202
- }
203
-
204
- .menuItem {
205
- @media (hover: hover) {
206
- &:hover {
207
- background: rgb(var(--gray-2));
208
- }
43
+ .dashboardContent {
44
+ grid-area: content;
209
45
  }
210
46
 
211
- &:active {
212
- background: rgb(var(--gray-3));
47
+ .dashboardHeader {
48
+ grid-area: header;
213
49
  }
214
- }
215
50
 
216
- .menuItemHighlighted {
217
- background: rgb(var(--gray-1));
218
- }
219
- }
220
-
221
- .dashboardNavigationCollapsed {
222
- composes: dashboardNavigation;
223
-
224
- width: 84px;
225
-
226
- .menuItem {
227
- width: 54px;
228
- }
229
-
230
- .menuItemLabel {
231
- opacity: 0;
232
- }
233
- }
234
-
235
- .dashboardNavigationLogo {
236
- display: flex;
237
- height: 54px;
238
- width: 54px;
239
- align-items: center;
240
- justify-content: center;
241
-
242
- :is(svg) {
243
- max-height: 48px;
244
- max-width: 48px;
245
- width: 100%;
246
- }
247
- }
248
-
249
- .dashboardNavigationRoundingFix {
250
- position: fixed;
251
- display: block;
252
- left: 300px;
253
- height: var(--radius);
254
- width: var(--radius);
255
- content: '';
256
- background: var(--dashboard-navigation-background);
257
- transition: left 210ms var(--swift-out);
258
- z-index: 750;
259
-
260
- &::before {
261
- position: absolute;
262
- display: block;
263
- inset: 0;
264
- content: '';
265
- background: rgb(var(--gray-1));
266
- }
267
-
268
- &:first-of-type {
269
- top: 0;
270
-
271
- &::before {
272
- border-top-left-radius: var(--radius);
51
+ .dashboardMenu {
52
+ grid-area: menu;
273
53
  }
274
- }
275
-
276
- &:not(:first-of-type) {
277
- bottom: 0;
278
54
 
279
- &::before {
280
- border-bottom-left-radius: var(--radius);
55
+ .dashboardSide {
56
+ grid-area: side;
281
57
  }
282
58
  }
283
59
  }
284
60
 
285
- .dashboardNavigationRoundingFixCollapsed {
286
- composes: dashboardNavigationRoundingFix;
287
-
288
- left: 84px;
289
- }
290
-
291
- .dashboard:not(:has(.dashboardMenu)) .dashboardNavigationRoundingFix::before {
292
- background: rgb(var(--gray-0));
293
- }
294
-
295
- .dashboardTopBar {
296
- position: sticky;
297
- display: flex;
298
- top: 0;
299
- height: 84px;
300
- padding-left: 30px;
301
- padding-right: 30px;
302
- align-items: center;
303
- flex-flow: row;
304
- gap: 15px;
305
- background: rgb(var(--gray-0) / .9);
306
- backdrop-filter: blur(10px) saturate(180%);
307
- z-index: 100;
308
-
309
- > h1 {
310
- font-size: 18px;
311
- overflow: hidden;
312
- text-overflow: ellipsis;
313
- white-space: nowrap;
314
- }
315
-
316
- > .icon {
317
- color: var(--foreground-prominent);
318
- }
319
-
320
- > .separator {
321
- height: 24px;
61
+ @include mixin.breakpoint-down(md) {
62
+ .dashboard {
63
+ display: flex;
64
+ padding-top: 84px;
65
+ flex-flow: column;
322
66
  }
323
67
  }
@@ -0,0 +1,44 @@
1
+ @use '../../../../components/src/css/mixin';
2
+
3
+ .dashboardContent {
4
+ display: flex;
5
+ padding: 0 30px;
6
+ flex-flow: column;
7
+ flex-grow: 1;
8
+
9
+ > :is(.calendar, .table) {
10
+ margin-left: -30px;
11
+ margin-right: -30px;
12
+ height: calc(100dvh - 84px);
13
+ flex-grow: 1;
14
+ }
15
+
16
+ > .calendar {
17
+ border-left: 0;
18
+ border-right: 0;
19
+ border-radius: 0;
20
+
21
+ .calendarActions {
22
+ padding-left: 30px;
23
+ padding-right: 30px;
24
+ }
25
+ }
26
+
27
+ > .table {
28
+ border-top: 1px solid rgb(var(--gray-2));
29
+
30
+ .tableCell:first-child .tableCellContent {
31
+ padding-left: 30px;
32
+ }
33
+
34
+ .tableCell:last-child .tableCellContent {
35
+ padding-right: 30px;
36
+ }
37
+ }
38
+ }
39
+
40
+ @include mixin.breakpoint-down(md) {
41
+ .dashboardContentCollapsed {
42
+ display: none;
43
+ }
44
+ }