@redseed/redseed-ui-vue3 8.32.0 → 8.32.2

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@redseed/redseed-ui-vue3",
3
- "version": "8.32.0",
3
+ "version": "8.32.2",
4
4
  "description": "RedSeed UI Vue 3 components",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/redseedtraining/redseed-ui",
@@ -0,0 +1,66 @@
1
+ <script setup>
2
+ import { computed } from 'vue'
3
+ import DropdownMenu from '../DropdownMenu/DropdownMenu.vue'
4
+ import ButtonTertiary from '../Button/ButtonTertiary.vue'
5
+ import Icon from '../Icon/Icon.vue'
6
+ import { AdjustmentsHorizontalIcon, CheckIcon } from '@heroicons/vue/24/outline'
7
+
8
+ const props = defineProps({
9
+ columns: {
10
+ type: Array,
11
+ required: true,
12
+ validator: value => value.every(c => typeof c === 'object' && c.key && c.name !== undefined),
13
+ },
14
+ label: {
15
+ type: String,
16
+ default: 'Columns',
17
+ },
18
+ })
19
+
20
+ const visibleKeys = defineModel('visibleKeys', { type: Array, required: true })
21
+
22
+ const isVisible = (key) => visibleKeys.value.includes(key)
23
+
24
+ function toggle(key) {
25
+ if (visibleKeys.value.includes(key)) {
26
+ visibleKeys.value = visibleKeys.value.filter(k => k !== key)
27
+ } else {
28
+ visibleKeys.value = [...visibleKeys.value, key]
29
+ }
30
+ }
31
+
32
+ const visibleCount = computed(() => visibleKeys.value.length)
33
+ const totalCount = computed(() => props.columns.length)
34
+ </script>
35
+
36
+ <template>
37
+ <DropdownMenu right class="rsui-column-picker">
38
+ <template #trigger="{ open, isOpen }">
39
+ <ButtonTertiary :aria-expanded="isOpen" @click="open">
40
+ <Icon sm><AdjustmentsHorizontalIcon /></Icon>
41
+ {{ label }}
42
+ <span class="rsui-column-picker__count">{{ visibleCount }}/{{ totalCount }}</span>
43
+ </ButtonTertiary>
44
+ </template>
45
+
46
+ <ul class="rsui-column-picker__list" role="none">
47
+ <li v-for="column in columns"
48
+ :key="column.key"
49
+ class="rsui-column-picker__item"
50
+ role="menuitemcheckbox"
51
+ :aria-checked="isVisible(column.key)"
52
+ tabindex="0"
53
+ @click.stop="toggle(column.key)"
54
+ @keydown.enter.self.prevent="toggle(column.key)"
55
+ @keydown.space.self.prevent="toggle(column.key)"
56
+ >
57
+ <span :class="['rsui-column-picker__check', { 'rsui-column-picker__check--on': isVisible(column.key) }]"
58
+ aria-hidden="true"
59
+ >
60
+ <CheckIcon v-if="isVisible(column.key)" />
61
+ </span>
62
+ <span class="rsui-column-picker__label">{{ column.name || column.key }}</span>
63
+ </li>
64
+ </ul>
65
+ </DropdownMenu>
66
+ </template>
@@ -1,8 +1,10 @@
1
1
  <script setup>
2
+ import { computed, ref, watch } from 'vue'
2
3
  import { Card, CardHeader } from '../Card'
3
4
  import Tr from './Tr.vue'
4
5
  import Th from './Th.vue'
5
6
  import Td from './Td.vue'
7
+ import ColumnPicker from './ColumnPicker.vue'
6
8
 
7
9
  const titleId = _.uniqueId('table-title-')
8
10
 
