@flux-ui/components 3.0.0-next.4 → 3.0.0-next.6

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.4",
4
+ "version": "3.0.0-next.6",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/basmilius",
@@ -52,9 +52,9 @@
52
52
  "typings": "./dist/index.d.ts",
53
53
  "sideEffects": false,
54
54
  "dependencies": {
55
- "@basmilius/utils": "^1.13.0",
56
- "@flux-ui/internals": "3.0.0-next.4",
57
- "@flux-ui/types": "3.0.0-next.4",
55
+ "@basmilius/utils": "^1.14.0",
56
+ "@flux-ui/internals": "3.0.0-next.6",
57
+ "@flux-ui/types": "3.0.0-next.6",
58
58
  "@fortawesome/fontawesome-common-types": "^6.7.2",
59
59
  "clsx": "^2.1.1",
60
60
  "imask": "^7.6.1",
@@ -1,12 +1,19 @@
1
1
  <template>
2
2
  <FluxTable
3
+ :fill-columns="fillColumns"
3
4
  :is-bordered="isBordered"
4
5
  :is-hoverable="isHoverable"
5
6
  :is-loading="isLoading"
6
7
  :is-separated="isSeparated"
7
8
  :is-striped="isStriped">
8
9
  <template
9
- v-if="'header' in slots"
10
+ v-if="slots.colgroups"
11
+ #colgroups>
12
+ <slot name="colgroups"/>
13
+ </template>
14
+
15
+ <template
16
+ v-if="slots.header"
10
17
  #header>
11
18
  <FluxTableRow>
12
19
  <slot
@@ -16,7 +23,7 @@
16
23
  </template>
17
24
 
18
25
  <template
19
- v-if="'footer' in slots"
26
+ v-if="slots.footer"
20
27
  #footer>
21
28
  <FluxTableRow>
22
29
  <slot
@@ -25,12 +32,28 @@
25
32
  </FluxTableRow>
26
33
  </template>
27
34
 
35
+ <template
36
+ v-if="total > limits[0]"
37
+ #pagination>
38
+ <slot
39
+ name="pagination"
40
+ v-bind="{page, perPage, rows, total}">
41
+ <FluxPaginationBar
42
+ :limits="limits"
43
+ :page="page"
44
+ :per-page="perPage"
45
+ :total="total"
46
+ @limit="emit('limit', $event)"
47
+ @navigate="emit('navigate', $event)"/>
48
+ </slot>
49
+ </template>
50
+
28
51
  <FluxTableRow
29
52
  v-for="(row, index) of rows"
30
53
  :key="uniqueKey ? row[uniqueKey] : index">
31
54
  <template v-for="(_, name) of slots">
32
55
  <slot
33
- v-if="name !== 'footer' && name !== 'header'"
56
+ v-if="!IGNORED_SLOTS.includes(name as string)"
34
57
  v-bind="{index, page, perPage, row, rows, total}"
35
58
  :name="name"/>
36
59
  </template>
@@ -42,10 +65,19 @@
42
65
  lang="ts"
43
66
  setup
44
67
  generic="T extends Record<string, any>">
68
+ import type { VNode } from 'vue';
45
69
  import { computed } from 'vue';
70
+ import FluxPaginationBar from './FluxPaginationBar.vue';
46
71
  import FluxTable from './FluxTable.vue';
47
72
  import FluxTableRow from './FluxTableRow.vue';
48
73
 
74
+ const IGNORED_SLOTS: string[] = ['header', 'footer', 'colgroups', 'pagination'];
75
+
76
+ const emit = defineEmits<{
77
+ limit: [number];
78
+ navigate: [number];
79
+ }>();
80
+
49
81
  const {
50
82
  isBordered = true,
51
83
  isHoverable = false,
@@ -56,11 +88,13 @@
56
88
  perPage
57
89
  } = defineProps<{
58
90
  readonly dataSet: T[];
91
+ readonly fillColumns?: number;
59
92
  readonly isBordered?: boolean;
60
93
  readonly isHoverable?: boolean;
61
94
  readonly isLoading?: boolean;
62
95
  readonly isSeparated?: boolean;
63
96
  readonly isStriped?: boolean;
97
+ readonly limits: number[];
64
98
  readonly page: number;
65
99
  readonly perPage: number;
66
100
  readonly total: number;
@@ -75,21 +109,30 @@
75
109
  readonly row: T;
76
110
  readonly rows: T[];
77
111
  readonly total: number;
78
- }) => any;
112
+ }) => VNode;
79
113
 
