frappe-ui 0.1.178 → 0.1.179
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 +1 -1
- package/src/components/Charts/NumberChart.vue +40 -34
- package/src/components/Dialog/Dialog.story.vue +175 -16
- package/src/components/Dialog/Dialog.vue +133 -85
- package/src/components/Dialog/index.ts +14 -2
- package/src/components/Dialog/types.ts +2 -2
- package/src/components/KeyboardShortcut.vue +33 -0
- package/src/components/Password/Password.story.vue +14 -0
- package/src/components/Password/Password.vue +58 -0
- package/src/components/Password/index.ts +2 -0
- package/src/components/Password/types.ts +4 -0
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -3,46 +3,52 @@
|
|
|
3
3
|
class="flex max-h-[140px] items-center gap-2 overflow-hidden bg-surface-white text-ink-gray-8 px-6 pt-5"
|
|
4
4
|
:class="config.delta ? 'pb-6' : 'pb-3'"
|
|
5
5
|
>
|
|
6
|
-
<
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
<slot name="body">
|
|
7
|
+
<div class="flex w-full flex-col">
|
|
8
|
+
<slot name="title">
|
|
9
|
+
<span class="truncate text-sm font-medium text-ink-gray-5">
|
|
10
|
+
{{ config.title }}
|
|
11
|
+
</span>
|
|
12
|
+
</slot>
|
|
13
|
+
<slot name="subtitle" v-bind="{ formatValue }">
|
|
14
|
+
<div
|
|
15
|
+
class="flex-1 flex-shrink-0 truncate text-[24px] text-ink-gray-6 font-semibold leading-10"
|
|
16
|
+
>
|
|
17
|
+
{{ config.prefix }}{{ formatValue(config.value, 1, true)
|
|
18
|
+
}}{{ config.suffix }}
|
|
19
|
+
</div>
|
|
20
|
+
</slot>
|
|
21
|
+
<slot name="delta" v-bind="{ formatValue }">
|
|
22
|
+
<div
|
|
23
|
+
v-if="config.delta"
|
|
24
|
+
class="flex items-center gap-0.5 text-xs font-medium"
|
|
25
|
+
:class="[
|
|
26
|
+
config.negativeIsBetter
|
|
27
|
+
? config.delta >= 0
|
|
28
|
+
? 'text-ink-red-4'
|
|
29
|
+
: 'text-ink-green-3'
|
|
30
|
+
: config.delta >= 0
|
|
31
|
+
? 'text-ink-green-3'
|
|
32
|
+
: 'text-ink-red-4',
|
|
33
|
+
]"
|
|
34
|
+
>
|
|
35
|
+
<span class="">
|
|
36
|
+
{{ config.delta >= 0 ? '↑' : '↓' }}
|
|
37
|
+
</span>
|
|
38
|
+
<span>
|
|
39
|
+
{{ config.deltaPrefix }}{{ formatValue(config.delta, 1, true)
|
|
40
|
+
}}{{ config.deltaSuffix }}
|
|
41
|
+
</span>
|
|
42
|
+
</div>
|
|
43
|
+
</slot>
|
|
15
44
|
</div>
|
|
16
|
-
|
|
17
|
-
v-if="config.delta"
|
|
18
|
-
class="flex items-center gap-0.5 text-xs font-medium"
|
|
19
|
-
:class="[
|
|
20
|
-
config.negativeIsBetter
|
|
21
|
-
? config.delta >= 0
|
|
22
|
-
? 'text-ink-red-4'
|
|
23
|
-
: 'text-ink-green-3'
|
|
24
|
-
: config.delta >= 0
|
|
25
|
-
? 'text-ink-green-3'
|
|
26
|
-
: 'text-ink-red-4',
|
|
27
|
-
]"
|
|
28
|
-
>
|
|
29
|
-
<span class="">
|
|
30
|
-
{{ config.delta >= 0 ? '↑' : '↓' }}
|
|
31
|
-
</span>
|
|
32
|
-
<span>
|
|
33
|
-
{{ config.deltaPrefix }}{{ formatValue(config.delta, 1, true)
|
|
34
|
-
}}{{ config.deltaSuffix }}
|
|
35
|
-
</span>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
45
|
+
</slot>
|
|
38
46
|
</div>
|
|
39
47
|
</template>
|
|
40
48
|
|
|
41
49
|
<script setup lang="ts">
|
|
42
|
-
import { ref } from 'vue'
|
|
43
50
|
import { formatValue } from './helpers'
|
|
44
51
|
import { NumberChartConfig } from './types'
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
const error = ref('')
|
|
53
|
+
defineProps<{ config: NumberChartConfig }>()
|
|
48
54
|
</script>
|
|
@@ -2,25 +2,78 @@
|
|
|
2
2
|
import { ref } from 'vue'
|
|
3
3
|
import Dialog from './Dialog.vue'
|
|
4
4
|
import { Button } from '../Button'
|
|
5
|
+
import { Dropdown } from '../Dropdown'
|
|
6
|
+
import LucideSettings from '~icons/lucide/settings'
|
|
7
|
+
import LucideStar from '~icons/lucide/star'
|
|
8
|
+
import LucideChevronDown from '~icons/lucide/chevron-down'
|
|
5
9
|
|
|
6
10
|
const dialog1 = ref(false)
|
|
7
11
|
const dialog2 = ref(false)
|
|
12
|
+
const dialog3 = ref(false)
|
|
13
|
+
const dialog4 = ref(false)
|
|
14
|
+
const dialog5 = ref(false)
|
|
15
|
+
const dialog6 = ref(false)
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
// Dropdown state
|
|
18
|
+
const selectedOption = ref('Option 1')
|
|
19
|
+
|
|
20
|
+
const dropdownOptions = [
|
|
21
|
+
{
|
|
22
|
+
label: 'Option 1',
|
|
23
|
+
onClick: () => {
|
|
24
|
+
selectedOption.value = 'Option 1'
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
label: 'Option 2',
|
|
29
|
+
onClick: () => {
|
|
30
|
+
selectedOption.value = 'Option 2'
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: 'Option 3',
|
|
35
|
+
onClick: () => {
|
|
36
|
+
selectedOption.value = 'Option 3'
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
group: 'Advanced Options',
|
|
41
|
+
items: [
|
|
42
|
+
{
|
|
43
|
+
label: 'Advanced Option A',
|
|
44
|
+
icon: LucideSettings,
|
|
45
|
+
onClick: () => {
|
|
46
|
+
selectedOption.value = 'Advanced Option A'
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
label: 'Advanced Option B',
|
|
51
|
+
icon: LucideStar,
|
|
52
|
+
onClick: () => {
|
|
53
|
+
selectedOption.value = 'Advanced Option B'
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
const createPromise = (): Promise<void> => {
|
|
10
61
|
return new Promise((resolve) => {
|
|
11
62
|
setTimeout(resolve, 2000)
|
|
12
63
|
})
|
|
13
64
|
}
|
|
14
65
|
</script>
|
|
66
|
+
|
|
15
67
|
<template>
|
|
16
68
|
<Story :layout="{ width: 500, type: 'grid' }">
|
|
17
|
-
|
|
18
|
-
|
|
69
|
+
<!-- 1. Basic Dialog with Actions -->
|
|
70
|
+
<Variant title="Basic Dialog with Actions" autoPropsDisabled>
|
|
71
|
+
<Button @click="dialog1 = true">Show Confirmation Dialog</Button>
|
|
19
72
|
<Dialog
|
|
20
73
|
:options="{
|
|
21
|
-
title: 'Confirm',
|
|
22
|
-
message: 'Are you sure you want to
|
|
23
|
-
size: '
|
|
74
|
+
title: 'Confirm Action',
|
|
75
|
+
message: 'Are you sure you want to proceed with this action?',
|
|
76
|
+
size: 'lg',
|
|
24
77
|
icon: {
|
|
25
78
|
name: 'alert-triangle',
|
|
26
79
|
appearance: 'warning',
|
|
@@ -29,27 +82,133 @@ const createPromise = () => {
|
|
|
29
82
|
{
|
|
30
83
|
label: 'Confirm',
|
|
31
84
|
variant: 'solid',
|
|
32
|
-
onClick: () =>
|
|
33
|
-
return createPromise()
|
|
34
|
-
},
|
|
85
|
+
onClick: () => createPromise(),
|
|
35
86
|
},
|
|
36
87
|
],
|
|
37
88
|
}"
|
|
38
89
|
v-model="dialog1"
|
|
39
90
|
/>
|
|
40
91
|
</Variant>
|
|
41
|
-
|
|
42
|
-
|
|
92
|
+
|
|
93
|
+
<!-- 2. Custom Content with Slots -->
|
|
94
|
+
<Variant title="Custom Content with Slots" autoPropsDisabled>
|
|
95
|
+
<Button @click="dialog2 = true">Show Custom Dialog</Button>
|
|
43
96
|
<Dialog v-model="dialog2">
|
|
44
97
|
<template #body-title>
|
|
45
|
-
<h3
|
|
98
|
+
<h3 class="text-2xl font-semibold text-blue-600">
|
|
99
|
+
Custom Title with Styling
|
|
100
|
+
</h3>
|
|
46
101
|
</template>
|
|
47
102
|
<template #body-content>
|
|
48
|
-
<
|
|
103
|
+
<div class="space-y-4">
|
|
104
|
+
<p class="text-gray-700">
|
|
105
|
+
This dialog uses custom slots for flexible content layout.
|
|
106
|
+
</p>
|
|
107
|
+
<div class="bg-blue-50 p-4 rounded-lg">
|
|
108
|
+
<p class="text-blue-800">
|
|
109
|
+
You can put any content here including forms, lists, or other
|
|
110
|
+
components.
|
|
111
|
+
</p>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</template>
|
|
115
|
+
<template #actions="{ close }">
|
|
116
|
+
<div class="flex justify-start flex-row-reverse gap-2">
|
|
117
|
+
<Button variant="solid" @click="close">Save Changes</Button>
|
|
118
|
+
<Button variant="outline" @click="close">Cancel</Button>
|
|
119
|
+
</div>
|
|
120
|
+
</template>
|
|
121
|
+
</Dialog>
|
|
122
|
+
</Variant>
|
|
123
|
+
|
|
124
|
+
<!-- 3. Different Sizes -->
|
|
125
|
+
<Variant title="Different Sizes" autoPropsDisabled>
|
|
126
|
+
<div class="space-x-2">
|
|
127
|
+
<Button @click="dialog3 = true">Small Dialog</Button>
|
|
128
|
+
<Button @click="dialog4 = true">Large Dialog</Button>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<!-- Small Dialog -->
|
|
132
|
+
<Dialog
|
|
133
|
+
:options="{
|
|
134
|
+
title: 'Small Dialog',
|
|
135
|
+
message: 'This is a small dialog.',
|
|
136
|
+
size: 'sm',
|
|
137
|
+
actions: [{ label: 'OK', variant: 'solid' }],
|
|
138
|
+
}"
|
|
139
|
+
v-model="dialog3"
|
|
140
|
+
/>
|
|
141
|
+
|
|
142
|
+
<!-- Large Dialog -->
|
|
143
|
+
<Dialog
|
|
144
|
+
:options="{
|
|
145
|
+
title: 'Large Dialog',
|
|
146
|
+
message: 'This is a large dialog with more space for content.',
|
|
147
|
+
size: '4xl',
|
|
148
|
+
actions: [{ label: 'OK', variant: 'solid' }],
|
|
149
|
+
}"
|
|
150
|
+
v-model="dialog4"
|
|
151
|
+
/>
|
|
152
|
+
</Variant>
|
|
153
|
+
|
|
154
|
+
<!-- 4. Disable Outside Click -->
|
|
155
|
+
<Variant title="Disable Outside Click to Close" autoPropsDisabled>
|
|
156
|
+
<Button @click="dialog5 = true">Show Modal Dialog</Button>
|
|
157
|
+
<Dialog
|
|
158
|
+
:options="{
|
|
159
|
+
title: 'Modal Dialog',
|
|
160
|
+
message:
|
|
161
|
+
'This dialog cannot be closed by clicking outside. Use the buttons or ESC key.',
|
|
162
|
+
actions: [{ label: 'Close', variant: 'solid' }],
|
|
163
|
+
}"
|
|
164
|
+
:disable-outside-click-to-close="true"
|
|
165
|
+
v-model="dialog5"
|
|
166
|
+
/>
|
|
167
|
+
</Variant>
|
|
168
|
+
|
|
169
|
+
<!-- 5. Dialog with Interactive Components -->
|
|
170
|
+
<Variant title="Dialog with Interactive Components" autoPropsDisabled>
|
|
171
|
+
<Button @click="dialog6 = true">Show Settings Dialog</Button>
|
|
172
|
+
<Dialog v-model="dialog6">
|
|
173
|
+
<template #body-title>
|
|
174
|
+
<h3 class="text-2xl font-semibold text-ink-gray-9">
|
|
175
|
+
Settings Dialog
|
|
176
|
+
</h3>
|
|
177
|
+
</template>
|
|
178
|
+
<template #body-content>
|
|
179
|
+
<div class="space-y-6 text-base">
|
|
180
|
+
<p class="text-gray-700">
|
|
181
|
+
This dialog contains interactive elements to test proper layering.
|
|
182
|
+
</p>
|
|
183
|
+
|
|
184
|
+
<div class="space-y-3">
|
|
185
|
+
<label class="block text-sm font-medium text-gray-700">
|
|
186
|
+
Select an option:
|
|
187
|
+
</label>
|
|
188
|
+
<Dropdown :options="dropdownOptions" placement="left">
|
|
189
|
+
<Button variant="outline">
|
|
190
|
+
{{ selectedOption }}
|
|
191
|
+
|
|
192
|
+
<template #suffix>
|
|
193
|
+
<LucideChevronDown class="h-4 w-4 text-gray-500" />
|
|
194
|
+
</template>
|
|
195
|
+
</Button>
|
|
196
|
+
</Dropdown>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div class="bg-gray-50 text-p-sm p-4 text-ink-gray-6 rounded-lg">
|
|
200
|
+
<p><strong>Selected value:</strong> {{ selectedOption }}</p>
|
|
201
|
+
<p class="mt-1">
|
|
202
|
+
Interactive components should work properly within dialogs.
|
|
203
|
+
</p>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
49
206
|
</template>
|
|
50
|
-
<template #actions>
|
|
51
|
-
<
|
|
52
|
-
|
|
207
|
+
<template #actions="{ close }">
|
|
208
|
+
<div class="flex space-x-2">
|
|
209
|
+
<Button variant="solid" @click="close">Save Settings</Button>
|
|
210
|
+
<Button variant="outline" @click="close">Cancel</Button>
|
|
211
|
+
</div>
|
|
53
212
|
</template>
|
|
54
213
|
</Dialog>
|
|
55
214
|
</Variant>
|
|
@@ -1,44 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
as="div"
|
|
9
|
-
class="fixed inset-0 z-10 overflow-y-auto"
|
|
10
|
-
@close="!disableOutsideClickToClose && close()"
|
|
11
|
-
>
|
|
12
|
-
<div
|
|
13
|
-
class="flex min-h-screen flex-col items-center px-4 py-4 text-center"
|
|
14
|
-
:class="dialogPositionClasses"
|
|
2
|
+
<DialogRoot v-model:open="isOpen" @update:open="handleOpenChange">
|
|
3
|
+
<DialogPortal>
|
|
4
|
+
<DialogOverlay
|
|
5
|
+
class="fixed inset-0 bg-black-overlay-200 backdrop-filter backdrop-blur-[12px] overflow-y-auto dialog-overlay"
|
|
6
|
+
:data-dialog="options.title"
|
|
7
|
+
@after-leave="$emit('after-leave')"
|
|
15
8
|
>
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
enter-from="opacity-0"
|
|
20
|
-
enter-to="opacity-100"
|
|
21
|
-
leave="ease-in duration-150"
|
|
22
|
-
leave-from="opacity-100"
|
|
23
|
-
leave-to="opacity-0"
|
|
9
|
+
<div
|
|
10
|
+
class="flex min-h-screen flex-col items-center px-4 py-4 text-center"
|
|
11
|
+
:class="dialogPositionClasses"
|
|
24
12
|
>
|
|
25
|
-
<
|
|
26
|
-
class="
|
|
27
|
-
:data-dialog="options.title"
|
|
28
|
-
/>
|
|
29
|
-
</TransitionChild>
|
|
30
|
-
|
|
31
|
-
<TransitionChild
|
|
32
|
-
as="template"
|
|
33
|
-
enter="ease-out duration-150"
|
|
34
|
-
enter-from="opacity-50 translate-y-2 scale-95"
|
|
35
|
-
enter-to="opacity-100 translate-y-0 scale-100"
|
|
36
|
-
leave="ease-in duration-150"
|
|
37
|
-
leave-from="opacity-100 translate-y-0 scale-100"
|
|
38
|
-
leave-to="opacity-50 translate-y-4 translate-y-4 scale-95"
|
|
39
|
-
>
|
|
40
|
-
<DialogPanel
|
|
41
|
-
class="my-8 inline-block w-full transform overflow-hidden rounded-xl bg-surface-modal text-left align-middle shadow-xl transition-all"
|
|
13
|
+
<DialogContent
|
|
14
|
+
class="my-8 inline-block w-full transform overflow-hidden rounded-xl bg-surface-modal text-left align-middle shadow-xl dialog-content"
|
|
42
15
|
:class="{
|
|
43
16
|
'max-w-7xl': options.size === '7xl',
|
|
44
17
|
'max-w-6xl': options.size === '6xl',
|
|
@@ -52,6 +25,14 @@
|
|
|
52
25
|
'max-w-sm': options.size === 'sm',
|
|
53
26
|
'max-w-xs': options.size === 'xs',
|
|
54
27
|
}"
|
|
28
|
+
@escape-key-down="close()"
|
|
29
|
+
@interact-outside="
|
|
30
|
+
(e: Event) => {
|
|
31
|
+
if (props.disableOutsideClickToClose) {
|
|
32
|
+
e.preventDefault()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
"
|
|
55
36
|
>
|
|
56
37
|
<slot name="body">
|
|
57
38
|
<slot name="body-main">
|
|
@@ -83,35 +64,22 @@
|
|
|
83
64
|
</slot>
|
|
84
65
|
</DialogTitle>
|
|
85
66
|
</div>
|
|
86
|
-
<
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
94
|
-
class="text-ink-gray-9"
|
|
95
|
-
>
|
|
96
|
-
<path
|
|
97
|
-
fill-rule="evenodd"
|
|
98
|
-
clip-rule="evenodd"
|
|
99
|
-
d="M12.8567 3.85355C13.052 3.65829 13.052 3.34171 12.8567 3.14645C12.6615 2.95118 12.3449 2.95118 12.1496 3.14645L8.00201 7.29405L3.85441 3.14645C3.65914 2.95118 3.34256 2.95118 3.1473 3.14645C2.95204 3.34171 2.95204 3.65829 3.1473 3.85355L7.29491 8.00116L3.14645 12.1496C2.95118 12.3449 2.95118 12.6615 3.14645 12.8567C3.34171 13.052 3.65829 13.052 3.85355 12.8567L8.00201 8.70827L12.1505 12.8567C12.3457 13.052 12.6623 13.052 12.8576 12.8567C13.0528 12.6615 13.0528 12.3449 12.8576 12.1496L8.70912 8.00116L12.8567 3.85355Z"
|
|
100
|
-
fill="currentColor"
|
|
101
|
-
/>
|
|
102
|
-
</svg>
|
|
103
|
-
</template>
|
|
104
|
-
</Button>
|
|
67
|
+
<DialogClose as-child>
|
|
68
|
+
<Button variant="ghost" @click="close">
|
|
69
|
+
<template #icon>
|
|
70
|
+
<LucideX class="h-4 w-4 text-ink-gray-9" />
|
|
71
|
+
</template>
|
|
72
|
+
</Button>
|
|
73
|
+
</DialogClose>
|
|
105
74
|
</div>
|
|
106
75
|
</slot>
|
|
107
76
|
|
|
108
77
|
<slot name="body-content">
|
|
109
|
-
<
|
|
110
|
-
class="text-p-base text-ink-gray-7"
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
</p>
|
|
78
|
+
<DialogDescription as-child v-if="options.message">
|
|
79
|
+
<p class="text-p-base text-ink-gray-7">
|
|
80
|
+
{{ options.message }}
|
|
81
|
+
</p>
|
|
82
|
+
</DialogDescription>
|
|
115
83
|
</slot>
|
|
116
84
|
</div>
|
|
117
85
|
</div>
|
|
@@ -136,25 +104,37 @@
|
|
|
136
104
|
</slot>
|
|
137
105
|
</div>
|
|
138
106
|
</slot>
|
|
139
|
-
</
|
|
140
|
-
</
|
|
141
|
-
</
|
|
142
|
-
</
|
|
143
|
-
</
|
|
107
|
+
</DialogContent>
|
|
108
|
+
</div>
|
|
109
|
+
</DialogOverlay>
|
|
110
|
+
</DialogPortal>
|
|
111
|
+
</DialogRoot>
|
|
144
112
|
</template>
|
|
145
113
|
|
|
146
114
|
<script setup lang="ts">
|
|
147
115
|
import {
|
|
148
|
-
|
|
116
|
+
DialogRoot,
|
|
117
|
+
DialogPortal,
|
|
118
|
+
DialogOverlay,
|
|
119
|
+
DialogContent,
|
|
149
120
|
DialogTitle,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
} from '@headlessui/vue'
|
|
121
|
+
DialogDescription,
|
|
122
|
+
DialogClose,
|
|
123
|
+
} from 'reka-ui'
|
|
154
124
|
import { computed, reactive } from 'vue'
|
|
155
125
|
import { Button } from '../Button'
|
|
156
126
|
import FeatherIcon from '../FeatherIcon.vue'
|
|
157
|
-
import type {
|
|
127
|
+
import type {
|
|
128
|
+
DialogProps,
|
|
129
|
+
DialogIcon,
|
|
130
|
+
DialogAction,
|
|
131
|
+
DialogActionContext,
|
|
132
|
+
} from './types'
|
|
133
|
+
|
|
134
|
+
// Type for dialog action with reactive loading state
|
|
135
|
+
type ReactiveDialogAction = DialogAction & {
|
|
136
|
+
loading: boolean
|
|
137
|
+
}
|
|
158
138
|
|
|
159
139
|
const props = withDefaults(defineProps<DialogProps>(), {
|
|
160
140
|
options: () => ({}),
|
|
@@ -167,7 +147,7 @@ const emit = defineEmits<{
|
|
|
167
147
|
(event: 'after-leave'): void
|
|
168
148
|
}>()
|
|
169
149
|
|
|
170
|
-
const actions = computed(() => {
|
|
150
|
+
const actions = computed((): ReactiveDialogAction[] => {
|
|
171
151
|
let actions = props.options.actions
|
|
172
152
|
if (!actions?.length) return []
|
|
173
153
|
|
|
@@ -183,12 +163,15 @@ const actions = computed(() => {
|
|
|
183
163
|
if (action.onClick) {
|
|
184
164
|
// deprecated: uncomment this when we remove the backwards compatibility
|
|
185
165
|
// let context: DialogActionContext = { close }
|
|
186
|
-
|
|
166
|
+
type BackwardsCompatibleDialogActionContext = (() => void) &
|
|
167
|
+
DialogActionContext
|
|
168
|
+
|
|
169
|
+
let backwardsCompatibleContext = (() => {
|
|
187
170
|
console.warn(
|
|
188
|
-
'Value passed to onClick is a context object. Please use context.close() instead of context() to close the dialog.'
|
|
171
|
+
'Value passed to onClick is a context object. Please use context.close() instead of context() to close the dialog.',
|
|
189
172
|
)
|
|
190
173
|
close()
|
|
191
|
-
}
|
|
174
|
+
}) as BackwardsCompatibleDialogActionContext
|
|
192
175
|
backwardsCompatibleContext.close = close
|
|
193
176
|
await action.onClick(backwardsCompatibleContext)
|
|
194
177
|
}
|
|
@@ -205,7 +188,7 @@ const isOpen = computed({
|
|
|
205
188
|
get() {
|
|
206
189
|
return props.modelValue
|
|
207
190
|
},
|
|
208
|
-
set(val) {
|
|
191
|
+
set(val: boolean) {
|
|
209
192
|
emit('update:modelValue', val)
|
|
210
193
|
if (!val) {
|
|
211
194
|
emit('close')
|
|
@@ -213,6 +196,10 @@ const isOpen = computed({
|
|
|
213
196
|
},
|
|
214
197
|
})
|
|
215
198
|
|
|
199
|
+
function handleOpenChange(open: boolean) {
|
|
200
|
+
isOpen.value = open
|
|
201
|
+
}
|
|
202
|
+
|
|
216
203
|
function close() {
|
|
217
204
|
isOpen.value = false
|
|
218
205
|
}
|
|
@@ -229,31 +216,92 @@ const icon = computed(() => {
|
|
|
229
216
|
|
|
230
217
|
const dialogPositionClasses = computed(() => {
|
|
231
218
|
const position = props.options?.position || 'center'
|
|
232
|
-
|
|
219
|
+
const classMap: Record<string, string> = {
|
|
233
220
|
center: 'justify-center',
|
|
234
221
|
top: 'pt-[20vh]',
|
|
235
|
-
}
|
|
222
|
+
}
|
|
223
|
+
return classMap[position]
|
|
236
224
|
})
|
|
237
225
|
|
|
238
226
|
const dialogIconBgClasses = computed(() => {
|
|
239
227
|
const appearance = icon.value?.appearance
|
|
240
228
|
if (!appearance) return 'bg-surface-gray-2'
|
|
241
|
-
|
|
229
|
+
const classMap: Record<string, string> = {
|
|
242
230
|
warning: 'bg-surface-amber-2',
|
|
243
231
|
info: 'bg-surface-blue-2',
|
|
244
232
|
danger: 'bg-surface-red-2',
|
|
245
233
|
success: 'bg-surface-green-2',
|
|
246
|
-
}
|
|
234
|
+
}
|
|
235
|
+
return classMap[appearance]
|
|
247
236
|
})
|
|
248
237
|
|
|
249
238
|
const dialogIconClasses = computed(() => {
|
|
250
239
|
const appearance = icon.value?.appearance
|
|
251
240
|
if (!appearance) return 'text-ink-gray-5'
|
|
252
|
-
|
|
241
|
+
const classMap: Record<string, string> = {
|
|
253
242
|
warning: 'text-ink-amber-3',
|
|
254
243
|
info: 'text-ink-blue-3',
|
|
255
244
|
danger: 'text-ink-red-4',
|
|
256
245
|
success: 'text-ink-green-3',
|
|
257
|
-
}
|
|
246
|
+
}
|
|
247
|
+
return classMap[appearance]
|
|
258
248
|
})
|
|
259
249
|
</script>
|
|
250
|
+
|
|
251
|
+
<style scoped>
|
|
252
|
+
@keyframes dialog-overlay-in {
|
|
253
|
+
from {
|
|
254
|
+
opacity: 0;
|
|
255
|
+
}
|
|
256
|
+
to {
|
|
257
|
+
opacity: 1;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
@keyframes dialog-overlay-out {
|
|
262
|
+
from {
|
|
263
|
+
opacity: 1;
|
|
264
|
+
}
|
|
265
|
+
to {
|
|
266
|
+
opacity: 0;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@keyframes dialog-content-in {
|
|
271
|
+
from {
|
|
272
|
+
opacity: 0.5;
|
|
273
|
+
transform: scale(0.98);
|
|
274
|
+
}
|
|
275
|
+
to {
|
|
276
|
+
opacity: 1;
|
|
277
|
+
transform: scale(1);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
@keyframes dialog-content-out {
|
|
282
|
+
from {
|
|
283
|
+
opacity: 1;
|
|
284
|
+
transform: scale(1);
|
|
285
|
+
}
|
|
286
|
+
to {
|
|
287
|
+
opacity: 0.5;
|
|
288
|
+
transform: scale(0.98);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
:global(.dialog-overlay[data-state='open']) {
|
|
293
|
+
animation: dialog-overlay-in 100ms ease-out;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
:global(.dialog-overlay[data-state='closed']) {
|
|
297
|
+
animation: dialog-overlay-out 150ms ease-in;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
:global(.dialog-content[data-state='open']) {
|
|
301
|
+
animation: dialog-content-in 100ms ease-out;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
:global(.dialog-content[data-state='closed']) {
|
|
305
|
+
animation: dialog-content-out 150ms ease-in;
|
|
306
|
+
}
|
|
307
|
+
</style>
|
|
@@ -1,2 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import DialogMain from './Dialog.vue'
|
|
2
|
+
import { DialogTitle, DialogDescription } from 'reka-ui'
|
|
3
|
+
export type { DialogProps } from './types'
|
|
4
|
+
|
|
5
|
+
type DialogExport = typeof DialogMain & {
|
|
6
|
+
Title: typeof DialogTitle
|
|
7
|
+
Description: typeof DialogDescription
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const Dialog = DialogMain as DialogExport
|
|
11
|
+
Dialog.Title = DialogTitle
|
|
12
|
+
Dialog.Description = DialogDescription
|
|
13
|
+
|
|
14
|
+
export { Dialog }
|
|
@@ -27,10 +27,10 @@ type DialogOptions = {
|
|
|
27
27
|
position?: 'top' | 'center'
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
type DialogActionContext = {
|
|
30
|
+
export type DialogActionContext = {
|
|
31
31
|
close: () => void
|
|
32
32
|
}
|
|
33
|
-
type DialogAction = ButtonProps & {
|
|
33
|
+
export type DialogAction = ButtonProps & {
|
|
34
34
|
onClick?: (context: DialogActionContext) => void | Promise<void>
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="inline-flex items-center gap-0.5 text-sm"
|
|
4
|
+
:class="{
|
|
5
|
+
'bg-surface-gray-2 rounded-sm text-ink-gray-5 py-0.5 px-1': bg,
|
|
6
|
+
'text-ink-gray-4': !bg,
|
|
7
|
+
}"
|
|
8
|
+
>
|
|
9
|
+
<span v-if="ctrl || meta">
|
|
10
|
+
<LucideCommand v-if="isMac" class="w-3 h-3" />
|
|
11
|
+
<span v-else>Ctrl</span>
|
|
12
|
+
</span>
|
|
13
|
+
<span v-if="shift"><LucideShift class="w-3 h-3" /></span>
|
|
14
|
+
<span v-if="alt"><LucideAlt class="w-3 h-3" /></span>
|
|
15
|
+
<slot></slot>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import LucideCommand from '~icons/lucide/command'
|
|
20
|
+
import LucideShift from '~icons/lucide/arrow-big-up'
|
|
21
|
+
import LucideAlt from '~icons/lucide/option'
|
|
22
|
+
|
|
23
|
+
const isMac = navigator.userAgent.includes('Mac')
|
|
24
|
+
|
|
25
|
+
defineProps({
|
|
26
|
+
meta: Boolean,
|
|
27
|
+
ctrl: Boolean,
|
|
28
|
+
shift: Boolean,
|
|
29
|
+
alt: Boolean,
|
|
30
|
+
shortcut: String,
|
|
31
|
+
bg: Boolean,
|
|
32
|
+
})
|
|
33
|
+
</script>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Password } from './index'
|
|
3
|
+
import { ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
const value = ref('')
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<Story title="Password" :layout="{ width: 500, type: 'grid' }">
|
|
10
|
+
<div class="p-2">
|
|
11
|
+
<Password v-model="value" />
|
|
12
|
+
</div>
|
|
13
|
+
</Story>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FormControl
|
|
3
|
+
:type="show ? 'text' : 'password'"
|
|
4
|
+
:value="modelValue || value"
|
|
5
|
+
v-bind="$attrs"
|
|
6
|
+
@keydown.meta.i.prevent="show = !show"
|
|
7
|
+
@keydown.ctrl.i.prevent="show = !show"
|
|
8
|
+
>
|
|
9
|
+
<template #prefix v-if="$slots.prefix">
|
|
10
|
+
<slot name="prefix" />
|
|
11
|
+
</template>
|
|
12
|
+
<template #suffix>
|
|
13
|
+
<Tooltip>
|
|
14
|
+
<template #body>
|
|
15
|
+
<div
|
|
16
|
+
class="rounded bg-surface-gray-7 py-1.5 px-2 text-xs text-ink-white shadow-xl"
|
|
17
|
+
>
|
|
18
|
+
<span class="flex items-center gap-1">
|
|
19
|
+
{{ show ? 'Hide Password' : 'Show Password' }}
|
|
20
|
+
<KeyboardShortcut
|
|
21
|
+
bg
|
|
22
|
+
ctrl
|
|
23
|
+
class="!bg-surface-gray-5 !text-ink-gray-2 px-1"
|
|
24
|
+
>
|
|
25
|
+
<span class="font-mono leading-none tracking-widest">+I</span>
|
|
26
|
+
</KeyboardShortcut>
|
|
27
|
+
</span>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
<div>
|
|
31
|
+
<component
|
|
32
|
+
v-show="showEye"
|
|
33
|
+
:is="show ? LucideEyeOff : LucideEye"
|
|
34
|
+
class="h-3 cursor-pointer mr-1"
|
|
35
|
+
@click="show = !show"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
</Tooltip>
|
|
39
|
+
</template>
|
|
40
|
+
</FormControl>
|
|
41
|
+
</template>
|
|
42
|
+
<script setup lang="ts">
|
|
43
|
+
import LucideEye from '~icons/lucide/eye'
|
|
44
|
+
import LucideEyeOff from '~icons/lucide/eye-off'
|
|
45
|
+
import KeyboardShortcut from '../KeyboardShortcut.vue'
|
|
46
|
+
import FormControl from '../FormControl/FormControl.vue'
|
|
47
|
+
import Tooltip from '../Tooltip/Tooltip.vue'
|
|
48
|
+
import type { PasswordProps } from './types'
|
|
49
|
+
import { ref, computed } from 'vue'
|
|
50
|
+
|
|
51
|
+
const props = defineProps<PasswordProps>()
|
|
52
|
+
|
|
53
|
+
const show = ref(false)
|
|
54
|
+
const showEye = computed(() => {
|
|
55
|
+
let v = props.modelValue || props.value
|
|
56
|
+
return !v?.includes('*')
|
|
57
|
+
})
|
|
58
|
+
</script>
|
package/src/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ export * from './components/Popover'
|
|
|
31
31
|
export * from './components/Rating'
|
|
32
32
|
export { default as Resource } from './components/Resource.vue'
|
|
33
33
|
export * from './components/Select'
|
|
34
|
+
export * from './components/Password'
|
|
34
35
|
export * from './components/Spinner'
|
|
35
36
|
export * from './components/Switch'
|
|
36
37
|
export * from './components/TabButtons'
|
|
@@ -67,6 +68,7 @@ export { default as CommandPaletteItem } from './components/CommandPalette/Comma
|
|
|
67
68
|
export { default as ListFilter } from './components/ListFilter/ListFilter.vue'
|
|
68
69
|
export { default as Calendar } from './components/Calendar/Calendar.vue'
|
|
69
70
|
export { default as NestedPopover } from './components/ListFilter/NestedPopover.vue'
|
|
71
|
+
export { default as KeyboardShortcut } from './components/KeyboardShortcut.vue'
|
|
70
72
|
export * from './components/CircularProgressBar'
|
|
71
73
|
export * from './components/Tree'
|
|
72
74
|
export { default as FrappeUIProvider } from './components/Provider/FrappeUIProvider.vue'
|