@flux-ui/components 3.0.0-next.28 → 3.0.0-next.29

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/components",
3
3
  "description": "A set of opiniated UI components.",
4
- "version": "3.0.0-next.28",
4
+ "version": "3.0.0-next.29",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/basmilius",
@@ -51,28 +51,28 @@
51
51
  "typings": "./dist/index.d.ts",
52
52
  "sideEffects": false,
53
53
  "dependencies": {
54
- "@basmilius/common": "^3.8.0",
55
- "@basmilius/utils": "^3.8.0",
56
- "@flux-ui/internals": "3.0.0-next.28",
57
- "@flux-ui/types": "3.0.0-next.28",
54
+ "@basmilius/common": "^3.11.0",
55
+ "@basmilius/utils": "^3.11.0",
56
+ "@flux-ui/internals": "3.0.0-next.29",
57
+ "@flux-ui/types": "3.0.0-next.29",
58
58
  "@fortawesome/fontawesome-common-types": "^7.2.0",
59
59
  "clsx": "^2.1.1",
60
60
  "imask": "^7.6.1",
61
61
  "lodash-es": "^4.17.23",
62
62
  "luxon": "^3.7.2",
63
- "vue": "^3.6.0-beta.6"
63
+ "vue": "^3.6.0-beta.7"
64
64
  },
65
65
  "devDependencies": {
66
- "@basmilius/vite-preset": "^3.8.0",
66
+ "@basmilius/vite-preset": "^3.11.0",
67
67
  "@types/lodash-es": "^4.17.12",
68
68
  "@types/luxon": "^3.7.1",
69
- "@types/node": "^25.3.1",
69
+ "@types/node": "^25.3.3",
70
70
  "@vitejs/plugin-vue": "^6.0.4",
71
- "@vue/tsconfig": "^0.8.1",
71
+ "@vue/tsconfig": "^0.9.0",
72
72
  "pinia": "^3.0.4",
73
73
  "sass-embedded": "^1.97.3",
74
74
  "typescript": "^5.9.3",
75
- "vite": "^8.0.0-beta.15",
75
+ "vite": "^8.0.0-beta.16",
76
76
  "vue-tsc": "^3.2.5"
77
77
  }
78
78
  }
@@ -20,16 +20,35 @@
20
20
  <template
21
21
  v-for="button of buttons"
22
22
  :key="button.name">
23
- <FluxSecondaryButton
24
- v-if="modelValue[button.name]"
25
- :icon-leading="button.icon"
26
- :label="button.label"/>
23
+ <FluxFlyout>
24
+ <template #opener="{open}">
25
+ <FluxSecondaryButton
26
+ v-if="modelValue[button.name]"
27
+ :class="$style.filterButton"
28
+ :icon-leading="button.icon"
29
+ :label="button.label"
30
+ @click="open()">
31
+ <template #after>
32
+ <FilterBadge
33
+ :item="button"
34
+ :value="modelValue[button.name]"/>
35
+ </template>
36
+ </FluxSecondaryButton>
37
+ </template>
38
+
39
+ <div :class="$style.filter">
40
+ <FluxMenu>
41
+ <VNodeRenderer :vnode="filters[button.name]"/>
42
+ </FluxMenu>
43
+ </div>
44
+ </FluxFlyout>
27
45
  </template>
28
46
 
29
47
  <template #overflow>
30
- <FluxSpacing
31
- v-if="buttons"
32
- :size="2"/>
48
+ <FluxSeparator
49
+ v-if="isFiltered"
50
+ direction="vertical"
51
+ style="margin-top: 9px; margin-bottom: 9px"/>
33
52
 
34
53
  <FluxFlyout>
35
54
  <template #opener="{open}">
@@ -56,14 +75,17 @@
56
75
  lang="ts"
57
76
  setup>
58
77
  import type { FluxFilterState } from '@flux-ui/types';
78
+ import { computed, unref } from "vue";
79
+ import { FilterBadge, VNodeRenderer } from '$flux/component/primitive';
59
80
  import FluxFilterBase from './FluxFilterBase.vue';
60
81
  import FluxFilterWindow from './FluxFilterWindow.vue';
61
82
  import FluxFlyout from './FluxFlyout.vue';
62
83
  import FluxFormInput from './FluxFormInput.vue';
84
+ import FluxMenu from '$flux/component/FluxMenu.vue';
63
85
  import FluxOverflowBar from './FluxOverflowBar.vue';
64
86
  import FluxSecondaryButton from './FluxSecondaryButton.vue';
87
+ import FluxSeparator from '$flux/component/FluxSeparator.vue';
65
88
  import $style from '$flux/css/component/Filter.module.scss';
