@redseed/redseed-ui-vue3 8.31.1 → 8.32.0

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/index.js CHANGED
@@ -14,6 +14,7 @@ export * from './src/components/Card'
14
14
  export * from './src/components/CardGroup'
15
15
  export * from './src/components/Comment'
16
16
  export * from './src/components/Disclosure'
17
+ export * from './src/components/Drawer'
17
18
  export * from './src/components/DropdownMenu'
18
19
  export * from './src/components/Empty'
19
20
  export * from './src/components/FlexContainer'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redseed/redseed-ui-vue3",
3
- "version": "8.31.1",
3
+ "version": "8.32.0",
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,208 @@
1
+ <script setup>
2
+ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
3
+ import Icon from '../Icon/Icon.vue'
4
+ import { XMarkIcon } from '@heroicons/vue/24/outline'
5
+
6
+ defineOptions({
7
+ inheritAttrs: false,
8
+ })
9
+
10
+ const drawerHeaderId = _.uniqueId('drawer-header-')
11
+ const drawerPanelRef = ref(null)
12
+ let previousActiveElement = null
13
+
14
+ const props = defineProps({
15
+ show: {
16
+ type: Boolean,
17
+ default: false,
18
+ },
19
+ closeable: {
20
+ type: Boolean,
21
+ default: true,
22
+ },
23
+ sm: {
24
+ type: Boolean,
25
+ default: false,
26
+ },
27
+ md: {
28
+ type: Boolean,
29
+ default: false,
30
+ },
31
+ lg: {
32
+ type: Boolean,
33
+ default: false,
34
+ },
35
+ left: {
36
+ type: Boolean,
37
+ default: false,
38
+ },
39
+ headerPadded: {
40
+ type: Boolean,
41
+ default: true,
42
+ },
43
+ bodyPadded: {
44
+ type: Boolean,
45
+ default: true,
46
+ },
47
+ footerPadded: {
48
+ type: Boolean,
49
+ default: true,
50
+ },
51
+ closeIcon: {
52
+ type: Boolean,
53
+ default: false,
54
+ },
55
+ })
56
+
57
+ const defaultWidth = computed(() => !props.sm && !props.md && !props.lg)
58
+
59
+ const drawerPanelClass = computed(() => [
60
+ 'rsui-drawer__panel',
61
+ {
62
+ 'rsui-drawer__panel--sm': props.sm,
63
+ 'rsui-drawer__panel--md': props.md || defaultWidth.value,
64
+ 'rsui-drawer__panel--lg': props.lg,
65
+ 'rsui-drawer__panel--left': props.left,
66
+ },
67
+ ])
68
+
69
+ const offScreenClass = computed(() => props.left ? 'rsui-drawer__panel--off-screen-left' : 'rsui-drawer__panel--off-screen-right')
70
+
71
+ const emit = defineEmits(['close'])
72
+
73
+ const isMounted = ref(props.show)
74
+ const isOverlayVisible = ref(props.show)
75
+ const isPanelVisible = ref(props.show)
76
+
77
+ watch(() => props.show, (value) => {
78
+ if (value) {
79
+ previousActiveElement = document.activeElement
80
+ isMounted.value = true
81
+ isOverlayVisible.value = true
82
+ isPanelVisible.value = true
83
+ document.body.style.overflow = 'hidden'
84
+
85
+ nextTick(() => {
86
+ const panel = drawerPanelRef.value
87
+ if (!panel) return
88
+ const focusable = panel.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
89
+ if (focusable) {
90
+ focusable.focus()
91
+ } else {
92
+ panel.focus()
93
+ }
94
+ })
95
+ } else {
96
+ isPanelVisible.value = false
97
+ document.body.style.overflow = null
98
+
99
+ if (previousActiveElement && typeof previousActiveElement.focus === 'function') {
100
+ nextTick(() => previousActiveElement.focus())
101
+ }
102
+ }
103
+ })
104
+
105
+ function close() {
106
+ if (props.closeable) {
107
+ emit('close')
108
+ }
109
+ }
110
+
111
+ function closeOnEscape(e) {
112
+ if (e.key === 'Escape' && props.show && !e.repeat) {
113
+ close()
114
+ }
115
+ }
116
+
117
+ onMounted(() => document.addEventListener('keydown', closeOnEscape))
118
+
119
+ onUnmounted(() => {
120
+ document.removeEventListener('keydown', closeOnEscape)
121
+ document.body.style.overflow = null
122
+ })
123
+
124
+ function handlePanelAfterLeave() {
125
+ if (!props.show) {
126
+ isOverlayVisible.value = false
127
+ isMounted.value = false
128
+ }
129
+ }
130
+ </script>
131
+ <template>
132
+ <teleport to="body">
133
+ <div v-if="isMounted" class="rsui-drawer" v-bind="$attrs">
134
+ <div v-if="isOverlayVisible"
135
+ class="rsui-drawer__background-wrapper"
136
+ aria-hidden="true"
137
+ @click="close"
138
+ >
139
+ <div class="rsui-drawer__background" />
140
+ </div>
141
+
142
+ <Transition
143
+ appear
144
+ enter-active-class="rsui-drawer__panel--entering"
145
+ :enter-from-class="offScreenClass"
146
+ leave-active-class="rsui-drawer__panel--leaving"
147
+ :leave-to-class="offScreenClass"
148
+ @after-leave="handlePanelAfterLeave"
149
+ >
150
+ <div v-if="isPanelVisible"
151
+ ref="drawerPanelRef"
152
+ :class="drawerPanelClass"
153
+ role="dialog"
154
+ aria-modal="true"
155
+ :aria-labelledby="$slots.header ? drawerHeaderId : undefined"
156
+ tabindex="-1"
157
+ >
158
+ <button v-if="closeIcon"
159
+ type="button"
160
+ class="rsui-drawer__close-icon"
161
+ aria-label="Close"
162
+ @click="close"
163
+ >
164
+ <Icon disabled>
165
+ <XMarkIcon />
166
+ </Icon>
167
+ </button>
168
+
169
+ <div v-if="$slots.header"
170
+ :id="drawerHeaderId"
171
+ :class="{
172
+ 'rsui-drawer__header': true,
173
+ 'rsui-drawer__header--padded': headerPadded,
174
+ }"
175
+ >
176
+ <slot name="header"
177
+ :close="close"
178
+ ></slot>
179
+ </div>
180
+
181
+ <div
182
+ :class="{
183
+ 'rsui-drawer__body': true,
184
+ 'rsui-drawer__body--padded': bodyPadded,
185
+ }"
186
+ >
187
+ <slot>
188
+ <slot name="body"
189
+ :close="close"
190
+ ></slot>
191
+ </slot>
192
+ </div>
193
+
194
+ <div v-if="$slots.footer"
195
+ :class="{
196
+ 'rsui-drawer__footer': true,
197
+ 'rsui-drawer__footer--padded': footerPadded,
198
+ }"
199
+ >
200
+ <slot name="footer"
201
+ :close="close"
202
+ ></slot>
203
+ </div>
204
+ </div>
205
+ </Transition>
206
+ </div>
207
+ </teleport>
208
+ </template>
@@ -0,0 +1,5 @@
1
+ import Drawer from './Drawer.vue'
2
+
3
+ export {
4
+ Drawer,
5
+ }