80
114
  footer(props: {
81
115
  readonly page: number;
82
116
  readonly perPage: number;
83
117
  readonly rows: T[];
84
118
  readonly total: number;
85
- }): any;
119
+ }): VNode;
86
120
 
87
121
  header(props: {
88
122
  readonly page: number;
89
123
  readonly perPage: number;
90
124
  readonly rows: T[];
91
125
  readonly total: number;
92
- }): any;
126
+ }): VNode;
127
+
128
+ pagination(props: {
129
+ readonly page: number;
130
+ readonly perPage: number;
131
+ readonly rows: T[];
132
+ readonly total: number;
133
+ }): VNode;
134
+
135
+ colgroups(): VNode;
93
136
  }>();
94
137
 
95
138
  const rows = computed(() => dataSet.slice(0, perPage));
@@ -1,52 +1,56 @@
1
1
  <template>
2
- <FluxButtonGroup
2
+ <nav
3
3
  :class="$style.pagination"
4
4
  role="navigation"
5
5
  :aria-label="translate('flux.pagination')">
6
- <FluxSecondaryButton
6
+ <FluxPaginationButton
7
7
  v-if="arrows || isCompact"
8
8
  :disabled="isPreviousDisabled"
9
9
  icon-leading="angle-left"
10
+ is-arrow
10
11
  :aria-label="translate('flux.previous')"
11
12
  @click="previous"/>
12
13
 
13
14
  <template
14
15
  v-if="!isCompact"
15
16
  v-for="p of visiblePages">
16
- <FluxSecondaryButton
17
+ <FluxPaginationButton
17
18
  v-if="p === 'dots'"
18
19
  disabled
19
- icon-leading="ellipsis-h"/>
20
+ icon-leading="ellipsis-h"
21
+ is-spacer/>
20
22
 
21
- <FluxPrimaryButton
23
+ <FluxPaginationButton
22
24
  v-else-if="p === page"
25
+ is-current
23
26
  :label="`${p}`"
24
27
  aria-current="page"/>
25
28
 
26
- <FluxSecondaryButton
29
+ <FluxPaginationButton
27
30
  v-else
28
31
  :label="`${p}`"
29
32
  @click="navigate(p)"/>
30
33
  </template>
31
34
 
32
35
  <template v-else>
33
- <FluxSecondaryButton
36
+ <FluxPaginationButton
34
37
  :class="$style.paginationCurrentZZ"
35
38
  @click="prompt"
36
39
  #before>
37
40
  <strong>{{ page }}</strong>
38
41
  <span>/</span>
39
42
  <span>{{ pages }}</span>
40
- </FluxSecondaryButton>
43
+ </FluxPaginationButton>
41
44
  </template>
42
45
 
43
- <FluxSecondaryButton
46
+ <FluxPaginationButton
44
47
  v-if="arrows || isCompact"
45
48
  :disabled="isNextDisabled"
46
49
  icon-leading="angle-right"
50
+ is-arrow
47
51
  :aria-label="translate('flux.next')"
48
52
  @click="next"/>
49
- </FluxButtonGroup>
53
+ </nav>
50
54
  </template>
51
55
 
52
56
  <script
@@ -55,9 +59,10 @@
55
59
  import { computed, unref } from 'vue';
56
60
  import { useTranslate } from '$flux/composable/private';
57
61
  import { showPrompt } from '$flux/data';
62
+ import FluxButton from './FluxButton.vue';
58
63
  import FluxButtonGroup from './FluxButtonGroup.vue';
64
+ import FluxPaginationButton from './FluxPaginationButton.vue';
59
65
  import FluxPrimaryButton from './FluxPrimaryButton.vue';
60
- import FluxSecondaryButton from './FluxSecondaryButton.vue';
61
66
  import $style from '$flux/css/component/Pagination.module.scss';
62
67
 