66
- import FluxSpacing from '$flux/component/FluxSpacing.vue';
67
89
 
68
90
  const emit = defineEmits<{
69
91
  reset: [string];
@@ -87,6 +109,8 @@
87
109
  default?(): any;
88
110
  }>();
89
111
 
112
+ const isFiltered = computed(() => Object.entries(unref(modelValue)).filter(([, val]) => Boolean(val)).length > 0);
113
+
90
114
  function reset(name: string): void {
91
115
  emit('reset', name);
92
116
  }
@@ -76,7 +76,17 @@
76
76
  availableSize.value = direction === 'horizontal' ? bar.offsetWidth : bar.offsetHeight;
77
77
  itemSizes.value = Array.from(measurer.children)
78
78
  .filter(item => item instanceof HTMLElement)
79
- .map(item => direction === 'horizontal' ? item.offsetWidth : item.offsetHeight);
79
+ .map(item => {
80
+ const {display} = getComputedStyle(item);
81
+
82
+ if (display === 'contents') {
83
+ item = item.children[0] as HTMLElement;
84
+ }
85
+
86
+ return direction === 'horizontal'
87
+ ? item.offsetWidth
88
+ : item.offsetHeight;
89
+ });
80
90
 
81
91
  let size = 0;
82
92
  let visible = 0;
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <FluxBadge
3
+ v-if="valueLabel"
4
+ :class="$style.filterBadge"
5
+ :is-loading="isLoading"
6
+ :label="valueLabel"
7
+ @click="onClick"/>
8
+ </template>
9
+
10
+ <script
11
+ lang="ts"
12
+ setup>
13
+ import { useLoaded } from '@basmilius/common';
14
+ import type { FluxFilterItem, FluxFilterValue } from '@flux-ui/types';
15
+ import { computed, ref, unref, watch } from 'vue';
16
+ import { FluxBadge } from '$flux/component';
17
+ import $style from '$flux/css/component/Filter.module.scss';
18
+
19
+ const emit = defineEmits<{
20
+ click: [MouseEvent];
21
+ }>();
22
+
23
+ const {
24
+ item,
25
+ value
26
+ } = defineProps<{
27
+ readonly item: FluxFilterItem;
28
+ readonly value: FluxFilterValue;
29
+ }>();
30
+
31
+ const {isLoading, loaded} = useLoaded();
32
+ const getValueLabel = computed(() => loaded(item.getValueLabel));
33
+
34
+ const valueLabel = ref<string>();
35
+
36
+ function onClick(evt: MouseEvent): void {
37
+ emit('click', evt);
38
+ }
39
+
40
+ watch([() => item, () => value], async () => {
41
+ valueLabel.value = await unref(getValueLabel)(value) ?? undefined;
42
+ }, {deep: true, immediate: true});
43
+ </script>
@@ -1,5 +1,7 @@
1
1
  export { default as Anchor } from './Anchor.vue';
2
2
  export { default as AnchorPopup } from './AnchorPopup.vue';
3
+ export { default as FilterBadge } from './FilterBadge.vue';
4
+ export { default as FilterItem } from './FilterItem.vue';
3
5
  export { default as FilterOptionBase } from './FilterOptionBase.vue';
4
6
  export { default as SelectBase } from './SelectBase.vue';
5
7
  export { default as SliderBase } from './SliderBase.vue';
@@ -16,6 +16,7 @@
16
16
  color: var(--foreground-prominent);
17
17
  font-size: 13px;
18
18
  font-weight: 500;
19
+ font-variant-numeric: tabular-nums;
19
20
 
20
21
  &:is(a) {
21
22
  color: var(--foreground-prominent);
@@ -2,7 +2,7 @@
2
2
  max-height: 50dvh;
3
3
  max-width: 100%;
4
4
  padding: 9px;
5
- width: 300px;
5
+ width: 270px;
6
6
  overflow: auto;
7
7
  scrollbar-width: none;
8
8
  transition: height 150ms var(--deceleration-curve);
@@ -40,6 +40,32 @@
40
40
  }
41
41
  }
42
42
 