@@ -39,6 +41,47 @@ const props = defineProps({
39
41
  type: String,
40
42
  default: undefined,
41
43
  },
44
+ // When true, Table renders an internal ColumnPicker in the header actions and
45
+ // manages column visibility itself. Use `v-model:visibleKeys` to persist or
46
+ // override the selection externally; without v-model, all columns are visible
47
+ // by default and toggling is purely local.
48
+ columnPicker: {
49
+ type: Boolean,
50
+ default: false,
51
+ },
52
+ })
53
+
54
+ // v-model:visibleKeys — undefined means "uncontrolled" / use internal default (all visible).
55
+ const visibleKeys = defineModel('visibleKeys', {
56
+ type: Array,
57
+ default: undefined,
58
+ })
59
+
60
+ // Internal fallback when no v-model is bound but columnPicker is enabled.
61
+ const internalVisibleKeys = ref(props.columns.map(c => c.key))
62
+
63
+ watch(() => props.columns, (next) => {
64
+ // Keep internal state in sync if the columns array changes (e.g. consumer adds/removes a column).
65
+ const knownKeys = next.map(c => c.key)
66
+ internalVisibleKeys.value = internalVisibleKeys.value.filter(k => knownKeys.includes(k))
67
+ for (const key of knownKeys) {
68
+ if (!internalVisibleKeys.value.includes(key)) internalVisibleKeys.value.push(key)
69
+ }
70
+ }, { deep: true })
71
+
72
+ const effectiveVisibleKeys = computed({
73
+ get: () => visibleKeys.value ?? internalVisibleKeys.value,
74
+ set: (next) => {
75
+ if (visibleKeys.value !== undefined) {
76
+ visibleKeys.value = next
77
+ } else {
78
+ internalVisibleKeys.value = next
79
+ }
80
+ },
81
+ })
82
+
83
+ const visibleColumns = computed(() => {
84
+ return props.columns.filter(column => effectiveVisibleKeys.value.includes(column.key))
42
85
  })
43
86
  </script>
44
87
 
@@ -67,8 +110,12 @@ const props = defineProps({
67
110
  <slot name="subtitle"></slot>
68
111
  </template>
69
112
 
70
- <template #actions v-if="$slots.actions">
113
+ <template #actions v-if="$slots.actions || columnPicker">
71
114
  <slot name="actions"></slot>
115
+ <ColumnPicker v-if="columnPicker"
116
+ :columns="columns"
117
+ v-model:visibleKeys="effectiveVisibleKeys"
118
+ />
72
119
  </template>
73
120
 
74
121
  <template #more-actions v-if="$slots['more-actions']">
@@ -93,15 +140,16 @@ const props = defineProps({
93
140
  :aria-label="!showHeader || !$slots.title ? ariaLabel : undefined"
94
141
  >
95
142
  <caption v-if="!showHeader && $slots.title"><slot name="title"></slot></caption>
96
- <thead v-if="columns">
143
+ <thead v-if="visibleColumns.length">
97
144
  <Tr>
98
- <Th v-for="column in columns"
145
+ <Th v-for="column in visibleColumns"
99
146
  :key="column.key"
100
147
  scope="col"
101
148
  :alignment="column?.alignment"
102
149
  :fixed="fixedColumns"
103
150
  :width="column?.width"
104
151
  :sort="column?.sort"
152
+ :pinned="!!column?.pinned"
105
153
  @sort="$emit('sort', { column: { ...column, ...$event } })"
106
154
  >
107
155
  <slot :name="'th-' + column.key"
@@ -119,10 +167,11 @@ const props = defineProps({
119
167
  :clickable="row.clickable ?? clickableRows"
120
168
  @click="$emit('click:row', row)"
121
169
  >
122
- <Td v-for="column in columns"
170
+ <Td v-for="column in visibleColumns"
123
171
  :key="column.key"
124
172
  :alignment="column?.alignment"
125
173
  :fixed="fixedColumns"
174
+ :pinned="!!column?.pinned"
126
175
  >
127
176
  <slot :name="'td-' + column.key"
128
177
  :row="row"
@@ -9,6 +9,10 @@ const props = defineProps({
9
9
  type: Boolean,
10
10
  default: false,
11
11
  },
12
+ pinned: {
13
+ type: Boolean,
14
+ default: false,
15
+ },
12
16
  })
13
17
  </script>
14
18
 
@@ -20,6 +24,7 @@ const props = defineProps({
20
24
  'rsui-td--right': alignment === 'right',
21
25
  'rsui-td--auto': !fixed,
22
26
  'rsui-td--fixed': fixed,
27
+ 'rsui-td--pinned': pinned,
23
28
  },
24
29
  ]">
25
30
  <slot></slot>
@@ -28,6 +28,10 @@ const props = defineProps({
28
28
  default: false,
29
29
  validator: value => ['asc', 'desc', true, false].includes(value),
30
30
  },
31
+ pinned: {
32
+ type: Boolean,
33
+ default: false,
34
+ },
31
35
  })
32
36
 
33
37
  const sortable = computed(() => props.sort !== false)
@@ -79,6 +83,7 @@ function handleSort(event) {
79
83
  'rsui-th--xl': fixed && width === 'xl',
80
84
  'rsui-th--2xl': fixed && width === '2xl',
81
85
  'rsui-th--sortable': sortable,
86
+ 'rsui-th--pinned': pinned,
82
87
  },
83
88
  ]"
84
89
  :scope="scope"