@a4ui/core 0.13.0 → 0.13.1
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/elements.css +3 -0
- package/dist/full.css +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1778 -1755
- package/dist/ui/NotificationStack.d.ts +7 -7
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/ui/NotificationStack.tsx +101 -66
|
@@ -8,23 +8,23 @@ export interface StackNotification {
|
|
|
8
8
|
export interface NotificationStackProps {
|
|
9
9
|
/** Newest first. The parent owns this array. */
|
|
10
10
|
items: StackNotification[];
|
|
11
|
-
/** How many cards
|
|
11
|
+
/** How many cards peek when collapsed. @default 3 */
|
|
12
12
|
max?: number;
|
|
13
13
|
/** Fired when a card is dismissed; the parent should drop it from `items`. */
|
|
14
14
|
onDismiss?: (id: string | number) => void;
|
|
15
15
|
class?: string;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* Stacked notifications.
|
|
19
|
-
* so they peek from the bottom edge;
|
|
20
|
-
*
|
|
21
|
-
* in via `animateIn`.
|
|
18
|
+
* Stacked notifications. Collapsed, cards behind the front one are offset down
|
|
19
|
+
* and scaled so they peek from the bottom edge; click the stack to expand every
|
|
20
|
+
* notification into a scrollable list, and "Show less" to collapse. Removing the
|
|
21
|
+
* front card promotes the rest; new cards fade/slide in via `animateIn`.
|
|
22
22
|
*
|
|
23
23
|
* @example
|
|
24
24
|
* ```tsx
|
|
25
|
-
* const [items, setItems] =
|
|
25
|
+
* const [items, setItems] = createSignal<StackNotification[]>([])
|
|
26
26
|
* <NotificationStack
|
|
27
|
-
* items={items}
|
|
27
|
+
* items={items()}
|
|
28
28
|
* onDismiss={(id) => setItems((xs) => xs.filter((x) => x.id !== id))}
|
|
29
29
|
* />
|
|
30
30
|
* ```
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// import '@a4ui/core/styles.css'
|
|
9
9
|
// import { Button, Card, Modal } from '@a4ui/core'
|
|
10
10
|
|
|
11
|
-
export const A4UI_VERSION = '0.13.
|
|
11
|
+
export const A4UI_VERSION = '0.13.1'
|
|
12
12
|
|
|
13
13
|
// Helpers (src/lib) — generic, framework-level utilities.
|
|
14
14
|
export { cn } from './lib/cn'
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
// A stacked-notifications widget (phone lock-screen style): the
|
|
2
|
-
// full-size on top and older
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
1
|
+
// A stacked-notifications widget (phone lock-screen style): collapsed, the
|
|
2
|
+
// newest card is full-size on top and older ones peek out behind it, scaled
|
|
3
|
+
// down and offset. Click the stack (or "Show all") to EXPAND it into a full,
|
|
4
|
+
// scrollable list; "Show less" collapses it back. Presentational + controlled —
|
|
5
|
+
// the parent owns `items` (newest first) and removes a card on `onDismiss`.
|
|
6
|
+
import { createSignal, For, Show, type JSX } from 'solid-js'
|
|
6
7
|
|
|
7
8
|
import { cn } from '../lib/cn'
|
|
8
9
|
import { animateIn, motionReduced } from '../lib/motion'
|
|
@@ -17,7 +18,7 @@ export interface StackNotification {
|
|
|
17
18
|
export interface NotificationStackProps {
|
|
18
19
|
/** Newest first. The parent owns this array. */
|
|
19
20
|
items: StackNotification[]
|
|
20
|
-
/** How many cards
|
|
21
|
+
/** How many cards peek when collapsed. @default 3 */
|
|
21
22
|
max?: number
|
|
22
23
|
/** Fired when a card is dismissed; the parent should drop it from `items`. */
|
|
23
24
|
onDismiss?: (id: string | number) => void
|
|
@@ -25,80 +26,114 @@ export interface NotificationStackProps {
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
|
-
* Stacked notifications.
|
|
29
|
-
* so they peek from the bottom edge;
|
|
30
|
-
*
|
|
31
|
-
* in via `animateIn`.
|
|
29
|
+
* Stacked notifications. Collapsed, cards behind the front one are offset down
|
|
30
|
+
* and scaled so they peek from the bottom edge; click the stack to expand every
|
|
31
|
+
* notification into a scrollable list, and "Show less" to collapse. Removing the
|
|
32
|
+
* front card promotes the rest; new cards fade/slide in via `animateIn`.
|
|
32
33
|
*
|
|
33
34
|
* @example
|
|
34
35
|
* ```tsx
|
|
35
|
-
* const [items, setItems] =
|
|
36
|
+
* const [items, setItems] = createSignal<StackNotification[]>([])
|
|
36
37
|
* <NotificationStack
|
|
37
|
-
* items={items}
|
|
38
|
+
* items={items()}
|
|
38
39
|
* onDismiss={(id) => setItems((xs) => xs.filter((x) => x.id !== id))}
|
|
39
40
|
* />
|
|
40
41
|
* ```
|
|
41
42
|
*/
|
|
42
43
|
export function NotificationStack(props: NotificationStackProps): JSX.Element {
|
|
43
44
|
const max = () => props.max ?? 3
|
|
45
|
+
const [expanded, setExpanded] = createSignal(false)
|
|
46
|
+
const canExpand = () => props.items.length > 1
|
|
44
47
|
|
|
45
48
|
return (
|
|
46
|
-
<div
|
|
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
|
-
|
|
49
|
+
<div role="log" aria-live="polite" class={cn('w-full max-w-sm', props.class)}>
|
|
50
|
+
<div
|
|
51
|
+
class={cn(
|
|
52
|
+
expanded()
|
|
53
|
+
? 'max-h-[60vh] space-y-2 overflow-y-auto pr-1'
|
|
54
|
+
: cn('relative', canExpand() && 'cursor-pointer'),
|
|
55
|
+
)}
|
|
56
|
+
style={
|
|
57
|
+
expanded()
|
|
58
|
+
? undefined
|
|
59
|
+
: // reserve room for the front card plus the peek of those behind it
|
|
60
|
+
{ 'min-height': `${88 + (Math.min(props.items.length, max()) - 1) * 10}px` }
|
|
61
|
+
}
|
|
62
|
+
onClick={() => {
|
|
63
|
+
if (!expanded() && canExpand()) setExpanded(true)
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<For each={props.items}>
|
|
67
|
+
{(item, i) => {
|
|
68
|
+
const collapsedHidden = () => !expanded() && i() > max() - 1
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
ref={(el) => animateIn(el, { y: -8 })}
|
|
72
|
+
class={cn(
|
|
73
|
+
'card rounded-xl border border-border bg-card p-3 shadow-lg',
|
|
74
|
+
expanded() ? 'relative' : 'absolute inset-x-0 top-0',
|
|
75
|
+
!motionReduced() && !expanded() && 'transition-[transform,opacity] duration-300 ease-out',
|
|
76
|
+
)}
|
|
77
|
+
style={
|
|
78
|
+
expanded()
|
|
79
|
+
? undefined
|
|
80
|
+
: {
|
|
81
|
+
transform: `translateY(${i() * 10}px) scale(${1 - i() * 0.05})`,
|
|
82
|
+
opacity: collapsedHidden() ? 0 : 1 - i() * 0.15,
|
|
83
|
+
'z-index': props.items.length - i(),
|
|
84
|
+
'pointer-events': collapsedHidden() ? 'none' : 'auto',
|
|
85
|
+
'transform-origin': 'top center',
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
>
|
|
89
|
+
<div class="flex items-start gap-2.5">
|
|
90
|
+
{item.icon && <span class="mt-0.5 shrink-0 text-primary">{item.icon}</span>}
|
|
91
|
+
<div class="min-w-0 flex-1">
|
|
92
|
+
<p class={cn('text-sm font-medium text-foreground', !expanded() && 'truncate')}>
|
|
93
|
+
{item.title}
|
|
94
|
+
</p>
|
|
95
|
+
{item.description && (
|
|
96
|
+
<p class="mt-0.5 line-clamp-2 text-sm text-muted-foreground">{item.description}</p>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
aria-label={`Dismiss ${item.title}`}
|
|
102
|
+
onClick={(e) => {
|
|
103
|
+
e.stopPropagation()
|
|
104
|
+
props.onDismiss?.(item.id)
|
|
105
|
+
}}
|
|
106
|
+
class="shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
93
107
|
>
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
<svg
|
|
109
|
+
viewBox="0 0 24 24"
|
|
110
|
+
class="h-3.5 w-3.5"
|
|
111
|
+
fill="none"
|
|
112
|
+
stroke="currentColor"
|
|
113
|
+
stroke-width="2.5"
|
|
114
|
+
stroke-linecap="round"
|
|
115
|
+
aria-hidden="true"
|
|
116
|
+
>
|
|
117
|
+
<path d="M6 6l12 12M18 6L6 18" />
|
|
118
|
+
</svg>
|
|
119
|
+
</button>
|
|
120
|
+
</div>
|
|
97
121
|
</div>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
</
|
|
122
|
+
)
|
|
123
|
+
}}
|
|
124
|
+
</For>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<Show when={canExpand()}>
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
aria-expanded={expanded()}
|
|
131
|
+
onClick={() => setExpanded((v) => !v)}
|
|
132
|
+
class="mt-2 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground"
|
|
133
|
+
>
|
|
134
|
+
{expanded() ? 'Show less' : `Show all ${props.items.length}`}
|
|
135
|
+
</button>
|
|
136
|
+
</Show>
|
|
102
137
|
</div>
|
|
103
138
|
)
|
|
104
139
|
}
|