43
+ .filterBadge {
44
+ padding-left: 6px;
45
+ padding-right: 6px;
46
+ background: var(--primary-50);
47
+ border: 1px dashed var(--primary-300);
48
+ font-weight: 500;
49
+
50
+ :local(.badgeLabel) {
51
+ color: var(--primary-900);
52
+ }
53
+ }
54
+
55
+ .filterButton {
56
+ border: 1px solid var(--surface-stroke);
57
+ box-shadow: none;
58
+
59
+ :local(.badge) {
60
+ border-radius: var(--radius-half);
61
+ }
62
+
63
+ :local(.buttonIcon) {
64
+ color: var(--primary-700);
65
+ font-size: 16px;
66
+ }
67
+ }
68
+
43
69
  .filterHeader {
44
70
  position: sticky;
45
71
  margin: -9px -9px 3px;
@@ -70,13 +96,16 @@
70
96
 
71
97
  .filterSearch {
72
98
  position: sticky;
73
- top: 52px;
74
99
  margin: -9px -9px 0;
75
100
  padding: 9px;
76
101
  background: linear-gradient(to bottom, var(--surface) 75%, transparent);
77
102
  z-index: 1;
78
103
  }
79
104
 
105
+ .filterHeader + .menuGroup .filterSearch {
106
+ top: 52px;
107
+ }
108
+
80
109
  .filterBar {
81
110
  display: flex;
82
111
  flex-flow: row nowrap;
@@ -1,31 +1,33 @@
1
- .icon {
2
- height: 1em;
3
- width: 1em;
4
- flex-shrink: 0;
5
- font-size: 20px;
6
- line-height: 1;
7
- overflow: visible;
8
- }
1
+ @layer flux-base {
2
+ .icon {
3
+ height: 1em;
4
+ width: 1em;
5
+ flex-shrink: 0;
6
+ font-size: 20px;
7
+ line-height: 1;
8
+ overflow: visible;
9
+ }
9
10
 
10
- .fontAwesomeIcon {
11
- composes: icon;
11
+ .fontAwesomeIcon {
12
+ composes: icon;
12
13
 
13
- display: inline-block;
14
- }
14
+ display: inline-block;
15
+ }
15
16
 
16
- .iconBoxed {
17
- composes: basePane from './base/Pane.module.scss';
17
+ .iconBoxed {
18
+ composes: basePane from './base/Pane.module.scss';
18
19
 
19
- display: flex;
20
- height: 1em;
21
- width: 1em;
22
- align-items: center;
23
- flex-shrink: 0;
24
- justify-content: center;
25
- font-size: 42px;
20
+ display: flex;
21
+ height: 1em;
22
+ width: 1em;
23
+ align-items: center;
24
+ flex-shrink: 0;
25
+ justify-content: center;
26
+ font-size: 42px;
26
27
 
27
- .icon {
28
- font-size: .45em;
28
+ .icon {
29
+ font-size: .45em;
30
+ }
29
31
  }
30
32
  }
31
33
 
@@ -36,7 +36,8 @@
36
36
  }
37
37
 
38
38
  .itemMedia > .icon {
39
- font-size: 24px;
39
+ margin: 1px;
40
+ font-size: 20px;
40
41
  }
41
42
 
42
43
  .itemMedia > .iconBoxed {
@@ -65,7 +65,10 @@
65
65
  .menuItemIcon {
66
66
  composes: buttonIcon from './base/Button.module.scss';
67
67
 
68
+ margin-left: 2px;
69
+ margin-right: 2px;
68
70
  color: var(--foreground-prominent);
71
+ font-size: 16px;
69
72
  }
70
73
 
71
74
  .menuItemLabel {
@@ -57,14 +57,18 @@
57
57
 
58
58
  .timelineItemBody {
59
59
  position: relative;
60
- align-self: center;
60
+ display: flex;
61
61
  padding-top: 9px;
62
62
  padding-bottom: 9px;
63
+ align-items: flex-start;
64
+ align-self: center;
65
+ flex-flow: column;
66
+ gap: 15px;
63
67
  }
64
68
 
65
69
  .timelineItemHeader {
66
70
  display: flex;
67
- margin-bottom: 9px;
71
+ margin-bottom: -9px;
68
72
  flex-flow: column;
69
73
 
70
74
  :is(strong) {
@@ -84,4 +84,14 @@
84
84
  min-width: 42px;
85
85
  }
86
86
  }
87
+
88
+ .badge {
89
+ &:first-child {
90
+ margin-left: -6px;
91
+ }
92
+
93
+ &:last-child {
94
+ margin-right: -6px;
95
+ }
96
+ }
87
97
  }
@@ -7,7 +7,7 @@
7
7
 
8
8
  h1 {
9
9
  color: var(--foreground-prominent);
10
- font-size: 30px;
10
+ font-size: 27px;
11
11
  }
12
12
 
13
13
  h2 {
@@ -35,8 +35,10 @@
35
35
  a:not([class]), a[class=""] {
36
36
  color: var(--primary-600);
37
37
  text-decoration: underline;
38
+ text-decoration-color: var(--primary-400);
39
+ text-decoration-skip: auto;
38
40
  text-decoration-thickness: 1px;
39
- text-underline-offset: 3px;
41
+ text-underline-offset: 2px;
40
42
 
41
43
  &:hover {
42
44
  text-decoration: none;