@soave/ui 0.1.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/dist/build.config.d.ts +2 -0
- package/dist/build.config.mjs +14 -0
- package/dist/components/ui/Alert.vue +39 -0
- package/dist/components/ui/AlertDescription.vue +12 -0
- package/dist/components/ui/AlertTitle.vue +12 -0
- package/dist/components/ui/Button.vue +59 -0
- package/dist/components/ui/Card.vue +15 -0
- package/dist/components/ui/CardContent.vue +12 -0
- package/dist/components/ui/CardDescription.vue +12 -0
- package/dist/components/ui/CardFooter.vue +12 -0
- package/dist/components/ui/CardHeader.vue +12 -0
- package/dist/components/ui/CardTitle.vue +12 -0
- package/dist/components/ui/Checkbox.vue +73 -0
- package/dist/components/ui/Dialog.vue +93 -0
- package/dist/components/ui/DialogDescription.vue +12 -0
- package/dist/components/ui/DialogFooter.vue +12 -0
- package/dist/components/ui/DialogHeader.vue +12 -0
- package/dist/components/ui/DialogTitle.vue +12 -0
- package/dist/components/ui/DropdownMenu.vue +33 -0
- package/dist/components/ui/DropdownMenuContent.vue +66 -0
- package/dist/components/ui/DropdownMenuItem.vue +77 -0
- package/dist/components/ui/DropdownMenuLabel.vue +20 -0
- package/dist/components/ui/DropdownMenuSeparator.vue +16 -0
- package/dist/components/ui/DropdownMenuTrigger.vue +38 -0
- package/dist/components/ui/FileInput.vue +153 -0
- package/dist/components/ui/FormError.vue +20 -0
- package/dist/components/ui/FormField.vue +12 -0
- package/dist/components/ui/FormInput.vue +46 -0
- package/dist/components/ui/FormLabel.vue +19 -0
- package/dist/components/ui/FormTextarea.vue +39 -0
- package/dist/components/ui/Input.vue +49 -0
- package/dist/components/ui/Popover.vue +36 -0
- package/dist/components/ui/PopoverContent.vue +62 -0
- package/dist/components/ui/PopoverTrigger.vue +36 -0
- package/dist/components/ui/RadioGroup.vue +42 -0
- package/dist/components/ui/RadioItem.vue +41 -0
- package/dist/components/ui/Select.vue +55 -0
- package/dist/components/ui/SelectContent.vue +29 -0
- package/dist/components/ui/SelectItem.vue +51 -0
- package/dist/components/ui/SelectTrigger.vue +38 -0
- package/dist/components/ui/SelectValue.vue +16 -0
- package/dist/components/ui/Sheet.vue +140 -0
- package/dist/components/ui/SheetDescription.vue +15 -0
- package/dist/components/ui/SheetFooter.vue +15 -0
- package/dist/components/ui/SheetHeader.vue +15 -0
- package/dist/components/ui/SheetTitle.vue +15 -0
- package/dist/components/ui/Switch.vue +43 -0
- package/dist/components/ui/Textarea.vue +50 -0
- package/dist/components/ui/Toast.vue +107 -0
- package/dist/components/ui/Toaster.vue +80 -0
- package/dist/components/ui/Tooltip.vue +42 -0
- package/dist/components/ui/TooltipContent.vue +68 -0
- package/dist/components/ui/TooltipTrigger.vue +39 -0
- package/dist/components/ui/UIProvider.vue +19 -0
- package/dist/components/ui/index.d.ts +52 -0
- package/dist/components/ui/index.mjs +52 -0
- package/dist/composables/index.d.ts +17 -0
- package/dist/composables/index.mjs +17 -0
- package/dist/composables/useButton.d.ts +8 -0
- package/dist/composables/useButton.mjs +49 -0
- package/dist/composables/useCard.d.ts +8 -0
- package/dist/composables/useCard.mjs +24 -0
- package/dist/composables/useCheckbox.d.ts +7 -0
- package/dist/composables/useCheckbox.mjs +51 -0
- package/dist/composables/useDialog.d.ts +6 -0
- package/dist/composables/useDialog.mjs +19 -0
- package/dist/composables/useDropdown.d.ts +24 -0
- package/dist/composables/useDropdown.mjs +170 -0
- package/dist/composables/useFileInput.d.ts +6 -0
- package/dist/composables/useFileInput.mjs +152 -0
- package/dist/composables/useForm.d.ts +7 -0
- package/dist/composables/useForm.mjs +159 -0
- package/dist/composables/useInput.d.ts +8 -0
- package/dist/composables/useInput.mjs +52 -0
- package/dist/composables/usePopover.d.ts +20 -0
- package/dist/composables/usePopover.mjs +113 -0
- package/dist/composables/useRadio.d.ts +7 -0
- package/dist/composables/useRadio.mjs +55 -0
- package/dist/composables/useSelect.d.ts +17 -0
- package/dist/composables/useSelect.mjs +71 -0
- package/dist/composables/useSwitch.d.ts +7 -0
- package/dist/composables/useSwitch.mjs +50 -0
- package/dist/composables/useTextarea.d.ts +7 -0
- package/dist/composables/useTextarea.mjs +50 -0
- package/dist/composables/useTheme.d.ts +15 -0
- package/dist/composables/useTheme.mjs +89 -0
- package/dist/composables/useToast.d.ts +11 -0
- package/dist/composables/useToast.mjs +64 -0
- package/dist/composables/useTooltip.d.ts +23 -0
- package/dist/composables/useTooltip.mjs +125 -0
- package/dist/composables/useUIConfig.d.ts +28 -0
- package/dist/composables/useUIConfig.mjs +36 -0
- package/dist/constants/errors.d.ts +22 -0
- package/dist/constants/errors.mjs +18 -0
- package/dist/constants/index.d.ts +2 -0
- package/dist/constants/index.mjs +2 -0
- package/dist/constants/logs.d.ts +17 -0
- package/dist/constants/logs.mjs +17 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +5 -0
- package/dist/types/alert.d.ts +15 -0
- package/dist/types/alert.mjs +0 -0
- package/dist/types/button.d.ts +20 -0
- package/dist/types/button.mjs +0 -0
- package/dist/types/card.d.ts +23 -0
- package/dist/types/card.mjs +0 -0
- package/dist/types/checkbox.d.ts +19 -0
- package/dist/types/checkbox.mjs +0 -0
- package/dist/types/config.d.ts +30 -0
- package/dist/types/config.mjs +15 -0
- package/dist/types/dialog.d.ts +29 -0
- package/dist/types/dialog.mjs +0 -0
- package/dist/types/dropdown.d.ts +27 -0
- package/dist/types/dropdown.mjs +0 -0
- package/dist/types/file-input.d.ts +35 -0
- package/dist/types/file-input.mjs +0 -0
- package/dist/types/form.d.ts +70 -0
- package/dist/types/form.mjs +0 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/types/index.mjs +20 -0
- package/dist/types/input.d.ts +27 -0
- package/dist/types/input.mjs +0 -0
- package/dist/types/popover.d.ts +15 -0
- package/dist/types/popover.mjs +0 -0
- package/dist/types/radio.d.ts +29 -0
- package/dist/types/radio.mjs +1 -0
- package/dist/types/select.d.ts +36 -0
- package/dist/types/select.mjs +1 -0
- package/dist/types/sheet.d.ts +11 -0
- package/dist/types/sheet.mjs +0 -0
- package/dist/types/switch.d.ts +17 -0
- package/dist/types/switch.mjs +0 -0
- package/dist/types/textarea.d.ts +25 -0
- package/dist/types/textarea.mjs +0 -0
- package/dist/types/theme.d.ts +43 -0
- package/dist/types/theme.mjs +42 -0
- package/dist/types/toast.d.ts +38 -0
- package/dist/types/toast.mjs +0 -0
- package/dist/types/tooltip.d.ts +25 -0
- package/dist/types/tooltip.mjs +0 -0
- package/dist/types/utils.d.ts +12 -0
- package/dist/types/utils.mjs +0 -0
- package/dist/utils/cn.d.ts +6 -0
- package/dist/utils/cn.mjs +5 -0
- package/dist/utils/deepMerge.d.ts +6 -0
- package/dist/utils/deepMerge.mjs +18 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.mjs +2 -0
- package/package.json +53 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="composable.base_classes.value"
|
|
4
|
+
:data-disabled="composable.is_disabled.value ? '' : undefined"
|
|
5
|
+
role="option"
|
|
6
|
+
:aria-selected="composable.is_selected.value"
|
|
7
|
+
@click="handleClick"
|
|
8
|
+
>
|
|
9
|
+
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
10
|
+
<svg
|
|
11
|
+
v-if="composable.is_selected.value"
|
|
12
|
+
class="h-4 w-4"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
viewBox="0 0 24 24"
|
|
15
|
+
fill="none"
|
|
16
|
+
stroke="currentColor"
|
|
17
|
+
stroke-width="2"
|
|
18
|
+
stroke-linecap="round"
|
|
19
|
+
stroke-linejoin="round"
|
|
20
|
+
>
|
|
21
|
+
<polyline points="20 6 9 17 4 12" />
|
|
22
|
+
</svg>
|
|
23
|
+
</span>
|
|
24
|
+
<slot />
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
import { inject, toRef } from "vue"
|
|
30
|
+
import { useSelectItem } from "../../composables/useSelect"
|
|
31
|
+
import type { SelectContext } from "../../types/select"
|
|
32
|
+
import { SELECT_KEY } from "../../types/select"
|
|
33
|
+
|
|
34
|
+
interface Props {
|
|
35
|
+
value: string
|
|
36
|
+
disabled?: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
40
|
+
disabled: false
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const context = inject<SelectContext>(SELECT_KEY)
|
|
44
|
+
const composable = useSelectItem(toRef(() => props))
|
|
45
|
+
|
|
46
|
+
const handleClick = () => {
|
|
47
|
+
if (!composable.is_disabled.value && context) {
|
|
48
|
+
context.updateValue(props.value)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
:class="composable.base_classes.value"
|
|
5
|
+
:disabled="composable.is_disabled.value"
|
|
6
|
+
:aria-expanded="context?.is_open.value"
|
|
7
|
+
aria-haspopup="listbox"
|
|
8
|
+
@click="handleClick"
|
|
9
|
+
>
|
|
10
|
+
<slot />
|
|
11
|
+
<svg
|
|
12
|
+
class="h-4 w-4 opacity-50"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
viewBox="0 0 24 24"
|
|
15
|
+
fill="none"
|
|
16
|
+
stroke="currentColor"
|
|
17
|
+
stroke-width="2"
|
|
18
|
+
stroke-linecap="round"
|
|
19
|
+
stroke-linejoin="round"
|
|
20
|
+
>
|
|
21
|
+
<path d="m6 9 6 6 6-6" />
|
|
22
|
+
</svg>
|
|
23
|
+
</button>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
import { inject } from "vue"
|
|
28
|
+
import { useSelectTrigger } from "../../composables/useSelect"
|
|
29
|
+
import type { SelectContext } from "../../types/select"
|
|
30
|
+
import { SELECT_KEY } from "../../types/select"
|
|
31
|
+
|
|
32
|
+
const context = inject<SelectContext>(SELECT_KEY)
|
|
33
|
+
const composable = useSelectTrigger()
|
|
34
|
+
|
|
35
|
+
const handleClick = () => {
|
|
36
|
+
context?.toggle()
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span :class="!hasValue && 'text-muted-foreground'">
|
|
3
|
+
<slot v-if="hasValue" />
|
|
4
|
+
<template v-else>{{ context?.placeholder.value }}</template>
|
|
5
|
+
</span>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script setup lang="ts">
|
|
9
|
+
import { computed, inject } from "vue"
|
|
10
|
+
import type { SelectContext } from "../../types/select"
|
|
11
|
+
import { SELECT_KEY } from "../../types/select"
|
|
12
|
+
|
|
13
|
+
const context = inject<SelectContext>(SELECT_KEY)
|
|
14
|
+
|
|
15
|
+
const hasValue = computed(() => !!context?.model_value.value)
|
|
16
|
+
</script>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<Transition name="sheet-overlay">
|
|
4
|
+
<div
|
|
5
|
+
v-if="is_open"
|
|
6
|
+
class="fixed inset-0 z-50 bg-black/80"
|
|
7
|
+
@click="handleOverlayClick"
|
|
8
|
+
/>
|
|
9
|
+
</Transition>
|
|
10
|
+
|
|
11
|
+
<Transition :name="transition_name">
|
|
12
|
+
<div
|
|
13
|
+
v-if="is_open"
|
|
14
|
+
ref="sheet_element"
|
|
15
|
+
role="dialog"
|
|
16
|
+
aria-modal="true"
|
|
17
|
+
:class="cn(base_classes, side_classes[side], props.class)"
|
|
18
|
+
tabindex="-1"
|
|
19
|
+
@keydown.escape="close"
|
|
20
|
+
>
|
|
21
|
+
<slot />
|
|
22
|
+
|
|
23
|
+
<button
|
|
24
|
+
v-if="show_close_button"
|
|
25
|
+
type="button"
|
|
26
|
+
class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
|
27
|
+
aria-label="Close"
|
|
28
|
+
@click="close"
|
|
29
|
+
>
|
|
30
|
+
<svg
|
|
31
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
32
|
+
width="24"
|
|
33
|
+
height="24"
|
|
34
|
+
viewBox="0 0 24 24"
|
|
35
|
+
fill="none"
|
|
36
|
+
stroke="currentColor"
|
|
37
|
+
stroke-width="2"
|
|
38
|
+
stroke-linecap="round"
|
|
39
|
+
stroke-linejoin="round"
|
|
40
|
+
>
|
|
41
|
+
<path d="M18 6 6 18" />
|
|
42
|
+
<path d="m6 6 12 12" />
|
|
43
|
+
</svg>
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
</Transition>
|
|
47
|
+
</Teleport>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<script setup lang="ts">
|
|
51
|
+
import { ref, computed, watch, provide, onMounted, onUnmounted, type InjectionKey } from "vue"
|
|
52
|
+
import { cn } from "../../utils/cn"
|
|
53
|
+
import type { SheetSide, SheetContext } from "../../types/sheet"
|
|
54
|
+
|
|
55
|
+
export interface Props {
|
|
56
|
+
open?: boolean
|
|
57
|
+
side?: SheetSide
|
|
58
|
+
show_close_button?: boolean
|
|
59
|
+
class?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
63
|
+
open: false,
|
|
64
|
+
side: "right",
|
|
65
|
+
show_close_button: true
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const emit = defineEmits<{
|
|
69
|
+
"update:open": [value: boolean]
|
|
70
|
+
}>()
|
|
71
|
+
|
|
72
|
+
export const SHEET_CONTEXT_KEY: InjectionKey<SheetContext> = Symbol("sheet-context")
|
|
73
|
+
|
|
74
|
+
const is_open = ref(props.open)
|
|
75
|
+
const sheet_element = ref<HTMLElement | null>(null)
|
|
76
|
+
|
|
77
|
+
watch(() => props.open, (value) => {
|
|
78
|
+
is_open.value = value
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const open = (): void => {
|
|
82
|
+
is_open.value = true
|
|
83
|
+
emit("update:open", true)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const close = (): void => {
|
|
87
|
+
is_open.value = false
|
|
88
|
+
emit("update:open", false)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const handleOverlayClick = (): void => {
|
|
92
|
+
close()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const handleKeyDown = (event: KeyboardEvent): void => {
|
|
96
|
+
if (event.key === "Escape" && is_open.value) {
|
|
97
|
+
close()
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
onMounted(() => {
|
|
102
|
+
document.addEventListener("keydown", handleKeyDown)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
onUnmounted(() => {
|
|
106
|
+
document.removeEventListener("keydown", handleKeyDown)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
watch(is_open, (open) => {
|
|
110
|
+
if (open) {
|
|
111
|
+
document.body.style.overflow = "hidden"
|
|
112
|
+
} else {
|
|
113
|
+
document.body.style.overflow = ""
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const side = computed(() => props.side)
|
|
118
|
+
|
|
119
|
+
const transition_name = computed(() => `sheet-${side.value}`)
|
|
120
|
+
|
|
121
|
+
provide(SHEET_CONTEXT_KEY, {
|
|
122
|
+
is_open: is_open.value,
|
|
123
|
+
side: side.value,
|
|
124
|
+
open,
|
|
125
|
+
close
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const base_classes = "fixed z-50 gap-4 bg-background p-6 shadow-lg"
|
|
129
|
+
|
|
130
|
+
const side_classes: Record<SheetSide, string> = {
|
|
131
|
+
top: "inset-x-0 top-0 border-b",
|
|
132
|
+
bottom: "inset-x-0 bottom-0 border-t",
|
|
133
|
+
left: "inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
|
|
134
|
+
right: "inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm"
|
|
135
|
+
}
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
<style scoped>
|
|
139
|
+
.sheet-overlay-enter-active,.sheet-overlay-leave-active{transition:opacity .3s ease}.sheet-overlay-enter-from,.sheet-overlay-leave-to{opacity:0}.sheet-bottom-enter-active,.sheet-bottom-leave-active,.sheet-left-enter-active,.sheet-left-leave-active,.sheet-right-enter-active,.sheet-right-leave-active,.sheet-top-enter-active,.sheet-top-leave-active{transition:transform .3s ease}.sheet-right-enter-from,.sheet-right-leave-to{transform:translateX(100%)}.sheet-left-enter-from,.sheet-left-leave-to{transform:translateX(-100%)}.sheet-top-enter-from,.sheet-top-leave-to{transform:translateY(-100%)}.sheet-bottom-enter-from,.sheet-bottom-leave-to{transform:translateY(100%)}
|
|
140
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<p :class="cn('text-sm text-muted-foreground', props.class)">
|
|
3
|
+
<slot />
|
|
4
|
+
</p>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { cn } from "../../utils/cn"
|
|
9
|
+
|
|
10
|
+
export interface Props {
|
|
11
|
+
class?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = defineProps<Props>()
|
|
15
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', props.class)">
|
|
3
|
+
<slot />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { cn } from "../../utils/cn"
|
|
9
|
+
|
|
10
|
+
export interface Props {
|
|
11
|
+
class?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = defineProps<Props>()
|
|
15
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="cn('flex flex-col space-y-2 text-center sm:text-left', props.class)">
|
|
3
|
+
<slot />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { cn } from "../../utils/cn"
|
|
9
|
+
|
|
10
|
+
export interface Props {
|
|
11
|
+
class?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = defineProps<Props>()
|
|
15
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<h2 :class="cn('text-lg font-semibold text-foreground', props.class)">
|
|
3
|
+
<slot />
|
|
4
|
+
</h2>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { cn } from "../../utils/cn"
|
|
9
|
+
|
|
10
|
+
export interface Props {
|
|
11
|
+
class?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const props = defineProps<Props>()
|
|
15
|
+
</script>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
:class="composable.track_classes.value"
|
|
5
|
+
:disabled="composable.is_disabled.value"
|
|
6
|
+
:data-state="modelValue ? 'checked' : 'unchecked'"
|
|
7
|
+
v-bind="composable.aria_attributes.value"
|
|
8
|
+
@click="handleClick"
|
|
9
|
+
>
|
|
10
|
+
<span
|
|
11
|
+
:class="composable.thumb_classes.value"
|
|
12
|
+
:data-state="modelValue ? 'checked' : 'unchecked'"
|
|
13
|
+
/>
|
|
14
|
+
</button>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import { toRef } from "vue"
|
|
19
|
+
import { useSwitch } from "../../composables/useSwitch"
|
|
20
|
+
import type { SwitchProps } from "../../types/switch"
|
|
21
|
+
|
|
22
|
+
interface Props extends SwitchProps {
|
|
23
|
+
modelValue?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
27
|
+
modelValue: false,
|
|
28
|
+
disabled: false
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const emit = defineEmits<{
|
|
32
|
+
"update:modelValue": [value: boolean]
|
|
33
|
+
}>()
|
|
34
|
+
|
|
35
|
+
const checked = toRef(() => props.modelValue)
|
|
36
|
+
const composable = useSwitch(toRef(() => props), checked)
|
|
37
|
+
|
|
38
|
+
const handleClick = () => {
|
|
39
|
+
if (!composable.is_disabled.value) {
|
|
40
|
+
emit("update:modelValue", !props.modelValue)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<textarea
|
|
3
|
+
:id="id"
|
|
4
|
+
:class="composable.base_classes.value"
|
|
5
|
+
:disabled="composable.is_disabled.value"
|
|
6
|
+
:readonly="composable.is_readonly.value"
|
|
7
|
+
:placeholder="placeholder"
|
|
8
|
+
:rows="rows"
|
|
9
|
+
:value="modelValue"
|
|
10
|
+
v-bind="composable.aria_attributes.value"
|
|
11
|
+
@input="handleInput"
|
|
12
|
+
@focus="composable.handleFocus"
|
|
13
|
+
@blur="handleBlur"
|
|
14
|
+
/>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import { toRef } from "vue"
|
|
19
|
+
import { useTextarea } from "../../composables/useTextarea"
|
|
20
|
+
import type { TextareaProps } from "../../types/textarea"
|
|
21
|
+
|
|
22
|
+
interface Props extends TextareaProps {
|
|
23
|
+
id?: string
|
|
24
|
+
modelValue?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
28
|
+
rows: 3,
|
|
29
|
+
resize: "vertical",
|
|
30
|
+
disabled: false,
|
|
31
|
+
readonly: false
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const emit = defineEmits<{
|
|
35
|
+
"update:modelValue": [value: string]
|
|
36
|
+
blur: [event: FocusEvent]
|
|
37
|
+
}>()
|
|
38
|
+
|
|
39
|
+
const composable = useTextarea(toRef(() => props))
|
|
40
|
+
|
|
41
|
+
const handleInput = (event: Event) => {
|
|
42
|
+
const target = event.target as HTMLTextAreaElement
|
|
43
|
+
emit("update:modelValue", target.value)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const handleBlur = (event: FocusEvent) => {
|
|
47
|
+
composable.handleBlur()
|
|
48
|
+
emit("blur", event)
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="cn(base_classes, variant_classes[variant], props.class)"
|
|
4
|
+
:role="aria_role"
|
|
5
|
+
:aria-live="aria_live"
|
|
6
|
+
aria-atomic="true"
|
|
7
|
+
>
|
|
8
|
+
<div class="flex-1 space-y-1">
|
|
9
|
+
<div v-if="title" class="text-sm font-semibold">
|
|
10
|
+
{{ title }}
|
|
11
|
+
</div>
|
|
12
|
+
<div v-if="description" class="text-sm opacity-90">
|
|
13
|
+
{{ description }}
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div v-if="action || dismissible" class="flex items-center gap-2 ml-4">
|
|
18
|
+
<button
|
|
19
|
+
v-if="action"
|
|
20
|
+
type="button"
|
|
21
|
+
:class="action_button_classes"
|
|
22
|
+
@click="action.onClick"
|
|
23
|
+
>
|
|
24
|
+
{{ action.label }}
|
|
25
|
+
</button>
|
|
26
|
+
|
|
27
|
+
<button
|
|
28
|
+
v-if="dismissible"
|
|
29
|
+
type="button"
|
|
30
|
+
:class="dismiss_button_classes"
|
|
31
|
+
aria-label="Dismiss"
|
|
32
|
+
@click="emit('dismiss')"
|
|
33
|
+
>
|
|
34
|
+
<svg
|
|
35
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
36
|
+
width="16"
|
|
37
|
+
height="16"
|
|
38
|
+
viewBox="0 0 24 24"
|
|
39
|
+
fill="none"
|
|
40
|
+
stroke="currentColor"
|
|
41
|
+
stroke-width="2"
|
|
42
|
+
stroke-linecap="round"
|
|
43
|
+
stroke-linejoin="round"
|
|
44
|
+
>
|
|
45
|
+
<path d="M18 6 6 18" />
|
|
46
|
+
<path d="m6 6 12 12" />
|
|
47
|
+
</svg>
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<script setup lang="ts">
|
|
54
|
+
import { computed } from "vue"
|
|
55
|
+
import { cn } from "../../utils/cn"
|
|
56
|
+
import type { ToastVariant, ToastAction } from "../../types/toast"
|
|
57
|
+
|
|
58
|
+
export interface Props {
|
|
59
|
+
title?: string
|
|
60
|
+
description?: string
|
|
61
|
+
variant?: ToastVariant
|
|
62
|
+
dismissible?: boolean
|
|
63
|
+
action?: ToastAction
|
|
64
|
+
class?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
68
|
+
variant: "default",
|
|
69
|
+
dismissible: true
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const emit = defineEmits<{
|
|
73
|
+
dismiss: []
|
|
74
|
+
}>()
|
|
75
|
+
|
|
76
|
+
const base_classes = "pointer-events-auto flex items-start gap-4 rounded-lg border p-4 shadow-lg transition-all"
|
|
77
|
+
|
|
78
|
+
const variant_classes: Record<ToastVariant, string> = {
|
|
79
|
+
default: "bg-background text-foreground border-border",
|
|
80
|
+
success: "bg-green-50 text-green-900 border-green-200 dark:bg-green-950 dark:text-green-100 dark:border-green-800",
|
|
81
|
+
error: "bg-red-50 text-red-900 border-red-200 dark:bg-red-950 dark:text-red-100 dark:border-red-800",
|
|
82
|
+
warning: "bg-yellow-50 text-yellow-900 border-yellow-200 dark:bg-yellow-950 dark:text-yellow-100 dark:border-yellow-800",
|
|
83
|
+
info: "bg-blue-50 text-blue-900 border-blue-200 dark:bg-blue-950 dark:text-blue-100 dark:border-blue-800"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const aria_role = computed(() => {
|
|
87
|
+
return props.variant === "error" ? "alert" : "status"
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const aria_live = computed(() => {
|
|
91
|
+
return props.variant === "error" ? "assertive" : "polite"
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const action_button_classes = cn(
|
|
95
|
+
"inline-flex items-center justify-center rounded-md text-sm font-medium",
|
|
96
|
+
"h-8 px-3 ring-offset-background transition-colors",
|
|
97
|
+
"hover:bg-secondary focus-visible:outline-none focus-visible:ring-2",
|
|
98
|
+
"focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
const dismiss_button_classes = cn(
|
|
102
|
+
"inline-flex items-center justify-center rounded-md",
|
|
103
|
+
"h-6 w-6 shrink-0 opacity-70 transition-opacity",
|
|
104
|
+
"hover:opacity-100 focus-visible:outline-none focus-visible:ring-2",
|
|
105
|
+
"focus-visible:ring-ring"
|
|
106
|
+
)
|
|
107
|
+
</script>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<div
|
|
4
|
+
:class="cn(container_classes, position_classes[position], props.class)"
|
|
5
|
+
aria-label="Notifications"
|
|
6
|
+
>
|
|
7
|
+
<TransitionGroup
|
|
8
|
+
name="toast"
|
|
9
|
+
tag="div"
|
|
10
|
+
:class="cn('flex flex-col', gap_class)"
|
|
11
|
+
>
|
|
12
|
+
<Toast
|
|
13
|
+
v-for="toast in visible_toasts"
|
|
14
|
+
:key="toast.id"
|
|
15
|
+
:title="toast.title"
|
|
16
|
+
:description="toast.description"
|
|
17
|
+
:variant="toast.variant"
|
|
18
|
+
:dismissible="toast.dismissible"
|
|
19
|
+
:action="toast.action"
|
|
20
|
+
@dismiss="handleDismiss(toast.id)"
|
|
21
|
+
/>
|
|
22
|
+
</TransitionGroup>
|
|
23
|
+
</div>
|
|
24
|
+
</Teleport>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { computed, ref, watchEffect } from "vue"
|
|
29
|
+
import { cn } from "../../utils/cn"
|
|
30
|
+
import { useToast } from "../../composables/useToast"
|
|
31
|
+
import Toast from "./Toast.vue"
|
|
32
|
+
import type { ToastPosition, Toast as ToastType } from "../../types/toast"
|
|
33
|
+
|
|
34
|
+
export interface Props {
|
|
35
|
+
position?: ToastPosition
|
|
36
|
+
max_toasts?: number
|
|
37
|
+
gap?: number
|
|
38
|
+
class?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
42
|
+
position: "bottom-right",
|
|
43
|
+
max_toasts: 5,
|
|
44
|
+
gap: 8
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const { toasts, dismiss } = useToast()
|
|
48
|
+
|
|
49
|
+
const internal_toasts = ref<ToastType[]>([])
|
|
50
|
+
|
|
51
|
+
watchEffect(() => {
|
|
52
|
+
internal_toasts.value = [...toasts]
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const visible_toasts = computed(() => {
|
|
56
|
+
const sorted = [...internal_toasts.value].sort((a, b) => b.created_at - a.created_at)
|
|
57
|
+
return sorted.slice(0, props.max_toasts)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const handleDismiss = (id: string): void => {
|
|
61
|
+
dismiss(id)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const container_classes = "fixed z-[100] flex pointer-events-none p-4"
|
|
65
|
+
|
|
66
|
+
const position_classes: Record<ToastPosition, string> = {
|
|
67
|
+
"top-left": "top-0 left-0 flex-col",
|
|
68
|
+
"top-center": "top-0 left-1/2 -translate-x-1/2 flex-col items-center",
|
|
69
|
+
"top-right": "top-0 right-0 flex-col items-end",
|
|
70
|
+
"bottom-left": "bottom-0 left-0 flex-col-reverse",
|
|
71
|
+
"bottom-center": "bottom-0 left-1/2 -translate-x-1/2 flex-col-reverse items-center",
|
|
72
|
+
"bottom-right": "bottom-0 right-0 flex-col-reverse items-end"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const gap_class = computed(() => `gap-${props.gap / 4}`)
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<style scoped>
|
|
79
|
+
.toast-enter-active,.toast-leave-active{transition:all .3s ease}.toast-enter-from,.toast-leave-to{opacity:0;transform:translateX(100%)}.toast-move{transition:transform .3s ease}
|
|
80
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="relative inline-block" :class="props.class">
|
|
3
|
+
<slot />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { provide, ref, toRef, type InjectionKey, type Ref } from "vue"
|
|
9
|
+
import { useTooltip, type UseTooltipReturn } from "../../composables/useTooltip"
|
|
10
|
+
import type { TooltipSide, TooltipAlign } from "../../types/tooltip"
|
|
11
|
+
|
|
12
|
+
export interface Props {
|
|
13
|
+
side?: TooltipSide
|
|
14
|
+
align?: TooltipAlign
|
|
15
|
+
delay_duration?: number
|
|
16
|
+
skip_delay_duration?: number
|
|
17
|
+
disabled?: boolean
|
|
18
|
+
class?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
22
|
+
side: "top",
|
|
23
|
+
align: "center",
|
|
24
|
+
delay_duration: 200,
|
|
25
|
+
skip_delay_duration: 100,
|
|
26
|
+
disabled: false
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export const TOOLTIP_CONTEXT_KEY: InjectionKey<UseTooltipReturn> = Symbol("tooltip-context")
|
|
30
|
+
|
|
31
|
+
const tooltip_props = ref({
|
|
32
|
+
side: props.side,
|
|
33
|
+
align: props.align,
|
|
34
|
+
delay_duration: props.delay_duration,
|
|
35
|
+
skip_delay_duration: props.skip_delay_duration,
|
|
36
|
+
disabled: props.disabled
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const tooltip = useTooltip(tooltip_props as Ref<typeof tooltip_props.value>)
|
|
40
|
+
|
|
41
|
+
provide(TOOLTIP_CONTEXT_KEY, tooltip)
|
|
42
|
+
</script>
|