63
68
  const emit = defineEmits<{
@@ -89,7 +94,7 @@
89
94
 
90
95
  const sizes = {
91
96
  end: 1,
92
- middle: 1
97
+ middle: 2
93
98
  } as const;
94
99
 
95
100
  let dots = false;
@@ -1,40 +1,32 @@
1
1
  <template>
2
- <FluxStack :class="$style.paginationBar">
3
- <FluxFormInputGroup>
4
- <FluxFormInputAddition>
5
- <span>{{ translate('flux.rowsPerPage') }}</span>
6
- </FluxFormInputAddition>
7
-
8
- <FluxFormSelect
9
- v-model="limit"
10
- :options="limitOptions"/>
11
- </FluxFormInputGroup>
2
+ <div :class="$style.paginationBar">
3
+ <FluxPagination
4
+ v-if="total > perPage"
5
+ arrows
6
+ :page="page"
7
+ :per-page="perPage"
8
+ :total="total"
9
+ @navigate="$emit('navigate', $event)"/>
12
10
 
13
11
  <FluxSpacer :class="$style.paginationBarSpacer"/>
14
12
 
15
- <FluxFormInputGroup>
16
- <FluxFormInputAddition>
17
- <span>
18
- {{
19
- translate('flux.displayingOf', {
20
- from: (page - 1) * perPage + 1,
21
- to: Math.min(total, page * perPage),
22
- total: total
23
- })
24
- }}
25
- </span>
26
- </FluxFormInputAddition>
13
+ <div :class="$style.paginationBarLimit">
14
+ <span :class="$style.paginationBarLimitDisplayingOf">
15
+ {{
16
+ translate('flux.displayingOf', {
17
+ from: (page - 1) * perPage + 1,
18
+ to: Math.min(total, page * perPage),
19
+ total: total
20
+ })
21
+ }}
22
+ </span>
27
23
 
28
- <FluxPagination
29
- v-if="total > perPage"
30
- arrows
31
- is-compact
32
- :page="page"
33
- :per-page="perPage"
34
- :total="total"
35
- @navigate="$emit('navigate', $event)"/>
36
- </FluxFormInputGroup>
37
- </FluxStack>
24
+ <FluxFormSelect
25
+ v-model="limit"
26
+ :class="$style.paginationBarLimitSelect"
27
+ :options="limitOptions"/>
28
+ </div>
29
+ </div>
38
30
  </template>
39
31
 
40
32
  <script
@@ -70,9 +62,9 @@
70
62
 
71
63
  const limit = ref(perPage);
72
64
 
73
- const limitOptions = computed(() => limits.map<FluxFormSelectOption>(limit => ({
74
- label: limit.toString(),
75
- value: limit
65
+ const limitOptions = computed(() => limits.map<FluxFormSelectOption>(n => ({
66
+ label: translate('flux.showN', {n}),
67
+ value: n
76
68
  })));
77
69
 
78
70
  watch(limit, limit => emit('limit', limit));
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <FluxButton
3
+ :="{type, disabled, iconLeading, iconTrailing, isFilled, isLoading, isSubmit, label, size, href, rel, target, to}"
4
+ :class="[
5
+ isArrow && $style.paginationButtonArrow,
6
+ isCurrent && $style.paginationButtonCurrent,
7
+ isSpacer && $style.paginationButtonSpacer
8
+ ]"
9
+ :css-class="$style.paginationButton"
10
+ :css-class-icon="$style.paginationButtonIcon"
11
+ :css-class-label="$style.paginationButtonLabel"
12
+ @click="$emit('click', $event)"
13
+ @mouseenter="$emit('mouseenter', $event)"
14
+ @mouseleave="$emit('mouseleave', $event)">
15
+ <template
16
+ v-for="slot of SLOTS"
17
+ #[slot]>
18
+ <slot :name="slot"/>
19
+ </template>
20
+ </FluxButton>
21
+ </template>
22
+
23
+ <script
24
+ lang="ts"
25
+ setup>
26
+ import type { FluxButtonEmits, FluxButtonProps, FluxButtonSlots } from '@flux-ui/types';
27
+ import FluxButton, { SLOTS } from './FluxButton.vue';
28
+ import $style from '$flux/css/component/Pagination.module.scss';
29
+
30
+ defineEmits<FluxButtonEmits>();
31
+
32
+ defineProps<FluxButtonProps & {
33
+ readonly isArrow?: boolean;
34
+ readonly isCurrent?: boolean;
35
+ readonly isSpacer?: boolean;
36
+ }>();
37
+
38
+ defineSlots<FluxButtonSlots>();
39
+ </script>
@@ -1,12 +1,20 @@
1
1
  <template>
2
2
  <div :class="$style.table">
3
3
  <table :class="$style.tableBase">
4
+ <slot name="colgroups"/>
5
+
4
6
  <thead v-if="slots.header">
5
7
  <slot name="header"/>
6
8
  </thead>
7
9
 
8
10
  <tbody v-if="slots.default">
9
11
  <slot/>
12
+
13
+ <FluxTableRow
14
+ v-if="fillColumns"
15
+ :class="$style.tableFill">
16
+ <FluxTableCell v-for="_ of fillColumns"/>
17
+ </FluxTableRow>
10
18
  </tbody>
11
19
 
12
20
  <tfoot v-if="slots.footer">
@@ -25,15 +33,25 @@
25
33
  :class="$style.tableLoader">
26
34
  <FluxSpinner/>
27
35
  </div>
36
+
37
+ <FluxPaneBody
38
+ v-if="slots.pagination"
39
+ :class="$style.tablePagination">
40
+ <slot name="pagination"/>
41
+ </FluxPaneBody>
28
42
  </div>
29
43
  </template>
30
44
 
31
45
  <script
32
46
  lang="ts"
33
47
  setup>
48
+ import type { VNode } from 'vue';
34
49
  import { provide } from 'vue';
35
50
  import { FluxTableInjectionKey } from '$flux/data';
51
+ import FluxPaneBody from './FluxPaneBody.vue';
36
52
  import FluxSpinner from './FluxSpinner.vue';
53
+ import FluxTableCell from './FluxTableCell.vue';
54
+ import FluxTableRow from './FluxTableRow.vue';
37
55
  import $style from '$flux/css/component/Table.module.scss';
38
56
 
39
57
  const {
@@ -45,6 +63,7 @@
45
63
  isStriped = false
46
64
  } = defineProps<{
47
65
  readonly captionSide?: 'top' | 'bottom';
66
+ readonly fillColumns?: number;
48
67
  readonly isBordered?: boolean;
49
68
  readonly isHoverable?: boolean;
50
69
  readonly isLoading?: boolean;
@@ -53,10 +72,12 @@
53
72
  }>();
54
73
 
55
74
  const slots = defineSlots<{
56
- default?(): any;
57
- caption?(): any;
58
- footer?(): any;
59
- header?(): any;
75
+ default?(): VNode;
76
+ colgroups?(): VNode;
77
+ caption?(): VNode;
78
+ footer?(): VNode;
79
+ header?(): VNode;
80
+ pagination?(): VNode;
60
81
  }>();
61
82
 
62
83
  provide(FluxTableInjectionKey, {
@@ -1,59 +1,91 @@
1
1
  @use '$flux/css/mixin';
2
2
 
3
3
  .pagination {
4
- z-index: 0;
4
+ display: flex;
5
+ gap: 1px;
6
+ }
5
7
 
6
- .button {
7
- min-width: 40px;
8
- }
8
+ .paginationButton {
9
+ composes: secondaryButton from './Button.module.scss';
10
+
11
+ height: 36px;
12
+ min-width: 36px;
13
+ padding: 0 6px;
14
+ background: unset;
15
+ border-color: transparent;
16
+ box-shadow: none !important;
17
+ }
9
18
 
10
- .buttonLabel {
11
- margin-left: 0;
12
- margin-right: 0;
13
- min-width: 18px;
19
+ .paginationButtonArrow {
20
+ border-color: rgb(var(--gray-3));
14
21
 
15
- &:nth-child(2) {
16
- min-width: unset;
17
- }
22
+ &:first-child {
23
+ margin-right: 6px;
18
24
  }
19
25
 
20
- .primaryButton {
21
- z-index: 1;
26
+ &:last-child {
27
+ margin-left: 6px;
22
28
  }
23
29
  }
24
30
 
25
- .paginationCurrent {
26
- gap: 3px;
27
- font-variant-numeric: tabular-nums;
31
+ .paginationButtonCurrent {
32
+ background: rgb(var(--primary-1));
33
+ border-color: rgb(var(--primary-3));
34
+ }
35
+
36
+ .paginationButtonSpacer {
37
+ pointer-events: none;
38
+ }
39
+
40
+ .paginationButtonIcon {
41
+ composes: secondaryButtonIcon from './Button.module.scss';
42
+
43
+ font-size: 16px;
44
+ }
45
+
46
+ .paginationButtonLabel {
47
+ composes: secondaryButtonLabel from './Button.module.scss';
48
+
49
+ margin: 0;
50
+ min-width: unset;
51
+ font-size: 12px;
28
52
  }
29
53
 
30
54
  .paginationBar {
55
+ display: flex;
31
56
  align-items: center;
32
- flex-direction: column;
57
+ flex-flow: row;
58
+ }
33
59
 
34
- .formInputGroup {
35
- max-width: max-content;
36
- }
60
+ .paginationBarLimit {
61
+ display: flex;
62
+ align-items: center;
63
+ flex-flow: row;
64
+ gap: 15px;
65
+ }
37
66
 
38
- .formSelect {
39
- width: 78px;
40
- }
67
+ .paginationBarLimitDisplayingOf {
68
+ font-size: 12px;
69
+ font-weight: 600;
70
+ }
41
71
 
42
- .pagination .button:first-child {
43
- border-radius: 0;
44
- }
72
+ .paginationBarLimitSelect {
73
+ min-height: 36px;
74
+ width: 120px;
45
75
 
46
- @include mixin.breakpoint-up(lg) {
47
- flex-direction: row;
76
+ .menuItem {
77
+ min-height: 36px;
48
78
  }
49
- }
50
79
 
51
- @include mixin.breakpoint-down(md) {
52
- .paginationBarSpacer {
53
- display: none;
80
+ .menuItemLabel {
81
+ font-size: 12px;
82
+ font-weight: 600;
54
83
  }
55
84
  }
56
85
 
57
- .paneFooter > .paginationBar {
58
- flex-grow: 1;
86
+ @include mixin.breakpoint-down(sm) {
87
+ .paginationBar {
88
+ flex-flow: column;
89
+ gap: 15px;
90
+ }
59
91
  }
@@ -2,6 +2,8 @@
2
2
  composes: basePaneElement from './base/Pane.module.scss';
3
3
 
4
4
  position: relative;
5
+ display: flex;
6
+ flex-flow: column;
5
7
  overflow: auto;
6
8
 
7
9
  :is(caption) {
@@ -16,6 +18,14 @@
16
18
  bottom: 0;
17
19
  border-spacing: 0;
18
20
  text-align: left;
21
+
22
+ &:has(.tableFill) {
23
+ flex-grow: 1;
24
+ }
25
+
26
+ &:has(.tableFill):has(.tablePagination) {
27
+ margin-bottom: -1px;
28
+ }
19
29
  }
20
30
 
21
31
  .tableLoader {
@@ -109,6 +119,27 @@ tfoot .tableCell {
109
119
  }
110
120
  }
111
121
 
122
+ .tableFill {
123
+ pointer-events: none;
124
+
125
+ .tableCell {
126
+ height: 100%;
127
+ }
128
+
129
+ .tableCellContent {
130
+ padding: 0;
131
+ }
132
+ }
133
+
134
+ .tablePagination {
135
+ position: sticky;
136
+ bottom: 0;
137
+ margin-top: auto;
138
+ background: rgb(var(--gray-0));
139
+ border-top: 1px solid rgb(var(--gray-2));
140
+ z-index: 100;
141
+ }
142
+
112
143
  .tableSort {
113
144
  display: flex;
114
145
  height: 24px;
package/src/data/i18n.ts CHANGED
@@ -18,7 +18,7 @@ export const english = {
18
18
  'flux.preview': 'Preview',
19
19
  'flux.previewClose': 'Close preview',
20
20
  'flux.displayingOf': '{from}–{to} of {total}',
21
- 'flux.rowsPerPage': 'Rows per page',
21
+ 'flux.showN': 'Show {n}',
22
22
  'flux.next': 'Next',
23
23
  'flux.noItems': 'There are no items (left).',
24
24
  'flux.pagination': 'Pagination',