@bl33dz/fa814698dcde12f86a37ac31dd3aedf9 1.0.20 → 1.0.22
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/fa814698dcde12f86a37ac31dd3aedf9.css +1 -1
- package/dist/perisai-ui.es.js +3485 -2386
- package/dist/perisai-ui.umd.js +20 -1
- package/package.json +3 -3
- package/src/index.ts +6 -5
- package/src/shadcn/context-menu/ContextMenuContent.vue +1 -1
- package/src/shadcn/context-menu/ContextMenuItem.vue +1 -1
- package/src/shadcn/context-menu/ContextMenuSubContent.vue +1 -1
- package/src/shadcn/input-otp/InputOTP.vue +28 -0
- package/src/shadcn/input-otp/InputOTPGroup.vue +22 -0
- package/src/shadcn/input-otp/InputOTPSeparator.vue +21 -0
- package/src/shadcn/input-otp/InputOTPSlot.vue +32 -0
- package/src/shadcn/input-otp/index.ts +4 -0
- package/src/shadcn/navigation-menu/NavigationMenuTrigger.vue +17 -12
- package/src/ui/card.vue +2 -2
- package/src/ui/dialog-body.vue +1 -1
- package/src/ui/dialog-header.vue +1 -1
- package/src/ui/select.vue +2 -1
- package/src/ui/sonner.vue +119 -22
- package/src/ui/toast.ts +55 -0
- package/vite.config.ts +0 -1
- package/src/ui/InputOTP.vue +0 -69
- package/src/ui/InputOTPGroup.vue +0 -7
- package/src/ui/InputOTPSeparator.vue +0 -8
- package/src/ui/InputOTPSlot.vue +0 -61
- package/src/ui/input-otp.vue +0 -52
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.22",
|
|
7
7
|
"main": "dist/perisai-ui.umd.js",
|
|
8
8
|
"module": "dist/perisai-ui.es.js",
|
|
9
9
|
"scripts": {
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
"esbuild": "^0.25.12",
|
|
29
29
|
"estree-walker": "^2.0.2",
|
|
30
30
|
"fdir": "^6.5.0",
|
|
31
|
-
"input-otp": "^1.4.2",
|
|
32
31
|
"lucide-vue-next": "^0.554.0",
|
|
33
32
|
"magic-string": "^0.30.21",
|
|
34
33
|
"nanoid": "^3.3.11",
|
|
@@ -39,7 +38,8 @@
|
|
|
39
38
|
"source-map-js": "^1.2.1",
|
|
40
39
|
"tailwind-merge": "^3.4.0",
|
|
41
40
|
"tinyglobby": "^0.2.15",
|
|
42
|
-
"vaul-vue": "^0.4.1"
|
|
41
|
+
"vaul-vue": "^0.4.1",
|
|
42
|
+
"vue-input-otp": "^0.3.2"
|
|
43
43
|
},
|
|
44
44
|
"description": ""
|
|
45
45
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,12 +10,11 @@ export * from './shadcn/tooltip';
|
|
|
10
10
|
export * from './shadcn/command';
|
|
11
11
|
export * from './shadcn/switch';
|
|
12
12
|
export * from './shadcn/collapsible';
|
|
13
|
+
export * from './shadcn/navigation-menu';
|
|
14
|
+
export * from './shadcn/input-otp';
|
|
15
|
+
export * from './shadcn/context-menu';
|
|
13
16
|
|
|
14
17
|
export * from './ui/tree';
|
|
15
|
-
export { default as InputOTP } from './ui/InputOTP.vue';
|
|
16
|
-
export { default as InputOTPGroup } from './ui/InputOTPGroup.vue';
|
|
17
|
-
export { default as InputOTPSeparator } from './ui/InputOTPSeparator.vue';
|
|
18
|
-
export { default as InputOTPSlot } from './ui/InputOTPSlot.vue';
|
|
19
18
|
export { default as PopoverContent } from './ui/PopoverContent.vue';
|
|
20
19
|
export { default as PopoverTrigger } from './ui/PopoverTrigger.vue';
|
|
21
20
|
export { default as SelectContent } from './ui/SelectContent.vue';
|
|
@@ -112,7 +111,6 @@ export { default as Slash } from './ui/icons/Slash.vue';
|
|
|
112
111
|
export { default as Trash2 } from './ui/icons/Trash2.vue';
|
|
113
112
|
export { default as Type } from './ui/icons/Type.vue';
|
|
114
113
|
export { default as User } from './ui/icons/User.vue';
|
|
115
|
-
export { default as InputOtp } from './ui/input-otp.vue';
|
|
116
114
|
export { default as Input } from './ui/input.vue';
|
|
117
115
|
export { default as Label } from './ui/label.vue';
|
|
118
116
|
export { default as Pagination } from './ui/pagination.vue';
|
|
@@ -176,3 +174,6 @@ export { default as Tabs } from './ui/tabs.vue';
|
|
|
176
174
|
export { default as Textarea } from './ui/textarea.vue';
|
|
177
175
|
export { default as ThreatGauge } from './ui/threat-gauge.vue';
|
|
178
176
|
export { default as Toggle } from './ui/toggle.vue';
|
|
177
|
+
|
|
178
|
+
// Toast utilities
|
|
179
|
+
export { toast } from './ui/toast';
|
|
@@ -24,7 +24,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
24
24
|
data-slot="context-menu-content"
|
|
25
25
|
v-bind="forwarded"
|
|
26
26
|
:class="cn(
|
|
27
|
-
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--reka-context-menu-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-
|
|
27
|
+
'bg-popover text-popover-foreground border-border data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--reka-context-menu-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-lg',
|
|
28
28
|
props.class,
|
|
29
29
|
)"
|
|
30
30
|
>
|
|
@@ -30,7 +30,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
30
30
|
:data-variant="variant"
|
|
31
31
|
v-bind="forwarded"
|
|
32
32
|
:class="cn(
|
|
33
|
-
'focus:bg-accent focus:text-accent-foreground
|
|
33
|
+
'focus:bg-accent focus:text-accent-foreground text-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
|
34
34
|
props.class,
|
|
35
35
|
)"
|
|
36
36
|
>
|
|
@@ -23,7 +23,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
23
23
|
v-bind="forwarded"
|
|
24
24
|
:class="
|
|
25
25
|
cn(
|
|
26
|
-
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--reka-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
|
|
26
|
+
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--reka-context-menu-content-transform-origin) overflow-hidden rounded-md border border-border p-1 shadow-lg',
|
|
27
27
|
props.class,
|
|
28
28
|
)
|
|
29
29
|
"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import type { OTPInputEmits, OTPInputProps } from "vue-input-otp"
|
|
4
|
+
import { reactiveOmit } from "@vueuse/core"
|
|
5
|
+
import { useForwardPropsEmits } from "reka-ui"
|
|
6
|
+
import { OTPInput } from "vue-input-otp"
|
|
7
|
+
import { cn } from "../../lib/utils"
|
|
8
|
+
|
|
9
|
+
const props = defineProps<OTPInputProps & { class?: HTMLAttributes["class"] }>()
|
|
10
|
+
|
|
11
|
+
const emits = defineEmits<OTPInputEmits>()
|
|
12
|
+
|
|
13
|
+
const delegatedProps = reactiveOmit(props, "class")
|
|
14
|
+
|
|
15
|
+
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<OTPInput
|
|
20
|
+
v-slot="slotProps"
|
|
21
|
+
v-bind="forwarded"
|
|
22
|
+
:container-class="cn('flex items-center gap-2 has-disabled:opacity-50', props.class)"
|
|
23
|
+
data-slot="input-otp"
|
|
24
|
+
class="disabled:cursor-not-allowed"
|
|
25
|
+
>
|
|
26
|
+
<slot v-bind="slotProps" />
|
|
27
|
+
</OTPInput>
|
|
28
|
+
</template>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import { reactiveOmit } from "@vueuse/core"
|
|
4
|
+
import { useForwardProps } from "reka-ui"
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
|
|
8
|
+
|
|
9
|
+
const delegatedProps = reactiveOmit(props, "class")
|
|
10
|
+
|
|
11
|
+
const forwarded = useForwardProps(delegatedProps)
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div
|
|
16
|
+
data-slot="input-otp-group"
|
|
17
|
+
v-bind="forwarded"
|
|
18
|
+
:class="cn('flex items-center', props.class)"
|
|
19
|
+
>
|
|
20
|
+
<slot />
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import { MinusIcon } from "lucide-vue-next"
|
|
4
|
+
import { useForwardProps } from "reka-ui"
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
|
|
7
|
+
|
|
8
|
+
const forwarded = useForwardProps(props)
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<div
|
|
13
|
+
data-slot="input-otp-separator"
|
|
14
|
+
role="separator"
|
|
15
|
+
v-bind="forwarded"
|
|
16
|
+
>
|
|
17
|
+
<slot>
|
|
18
|
+
<MinusIcon />
|
|
19
|
+
</slot>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from "vue"
|
|
3
|
+
import { reactiveOmit } from "@vueuse/core"
|
|
4
|
+
import { useForwardProps } from "reka-ui"
|
|
5
|
+
import { computed } from "vue"
|
|
6
|
+
import { useVueOTPContext } from "vue-input-otp"
|
|
7
|
+
import { cn } from "../../lib/utils"
|
|
8
|
+
|
|
9
|
+
const props = defineProps<{ index: number, class?: HTMLAttributes["class"] }>()
|
|
10
|
+
|
|
11
|
+
const delegatedProps = reactiveOmit(props, "class")
|
|
12
|
+
|
|
13
|
+
const forwarded = useForwardProps(delegatedProps)
|
|
14
|
+
|
|
15
|
+
const context = useVueOTPContext()
|
|
16
|
+
|
|
17
|
+
const slot = computed(() => context?.value.slots[props.index])
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<div
|
|
22
|
+
v-bind="forwarded"
|
|
23
|
+
data-slot="input-otp-slot"
|
|
24
|
+
:data-active="slot?.isActive"
|
|
25
|
+
:class="cn('data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive border-input relative flex h-9 w-9 items-center justify-center border-y border-r text-sm bg-input-background transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]', props.class)"
|
|
26
|
+
>
|
|
27
|
+
{{ slot?.char }}
|
|
28
|
+
<div v-if="slot?.hasFakeCaret" class="pointer-events-none absolute inset-0 flex items-center justify-center">
|
|
29
|
+
<div class="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import type { NavigationMenuTriggerProps } from "reka-ui"
|
|
3
|
+
import type { HTMLAttributes } from "vue"
|
|
4
|
+
import { reactiveOmit } from "@vueuse/core"
|
|
5
|
+
import { NavigationMenuTrigger, useForwardProps } from "reka-ui"
|
|
2
6
|
import { cn } from '@/lib/utils';
|
|
3
7
|
import { ChevronDown } from 'lucide-vue-next';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
|
|
9
|
+
const props = defineProps<NavigationMenuTriggerProps & { class?: HTMLAttributes["class"] }>()
|
|
10
|
+
|
|
11
|
+
const delegatedProps = reactiveOmit(props, "class")
|
|
12
|
+
const forwardedProps = useForwardProps(delegatedProps)
|
|
8
13
|
</script>
|
|
9
14
|
<template>
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
<NavigationMenuTrigger
|
|
16
|
+
data-slot="navigation-menu-trigger"
|
|
17
|
+
v-bind="forwardedProps"
|
|
18
|
+
:class="cn(
|
|
19
|
+
'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 transition-[color,box-shadow] outline-none focus-visible:ring-ring/50 focus-visible:ring-[3px] focus-visible:outline-1',
|
|
20
|
+
props.class
|
|
21
|
+
)"
|
|
16
22
|
>
|
|
17
23
|
<slot />
|
|
18
24
|
<ChevronDown
|
|
19
25
|
class="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
|
|
20
26
|
aria-hidden="true"
|
|
21
|
-
:class="{ 'rotate-180': props.open }"
|
|
22
27
|
/>
|
|
23
|
-
</
|
|
28
|
+
</NavigationMenuTrigger>
|
|
24
29
|
</template>
|
package/src/ui/card.vue
CHANGED
|
@@ -55,7 +55,7 @@ const customPaddingValue = computed(() => {
|
|
|
55
55
|
<!-- Content container -->
|
|
56
56
|
<div
|
|
57
57
|
data-slot="card"
|
|
58
|
-
:class="cn('bg-card text-card-foreground flex flex-col gap-3 relative rounded
|
|
58
|
+
:class="cn('bg-card text-card-foreground flex flex-col gap-3 relative rounded', customPaddingValue, $attrs.class as any)"
|
|
59
59
|
:style="{ clipPath: clipPathValue, margin: '1px' }"
|
|
60
60
|
v-bind="$attrs"
|
|
61
61
|
>
|
|
@@ -65,7 +65,7 @@ const customPaddingValue = computed(() => {
|
|
|
65
65
|
<div
|
|
66
66
|
v-else
|
|
67
67
|
data-slot="card"
|
|
68
|
-
:class="cn('bg-
|
|
68
|
+
:class="cn('bg-card text-card-foreground flex flex-col gap-3 relative rounded-xl border border-border', $attrs.class as any)"
|
|
69
69
|
v-bind="$attrs"
|
|
70
70
|
>
|
|
71
71
|
<slot />
|
package/src/ui/dialog-body.vue
CHANGED
package/src/ui/dialog-header.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
data-slot="dialog-header"
|
|
4
|
-
:class="['flex flex-col gap-2 text-center pl-4 py-4 pr-5 sm:text-left border-b', $attrs.class]"
|
|
4
|
+
:class="['flex flex-col gap-2 text-center pl-4 py-4 pr-5 sm:text-left border-b border-border', $attrs.class]"
|
|
5
5
|
v-bind="$attrs"
|
|
6
6
|
>
|
|
7
7
|
<slot />
|
package/src/ui/select.vue
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
:disabled="disabled"
|
|
21
21
|
:class="mergedClass"
|
|
22
22
|
>
|
|
23
|
-
<SelectTrigger class="w-full">
|
|
23
|
+
<SelectTrigger :class="cn('w-full', triggerClass)">
|
|
24
24
|
<slot name="trigger">
|
|
25
25
|
<SelectValue :placeholder="placeholder" />
|
|
26
26
|
</slot>
|
|
@@ -59,6 +59,7 @@ const props = defineProps({
|
|
|
59
59
|
disabled: Boolean,
|
|
60
60
|
customClass: String,
|
|
61
61
|
contentClass: String,
|
|
62
|
+
triggerClass: String,
|
|
62
63
|
title: String,
|
|
63
64
|
maxDisplay: { type: Number, default: 3 },
|
|
64
65
|
size: { type: String, default: 'default' },
|
package/src/ui/sonner.vue
CHANGED
|
@@ -1,37 +1,53 @@
|
|
|
1
1
|
|
|
2
2
|
<template>
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
<div
|
|
4
|
+
class="fixed z-50 pointer-events-none bottom-4 right-4 relative min-h-[80px]"
|
|
5
|
+
@mouseenter="expandStack"
|
|
6
|
+
@mouseleave="collapseStack"
|
|
7
|
+
>
|
|
8
|
+
<transition-group name="sonner-fade" tag="div">
|
|
9
|
+
<div
|
|
10
|
+
v-for="(toast, idx) in toasts"
|
|
11
|
+
:key="toast.id"
|
|
12
|
+
:class="getToastClasses(toast.type, idx)"
|
|
13
|
+
:style="{
|
|
14
|
+
opacity: toast.visible ? 1 : 0,
|
|
15
|
+
transform: isStackExpanded ? `translateY(-${(toasts.length - 1 - idx) * 72}px)` : `translateY(-${(toasts.length - 1 - idx) * 4}px)`,
|
|
16
|
+
zIndex: idx + 1,
|
|
17
|
+
position: 'absolute',
|
|
18
|
+
right: '0',
|
|
19
|
+
top: '0',
|
|
20
|
+
pointerEvents: 'auto'
|
|
21
|
+
}"
|
|
19
22
|
>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
<span v-if="toast.icon" :class="getIconClasses(toast.type)">{{ toast.icon }}</span>
|
|
24
|
+
<div class="flex-1">
|
|
25
|
+
<div v-if="toast.title" class="font-medium text-sm leading-none">{{ toast.title }}</div>
|
|
26
|
+
<div v-if="toast.description" class="text-xs mt-1 leading-none" :class="getDescriptionClasses(toast.type)">{{ toast.description }}</div>
|
|
27
|
+
</div>
|
|
28
|
+
<button
|
|
29
|
+
:class="getCloseButtonClasses(toast.type)"
|
|
30
|
+
@click="removeToast(toast.id)"
|
|
31
|
+
aria-label="Close"
|
|
32
|
+
>
|
|
33
|
+
×
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
</transition-group>
|
|
37
|
+
</div>
|
|
24
38
|
</template>
|
|
25
39
|
|
|
26
40
|
<script setup>
|
|
27
41
|
import { ref, onUnmounted } from 'vue';
|
|
28
42
|
|
|
29
43
|
const toasts = ref([]);
|
|
44
|
+
const isStackExpanded = ref(false);
|
|
30
45
|
let idCounter = 0;
|
|
31
46
|
|
|
32
|
-
function showToast({ title, description, icon, duration = 3000 }) {
|
|
47
|
+
function showToast({ title, description, icon, duration = 3000, type = 'default' }) {
|
|
33
48
|
const id = ++idCounter;
|
|
34
|
-
|
|
49
|
+
console.log('Creating toast:', { id, title, description, icon, type });
|
|
50
|
+
toasts.value.push({ id, title, description, icon, visible: true, type });
|
|
35
51
|
setTimeout(() => removeToast(id), duration);
|
|
36
52
|
}
|
|
37
53
|
|
|
@@ -45,6 +61,87 @@ function removeToast(id) {
|
|
|
45
61
|
}
|
|
46
62
|
}
|
|
47
63
|
|
|
64
|
+
function expandStack() {
|
|
65
|
+
isStackExpanded.value = true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function collapseStack() {
|
|
69
|
+
isStackExpanded.value = false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getToastClasses(type, index) {
|
|
73
|
+
const baseClasses = 'rounded-lg px-4 py-3 flex items-center gap-3 pointer-events-auto w-80 shadow-lg border font-sans transition-all duration-200 ease-out';
|
|
74
|
+
const stackOffset = index * 8; // Stack offset in pixels
|
|
75
|
+
|
|
76
|
+
switch (type) {
|
|
77
|
+
case 'success':
|
|
78
|
+
return `${baseClasses} bg-green-50 text-green-800 border-green-200`;
|
|
79
|
+
case 'error':
|
|
80
|
+
return `${baseClasses} bg-red-50 text-red-800 border-red-200`;
|
|
81
|
+
case 'warning':
|
|
82
|
+
return `${baseClasses} bg-yellow-50 text-yellow-800 border-yellow-200`;
|
|
83
|
+
case 'info':
|
|
84
|
+
return `${baseClasses} bg-blue-50 text-blue-800 border-blue-200`;
|
|
85
|
+
case 'loading':
|
|
86
|
+
return `${baseClasses} bg-gray-50 text-gray-800 border-gray-200`;
|
|
87
|
+
default:
|
|
88
|
+
return `${baseClasses} bg-card text-card-foreground border-border`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getDescriptionClasses(type) {
|
|
93
|
+
switch (type) {
|
|
94
|
+
case 'success':
|
|
95
|
+
return 'text-green-700';
|
|
96
|
+
case 'error':
|
|
97
|
+
return 'text-red-700';
|
|
98
|
+
case 'warning':
|
|
99
|
+
return 'text-yellow-700';
|
|
100
|
+
case 'info':
|
|
101
|
+
return 'text-blue-700';
|
|
102
|
+
case 'loading':
|
|
103
|
+
return 'text-gray-700';
|
|
104
|
+
default:
|
|
105
|
+
return 'text-muted-foreground';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getCloseButtonClasses(type) {
|
|
110
|
+
const baseClasses = 'ml-2 hover:opacity-80 focus:outline-none transition-opacity rounded-full w-5 h-5 flex items-center justify-center text-xs';
|
|
111
|
+
|
|
112
|
+
switch (type) {
|
|
113
|
+
case 'success':
|
|
114
|
+
return `${baseClasses} text-green-600 hover:bg-green-100`;
|
|
115
|
+
case 'error':
|
|
116
|
+
return `${baseClasses} text-red-600 hover:bg-red-100`;
|
|
117
|
+
case 'warning':
|
|
118
|
+
return `${baseClasses} text-yellow-600 hover:bg-yellow-100`;
|
|
119
|
+
case 'info':
|
|
120
|
+
return `${baseClasses} text-blue-600 hover:bg-blue-100`;
|
|
121
|
+
case 'loading':
|
|
122
|
+
return `${baseClasses} text-gray-600 hover:bg-gray-100`;
|
|
123
|
+
default:
|
|
124
|
+
return `${baseClasses} text-muted-foreground hover:bg-muted`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getIconClasses(type) {
|
|
129
|
+
switch (type) {
|
|
130
|
+
case 'success':
|
|
131
|
+
return 'text-green-600 text-lg';
|
|
132
|
+
case 'error':
|
|
133
|
+
return 'text-red-600 text-lg';
|
|
134
|
+
case 'warning':
|
|
135
|
+
return 'text-yellow-600 text-lg';
|
|
136
|
+
case 'info':
|
|
137
|
+
return 'text-blue-600 text-lg';
|
|
138
|
+
case 'loading':
|
|
139
|
+
return 'text-gray-600 text-lg';
|
|
140
|
+
default:
|
|
141
|
+
return 'text-muted-foreground text-lg';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
48
145
|
// Expose showToast globally (for demo, you may want to use provide/inject or a plugin in real apps)
|
|
49
146
|
if (typeof window !== 'undefined') {
|
|
50
147
|
window.$sonner = showToast;
|
package/src/ui/toast.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Toast utilities for the Sonner component
|
|
2
|
+
|
|
3
|
+
export interface ToastOptions {
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
icon?: string;
|
|
7
|
+
duration?: number;
|
|
8
|
+
type?: 'success' | 'error' | 'warning' | 'info' | 'loading';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const toast = {
|
|
12
|
+
success: (title: string, description?: string, duration?: number) => {
|
|
13
|
+
if (typeof window !== 'undefined' && window.$sonner) {
|
|
14
|
+
window.$sonner({ title, description, icon: '✅', duration, type: 'success' });
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
error: (title: string, description?: string, duration?: number) => {
|
|
19
|
+
if (typeof window !== 'undefined' && window.$sonner) {
|
|
20
|
+
window.$sonner({ title, description, icon: '❌', duration, type: 'error' });
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
warning: (title: string, description?: string, duration?: number) => {
|
|
25
|
+
if (typeof window !== 'undefined' && window.$sonner) {
|
|
26
|
+
window.$sonner({ title, description, icon: '⚠️', duration, type: 'warning' });
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
info: (title: string, description?: string, duration?: number) => {
|
|
31
|
+
if (typeof window !== 'undefined' && window.$sonner) {
|
|
32
|
+
window.$sonner({ title, description, icon: 'ℹ️', duration, type: 'info' });
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
loading: (title: string, description?: string, duration?: number) => {
|
|
37
|
+
if (typeof window !== 'undefined' && window.$sonner) {
|
|
38
|
+
window.$sonner({ title, description, icon: '⏳', duration, type: 'loading' });
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// Custom toast method
|
|
43
|
+
show: (options: ToastOptions) => {
|
|
44
|
+
if (typeof window !== 'undefined' && window.$sonner) {
|
|
45
|
+
window.$sonner(options);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Extend Window interface for TypeScript
|
|
51
|
+
declare global {
|
|
52
|
+
interface Window {
|
|
53
|
+
$sonner?: (options: ToastOptions) => void;
|
|
54
|
+
}
|
|
55
|
+
}
|
package/vite.config.ts
CHANGED
package/src/ui/InputOTP.vue
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, provide, h, defineAsyncComponent, useSlots } from 'vue';
|
|
3
|
-
import { cn } from './utils';
|
|
4
|
-
import { InputOTPContext, InputOTPMaskContext } from './InputOTPContext';
|
|
5
|
-
|
|
6
|
-
const props = defineProps({
|
|
7
|
-
modelValue: { type: String, default: '' },
|
|
8
|
-
maxLength: { type: Number, required: true },
|
|
9
|
-
disabled: { type: Boolean, default: false },
|
|
10
|
-
masked: { type: Boolean, default: false },
|
|
11
|
-
class: String,
|
|
12
|
-
containerClass: String,
|
|
13
|
-
});
|
|
14
|
-
const emit = defineEmits(['update:modelValue']);
|
|
15
|
-
const slots = useSlots();
|
|
16
|
-
|
|
17
|
-
const handleChange = (newValue: string) => {
|
|
18
|
-
if (newValue.length <= props.maxLength) {
|
|
19
|
-
emit('update:modelValue', newValue);
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// Try to use external input-otp package if available
|
|
24
|
-
let OTPInput: any = null;
|
|
25
|
-
try {
|
|
26
|
-
OTPInput = defineAsyncComponent(() => import('input-otp'));
|
|
27
|
-
} catch (e) {
|
|
28
|
-
OTPInput = null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (OTPInput) {
|
|
32
|
-
provide(InputOTPMaskContext, { masked: props.masked });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const contextValue = computed(() => ({
|
|
36
|
-
value: props.modelValue,
|
|
37
|
-
onChange: handleChange,
|
|
38
|
-
maxLength: props.maxLength,
|
|
39
|
-
disabled: props.disabled,
|
|
40
|
-
masked: props.masked,
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
if (!OTPInput) {
|
|
44
|
-
provide(InputOTPContext, contextValue.value);
|
|
45
|
-
provide(InputOTPMaskContext, { masked: props.masked });
|
|
46
|
-
}
|
|
47
|
-
</script>
|
|
48
|
-
<template>
|
|
49
|
-
<component
|
|
50
|
-
v-if="OTPInput"
|
|
51
|
-
:is="OTPInput"
|
|
52
|
-
data-slot="input-otp"
|
|
53
|
-
:container-class="cn('flex items-center gap-2 has-disabled:opacity-50', props.containerClass)"
|
|
54
|
-
:class="cn('disabled:cursor-not-allowed', props.class)"
|
|
55
|
-
:max-length="props.maxLength"
|
|
56
|
-
:value="props.modelValue"
|
|
57
|
-
:onChange="(val: string) => emit('update:modelValue', val)"
|
|
58
|
-
:disabled="props.disabled"
|
|
59
|
-
>
|
|
60
|
-
<slot />
|
|
61
|
-
</component>
|
|
62
|
-
<div
|
|
63
|
-
v-else
|
|
64
|
-
data-slot="input-otp"
|
|
65
|
-
:class="cn('flex items-center gap-2 has-disabled:opacity-50', props.containerClass)"
|
|
66
|
-
>
|
|
67
|
-
<slot />
|
|
68
|
-
</div>
|
|
69
|
-
</template>
|
package/src/ui/InputOTPGroup.vue
DELETED