@rimelight/ui 0.0.40 → 0.0.41
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/checkbox/checkbox.theme.ts +1 -1
- package/src/components/header/header.theme.ts +2 -2
- package/src/components/navigation-menu/navigation-menu.theme.ts +1 -1
- package/src/components/popover/RLAPopover.astro +170 -127
- package/src/components/popover/popover.theme.ts +3 -1
- package/src/components/popover/popover.ts +5 -1
- package/src/components/table-of-contents/table-of-contents.theme.ts +1 -1
- package/src/components/tabs/tabs.ts +7 -7
- package/src/components/toaster/RLAToaster.astro +88 -0
- package/src/icons/defaults.ts +4 -1
- package/src/presets/index.ts +3 -3
- package/src/stores/toast.ts +24 -0
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@ export const createCheckboxTheme = (colors: readonly string[] = colorConfig.colo
|
|
|
21
21
|
color,
|
|
22
22
|
{
|
|
23
23
|
root: "rl-color-" + color,
|
|
24
|
-
box: `peer-checked:bg-[var(--rl-bg)] peer-checked:border-[var(--rl-border)] focus-within:ring-[var(--rl-ring)]/50`
|
|
24
|
+
box: `peer-checked:bg-[var(--rl-bg)] peer-checked:border-[var(--rl-border)] focus-within:ring-[var(--rl-ring)]/50 peer-checked:[&_[data-slot=indicator]]:scale-100`
|
|
25
25
|
}
|
|
26
26
|
])
|
|
27
27
|
),
|
|
@@ -8,9 +8,9 @@ export const createHeaderTheme = () =>
|
|
|
8
8
|
content: "w-full",
|
|
9
9
|
container:
|
|
10
10
|
"flex flex-row items-center justify-between gap-4xs h-16 md:h-18 px-xs sm:px-md md:px-xl lg:px-xl",
|
|
11
|
-
left: "flex-1 min-w-0 flex items-center justify-start
|
|
11
|
+
left: "flex-1 min-w-0 flex items-center justify-start",
|
|
12
12
|
center: "flex-none flex items-center justify-center mx-4",
|
|
13
|
-
right: "flex-1 min-w-0 flex items-center justify-end
|
|
13
|
+
right: "flex-1 min-w-0 flex items-center justify-end"
|
|
14
14
|
},
|
|
15
15
|
variants: {
|
|
16
16
|
contain: {
|
|
@@ -14,7 +14,7 @@ export const createNavigationMenuTheme = () =>
|
|
|
14
14
|
"absolute left-0 top-full z-50 mt-1.5 w-56 rounded-md border bg-popover p-1 text-popover-foreground shadow-md origin-top-left",
|
|
15
15
|
viewportWrapper: "absolute left-0 top-full flex justify-center w-full",
|
|
16
16
|
viewport:
|
|
17
|
-
"relative mt-1.5 h-auto w-full origin-
|
|
17
|
+
"relative mt-1.5 h-auto w-full origin-top overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90"
|
|
18
18
|
},
|
|
19
19
|
variants: {
|
|
20
20
|
theme: {
|
|
@@ -1,130 +1,173 @@
|
|
|
1
|
-
---
|
|
2
|
-
import { scv } from "css-variants"
|
|
1
|
+
---
|
|
2
|
+
import { scv } from "css-variants"
|
|
3
3
|
import { useUi } from "../../utils/useUi"
|
|
4
|
-
import { resolveClasses } from "../../utils/resolveClasses"
|
|
5
|
-
import type { PopoverProps } from "./popover"
|
|
4
|
+
import { resolveClasses } from "../../utils/resolveClasses"
|
|
5
|
+
import type { PopoverProps } from "./popover"
|
|
6
6
|
import { resolveTheme } from "../../utils/resolveTheme"
|
|
7
7
|
import popoverThemeDefault, { createPopoverTheme } from "./popover.theme"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
ui: uiProp,
|
|
17
|
-
class: className,
|
|
18
|
-
...rest
|
|
19
|
-
} = Astro.props as PopoverProps
|
|
20
|
-
|
|
21
|
-
const classes = resolveClasses(popover, {
|
|
22
|
-
placement
|
|
23
|
-
}, useUi("popover", uiProp), className)
|
|
24
|
-
---
|
|
25
|
-
<div class={classes.root} data-slot="popover-container" data-placement={placement} {...rest}>
|
|
26
|
-
<div class={`${classes.trigger} popover-trigger`} data-slot="trigger">
|
|
27
|
-
<slot name="trigger">
|
|
28
|
-
{triggerLabel && <button type="button" class="cursor-pointer">{triggerLabel}</button>}
|
|
29
|
-
</slot>
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
<div class={`${classes.content} popover-content hidden`} data-slot="content">
|
|
33
|
-
<slot />
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
|
-
<script>
|
|
38
|
-
function
|
|
39
|
-
document.querySelectorAll('
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
content.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
8
|
+
|
|
9
|
+
const popoverTheme = resolveTheme(popoverThemeDefault, createPopoverTheme)
|
|
10
|
+
const popover = scv(popoverTheme)
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
triggerLabel,
|
|
14
|
+
placement = "bottom",
|
|
15
|
+
mode = "both",
|
|
16
|
+
ui: uiProp,
|
|
17
|
+
class: className,
|
|
18
|
+
...rest
|
|
19
|
+
} = Astro.props as PopoverProps
|
|
20
|
+
|
|
21
|
+
const classes = resolveClasses(popover, {
|
|
22
|
+
placement
|
|
23
|
+
}, useUi("popover", uiProp), className)
|
|
24
|
+
---
|
|
25
|
+
<div class={`group/popover ${classes.root}`} data-slot="popover-container" data-placement={placement} data-mode={mode} {...rest}>
|
|
26
|
+
<div class={`${classes.trigger} popover-trigger`} data-slot="trigger">
|
|
27
|
+
<slot name="trigger">
|
|
28
|
+
{triggerLabel && <button type="button" class="cursor-pointer">{triggerLabel}</button>}
|
|
29
|
+
</slot>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class={`${classes.content} popover-content hidden`} data-slot="content">
|
|
33
|
+
<slot />
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<script>
|
|
38
|
+
function openPopover(container: Element, content: HTMLElement) {
|
|
39
|
+
document.querySelectorAll('.popover-content').forEach(other => {
|
|
40
|
+
if (other !== content && !other.classList.contains('hidden')) {
|
|
41
|
+
other.classList.add('hidden');
|
|
42
|
+
(other as HTMLElement).style.transform = '';
|
|
43
|
+
other.dispatchEvent(new CustomEvent('popover-toggle', { detail: { open: false } }));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
content.classList.remove('hidden');
|
|
48
|
+
content.style.transform = '';
|
|
49
|
+
|
|
50
|
+
const rect = content.getBoundingClientRect();
|
|
51
|
+
const viewportWidth = window.innerWidth;
|
|
52
|
+
let shift = 0;
|
|
53
|
+
const safetyMargin = 16;
|
|
54
|
+
if (rect.right > viewportWidth - safetyMargin) {
|
|
55
|
+
shift = (viewportWidth - safetyMargin) - rect.right;
|
|
56
|
+
} else if (rect.left < safetyMargin) {
|
|
57
|
+
shift = safetyMargin - rect.left;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const placement = container.getAttribute('data-placement') || 'bottom';
|
|
61
|
+
const isCentered = (placement === 'top' || placement === 'bottom') &&
|
|
62
|
+
!content.classList.contains('right-0') &&
|
|
63
|
+
!content.classList.contains('left-0');
|
|
64
|
+
|
|
65
|
+
if (shift !== 0) {
|
|
66
|
+
if (isCentered) {
|
|
67
|
+
content.style.transform = `translateX(calc(-50% + ${shift}px))`;
|
|
68
|
+
} else {
|
|
69
|
+
content.style.transform = `translateX(${shift}px)`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
content.dispatchEvent(new CustomEvent('popover-toggle', { detail: { open: true } }));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function closePopover(content: HTMLElement) {
|
|
77
|
+
if (!content.classList.contains('hidden')) {
|
|
78
|
+
content.classList.add('hidden');
|
|
79
|
+
content.style.transform = '';
|
|
80
|
+
content.dispatchEvent(new CustomEvent('popover-toggle', { detail: { open: false } }));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function setupPopoverListeners() {
|
|
85
|
+
const win = window as any;
|
|
86
|
+
if (win.hasGlobalPopoverListeners) return;
|
|
87
|
+
win.hasGlobalPopoverListeners = true;
|
|
88
|
+
|
|
89
|
+
let hoverTimeout: any = null;
|
|
90
|
+
|
|
91
|
+
document.addEventListener('mouseover', (e) => {
|
|
92
|
+
const target = e.target as HTMLElement | null;
|
|
93
|
+
if (!target) return;
|
|
94
|
+
|
|
95
|
+
const container = target.closest('[data-slot="popover-container"]') as HTMLElement | null;
|
|
96
|
+
if (container) {
|
|
97
|
+
const mode = container.getAttribute('data-mode') || 'both';
|
|
98
|
+
if (mode === 'hover' || mode === 'both') {
|
|
99
|
+
if (hoverTimeout) clearTimeout(hoverTimeout);
|
|
100
|
+
const content = container.querySelector('.popover-content') as HTMLElement | null;
|
|
101
|
+
if (content && content.classList.contains('hidden')) {
|
|
102
|
+
openPopover(container, content);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
document.addEventListener('mouseout', (e) => {
|
|
109
|
+
const target = e.target as HTMLElement | null;
|
|
110
|
+
if (!target) return;
|
|
111
|
+
|
|
112
|
+
const container = target.closest('[data-slot="popover-container"]') as HTMLElement | null;
|
|
113
|
+
if (container) {
|
|
114
|
+
const mode = container.getAttribute('data-mode') || 'both';
|
|
115
|
+
if (mode === 'hover' || mode === 'both') {
|
|
116
|
+
const related = e.relatedTarget as HTMLElement | null;
|
|
117
|
+
if (related && container.contains(related)) return;
|
|
118
|
+
|
|
119
|
+
const content = container.querySelector('.popover-content') as HTMLElement | null;
|
|
120
|
+
if (content && !content.classList.contains('hidden')) {
|
|
121
|
+
hoverTimeout = setTimeout(() => {
|
|
122
|
+
closePopover(content);
|
|
123
|
+
}, 150);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
document.addEventListener('click', (e) => {
|
|
130
|
+
const target = e.target as HTMLElement | null;
|
|
131
|
+
if (!target) return;
|
|
132
|
+
|
|
133
|
+
const trigger = target.closest('.popover-trigger') as HTMLElement | null;
|
|
134
|
+
if (trigger) {
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
const container = trigger.closest('[data-slot="popover-container"]');
|
|
137
|
+
if (!container) return;
|
|
138
|
+
|
|
139
|
+
const content = container.querySelector('.popover-content') as HTMLElement | null;
|
|
140
|
+
if (!content) return;
|
|
141
|
+
|
|
142
|
+
const isOpen = !content.classList.contains('hidden');
|
|
143
|
+
if (isOpen) {
|
|
144
|
+
closePopover(content);
|
|
145
|
+
} else {
|
|
146
|
+
openPopover(container, content);
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (target.closest('.popover-content')) {
|
|
152
|
+
const isActionLink = target.closest('a') || target.closest('button[data-close-popover]');
|
|
153
|
+
if (isActionLink) {
|
|
154
|
+
document.querySelectorAll('.popover-content').forEach(other => {
|
|
155
|
+
closePopover(other as HTMLElement);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
document.querySelectorAll('.popover-content').forEach(other => {
|
|
162
|
+
closePopover(other as HTMLElement);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (document.readyState === 'loading') {
|
|
168
|
+
document.addEventListener('DOMContentLoaded', setupPopoverListeners);
|
|
169
|
+
} else {
|
|
170
|
+
setupPopoverListeners();
|
|
171
|
+
}
|
|
172
|
+
document.addEventListener('astro:page-load', setupPopoverListeners);
|
|
173
|
+
</script>
|
|
@@ -8,13 +8,15 @@ export const createPopoverTheme = (_colors: readonly string[] = colorConfig.colo
|
|
|
8
8
|
root: "relative inline-block",
|
|
9
9
|
trigger: "",
|
|
10
10
|
content: [
|
|
11
|
-
"absolute z-50 w-72 rounded-xl border border-border bg-
|
|
11
|
+
"absolute z-50 w-72 rounded-xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-4 text-neutral-900 dark:text-neutral-100 shadow-xl outline-none"
|
|
12
12
|
]
|
|
13
13
|
},
|
|
14
14
|
variants: {
|
|
15
15
|
placement: {
|
|
16
16
|
top: { content: "bottom-full left-1/2 -translate-x-1/2 mb-2" },
|
|
17
17
|
bottom: { content: "top-full left-1/2 -translate-x-1/2 mt-2" },
|
|
18
|
+
"bottom-end": { content: "top-full right-0 left-auto mt-2" },
|
|
19
|
+
"bottom-start": { content: "top-full left-0 right-auto mt-2" },
|
|
18
20
|
left: { content: "right-full top-1/2 -translate-y-1/2 mr-2" },
|
|
19
21
|
right: { content: "left-full top-1/2 -translate-y-1/2 ml-2" }
|
|
20
22
|
}
|
|
@@ -10,7 +10,11 @@ export interface PopoverProps {
|
|
|
10
10
|
/**
|
|
11
11
|
* The placement of the popover relative to trigger.
|
|
12
12
|
*/
|
|
13
|
-
placement?: "top" | "bottom" | "left" | "right"
|
|
13
|
+
placement?: "top" | "bottom" | "bottom-end" | "bottom-start" | "left" | "right"
|
|
14
|
+
/**
|
|
15
|
+
* Trigger mode: 'click', 'hover', or 'both'.
|
|
16
|
+
*/
|
|
17
|
+
mode?: "click" | "hover" | "both"
|
|
14
18
|
/**
|
|
15
19
|
* Slot-specific CSS class overrides.
|
|
16
20
|
*/
|
|
@@ -35,7 +35,7 @@ export const createTableOfContentsTheme = () =>
|
|
|
35
35
|
trailingIcon:
|
|
36
36
|
"size-5 transform transition-transform duration-200 shrink-0 group-data-[state=open]:rotate-180 lg:hidden",
|
|
37
37
|
content:
|
|
38
|
-
"relative data-[state=open]:animate-
|
|
38
|
+
"relative data-[state=open]:animate-fade-in data-[state=closed]:animate-fade-out overflow-hidden focus:outline-none",
|
|
39
39
|
list: "min-w-0",
|
|
40
40
|
listWithChildren: "ms-3",
|
|
41
41
|
item: "min-w-0",
|
|
@@ -29,19 +29,19 @@ export interface TabsProps<T extends TabItem = TabItem> {
|
|
|
29
29
|
/**
|
|
30
30
|
* Tab variant.
|
|
31
31
|
*
|
|
32
|
-
* @
|
|
32
|
+
* @default "pill"
|
|
33
33
|
*/
|
|
34
34
|
variant?: "pill" | "underline" | "link" | "solid" | "pills"
|
|
35
35
|
/**
|
|
36
36
|
* Layout orientation.
|
|
37
37
|
*
|
|
38
|
-
* @
|
|
38
|
+
* @default "horizontal"
|
|
39
39
|
*/
|
|
40
40
|
orientation?: "horizontal" | "vertical"
|
|
41
41
|
/**
|
|
42
42
|
* Size scale.
|
|
43
43
|
*
|
|
44
|
-
* @
|
|
44
|
+
* @default "md"
|
|
45
45
|
*/
|
|
46
46
|
size?: "xs" | "sm" | "md" | "lg" | "xl"
|
|
47
47
|
/**
|
|
@@ -51,26 +51,26 @@ export interface TabsProps<T extends TabItem = TabItem> {
|
|
|
51
51
|
/**
|
|
52
52
|
* Whether to render content panels automatically. Set to false for tab bar only.
|
|
53
53
|
*
|
|
54
|
-
* @
|
|
54
|
+
* @default true
|
|
55
55
|
*/
|
|
56
56
|
content?: boolean
|
|
57
57
|
/**
|
|
58
58
|
* Unmount hidden tab panels from DOM. Defaults to true. If false, panel is hidden via
|
|
59
59
|
* CSS/attribute.
|
|
60
60
|
*
|
|
61
|
-
* @
|
|
61
|
+
* @default true
|
|
62
62
|
*/
|
|
63
63
|
unmountOnHide?: boolean
|
|
64
64
|
/**
|
|
65
65
|
* Property key for value on item object.
|
|
66
66
|
*
|
|
67
|
-
* @
|
|
67
|
+
* @default "value"
|
|
68
68
|
*/
|
|
69
69
|
valueKey?: string
|
|
70
70
|
/**
|
|
71
71
|
* Property key for label on item object.
|
|
72
72
|
*
|
|
73
|
-
* @
|
|
73
|
+
* @default "label"
|
|
74
74
|
*/
|
|
75
75
|
labelKey?: string
|
|
76
76
|
/**
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
---
|
|
3
|
+
|
|
4
|
+
<div id="rla-toaster-container" class="fixed bottom-5 right-5 z-[200] flex flex-col gap-2 max-w-sm w-full pointer-events-none"></div>
|
|
5
|
+
|
|
6
|
+
<script>
|
|
7
|
+
import { toasts, removeToast, addToast } from "../../stores/toast"
|
|
8
|
+
|
|
9
|
+
function checkPendingToasts() {
|
|
10
|
+
const stored = sessionStorage.getItem("pending_toast")
|
|
11
|
+
if (stored) {
|
|
12
|
+
sessionStorage.removeItem("pending_toast")
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(stored)
|
|
15
|
+
if (parsed && parsed.title) {
|
|
16
|
+
addToast(parsed)
|
|
17
|
+
}
|
|
18
|
+
} catch (err) {}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const url = new URL(window.location.href)
|
|
22
|
+
let toastTitle = url.searchParams.get("toast_title")
|
|
23
|
+
let toastDesc = url.searchParams.get("toast_desc") ?? undefined
|
|
24
|
+
let toastVariant = (url.searchParams.get("toast_variant") as any) || "success"
|
|
25
|
+
|
|
26
|
+
if (!toastTitle) {
|
|
27
|
+
if (url.searchParams.has("signed_in")) {
|
|
28
|
+
toastTitle = "Signed in successfully"
|
|
29
|
+
toastDesc = "Welcome back!"
|
|
30
|
+
} else if (url.searchParams.has("new")) {
|
|
31
|
+
toastTitle = "Account created successfully"
|
|
32
|
+
toastDesc = "Please sign in to continue."
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (toastTitle) {
|
|
37
|
+
url.searchParams.delete("toast_title")
|
|
38
|
+
url.searchParams.delete("toast_desc")
|
|
39
|
+
url.searchParams.delete("toast_variant")
|
|
40
|
+
url.searchParams.delete("signed_in")
|
|
41
|
+
url.searchParams.delete("new")
|
|
42
|
+
const cleanUrl = url.pathname + (url.search ? url.search : "")
|
|
43
|
+
window.history.replaceState({}, "", cleanUrl)
|
|
44
|
+
|
|
45
|
+
const payload: any = {
|
|
46
|
+
title: toastTitle,
|
|
47
|
+
variant: toastVariant
|
|
48
|
+
}
|
|
49
|
+
if (toastDesc) payload.description = toastDesc
|
|
50
|
+
|
|
51
|
+
addToast(payload)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
checkPendingToasts()
|
|
56
|
+
document.addEventListener("astro:page-load", checkPendingToasts)
|
|
57
|
+
|
|
58
|
+
const container = document.getElementById("rla-toaster-container")
|
|
59
|
+
|
|
60
|
+
toasts.subscribe((list) => {
|
|
61
|
+
if (!container) return
|
|
62
|
+
container.innerHTML = ""
|
|
63
|
+
list.forEach((t) => {
|
|
64
|
+
const el = document.createElement("div")
|
|
65
|
+
const isError = t.variant === "error"
|
|
66
|
+
const bgClass = isError
|
|
67
|
+
? "bg-error-900 text-error-100 border-error-700"
|
|
68
|
+
: "bg-neutral-900 text-white border-neutral-800 dark:bg-neutral-800"
|
|
69
|
+
const iconSvg = isError
|
|
70
|
+
? `<svg class="size-4 shrink-0 text-error-400 mt-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6M9 9l6 6"/></svg>`
|
|
71
|
+
: `<svg class="size-4 shrink-0 text-success-400 mt-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>`
|
|
72
|
+
|
|
73
|
+
el.className = `pointer-events-auto flex items-start gap-3 p-4 rounded-lg border shadow-xl transition-all duration-300 ${bgClass}`
|
|
74
|
+
el.innerHTML = `
|
|
75
|
+
${iconSvg}
|
|
76
|
+
<div class="flex flex-col gap-0.5 flex-1 min-w-0">
|
|
77
|
+
<div class="font-semibold text-sm">${t.title}</div>
|
|
78
|
+
${t.description ? `<div class="text-xs opacity-90">${t.description}</div>` : ""}
|
|
79
|
+
</div>
|
|
80
|
+
<button type="button" class="text-neutral-400 hover:text-white p-0.5 rounded focus:outline-none" aria-label="Close">
|
|
81
|
+
<svg class="size-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>
|
|
82
|
+
</button>
|
|
83
|
+
`
|
|
84
|
+
el.querySelector("button")?.addEventListener("click", () => removeToast(t.id))
|
|
85
|
+
container.appendChild(el)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
</script>
|
package/src/icons/defaults.ts
CHANGED
|
@@ -34,7 +34,10 @@ export const DEFAULT_ICONS = {
|
|
|
34
34
|
sun: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" width="100%" height="100%"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></svg>`,
|
|
35
35
|
uploadCloud: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" width="100%" height="100%"><path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242M12 12v9M9 15l3-3 3 3"/></svg>`,
|
|
36
36
|
user: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" width="100%" height="100%"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`,
|
|
37
|
-
star: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" width="100%" height="100%"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg
|
|
37
|
+
star: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" width="100%" height="100%"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>`,
|
|
38
|
+
lock: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" width="100%" height="100%"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`,
|
|
39
|
+
eye: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" width="100%" height="100%"><path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0z"/><circle cx="12" cy="12" r="3"/></svg>`,
|
|
40
|
+
eyeOff: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" width="100%" height="100%"><path d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49"/><path d="M14.084 14.158a3 3 0 0 1-4.242-4.242"/><path d="M17.479 17.499A10.75 10.75 0 0 1 2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.225-4.22"/><line x1="2" x2="22" y1="2" y2="22"/></svg>`
|
|
38
41
|
} as const
|
|
39
42
|
|
|
40
43
|
export type RimelightIcons = Partial<Record<keyof typeof DEFAULT_ICONS, string>> &
|
package/src/presets/index.ts
CHANGED
|
@@ -358,10 +358,10 @@ export default definePreset((options?: RimelightPresetOptions) => {
|
|
|
358
358
|
animation: {
|
|
359
359
|
keyframes: {
|
|
360
360
|
"accordion-down":
|
|
361
|
-
"
|
|
361
|
+
"from { height: 0; } to { height: var(--starwind-accordion-content-height); }",
|
|
362
362
|
"accordion-up":
|
|
363
|
-
"
|
|
364
|
-
"marquee": "
|
|
363
|
+
"from { height: var(--starwind-accordion-content-height); } to { height: 0; }",
|
|
364
|
+
"marquee": "from { transform: translateX(0); } to { transform: translateX(-100%); }"
|
|
365
365
|
},
|
|
366
366
|
durations: {
|
|
367
367
|
"accordion-down": "0.2s",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { atom } from "nanostores"
|
|
2
|
+
|
|
3
|
+
export interface ToastItem {
|
|
4
|
+
id: string
|
|
5
|
+
title: string
|
|
6
|
+
description?: string
|
|
7
|
+
variant?: "success" | "error" | "warning" | "info" | "default"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const toasts = atom<ToastItem[]>([])
|
|
11
|
+
|
|
12
|
+
export function addToast(toast: Omit<ToastItem, "id">) {
|
|
13
|
+
const id = Math.random().toString(36).slice(2, 9)
|
|
14
|
+
const item: ToastItem = { id, ...toast }
|
|
15
|
+
toasts.set([...toasts.get(), item])
|
|
16
|
+
|
|
17
|
+
setTimeout(() => {
|
|
18
|
+
removeToast(id)
|
|
19
|
+
}, 4500)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function removeToast(id: string) {
|
|
23
|
+
toasts.set(toasts.get().filter((t) => t.id !== id))
|
|
24
|
+
}
|