@a4ui/core 0.30.0 → 0.31.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/index.d.ts +2 -1
- package/dist/index.js +3140 -2948
- package/dist/ui/MorphPresets.d.ts +126 -0
- package/package.json +1 -1
- package/src/index.ts +13 -1
- package/src/ui/CardSpread.tsx +8 -6
- package/src/ui/MorphPresets.tsx +218 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { JSX } from 'solid-js';
|
|
2
|
+
import { IconMorphButtonVariant } from './IconMorphButton';
|
|
3
|
+
export interface CopyButtonProps {
|
|
4
|
+
/** Text copied to the clipboard when the button is pressed. */
|
|
5
|
+
value: string;
|
|
6
|
+
/** Optional label shown next to the icon before copying. */
|
|
7
|
+
label?: JSX.Element;
|
|
8
|
+
/** Optional label shown next to the icon once copied. */
|
|
9
|
+
copiedLabel?: JSX.Element;
|
|
10
|
+
/** Visual style. Defaults to `'ghost'`. */
|
|
11
|
+
variant?: IconMorphButtonVariant;
|
|
12
|
+
class?: string;
|
|
13
|
+
'aria-label'?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Copy-to-clipboard button that morphs Copy → Check and auto-reverts after
|
|
17
|
+
* 1.5s.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <CopyButton value="npm install @a4ui/core" aria-label="Copy install command" />
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function CopyButton(props: CopyButtonProps): JSX.Element;
|
|
25
|
+
export interface PlayPauseButtonProps {
|
|
26
|
+
/** Controlled playing state. Omit to let the button manage its own state. */
|
|
27
|
+
pressed?: boolean;
|
|
28
|
+
/** Initial playing state when uncontrolled. Defaults to `false`. */
|
|
29
|
+
defaultPressed?: boolean;
|
|
30
|
+
/** Fired with the next playing state, controlled or uncontrolled. */
|
|
31
|
+
onChange?: (pressed: boolean) => void;
|
|
32
|
+
/** Optional label shown next to the icon while paused. */
|
|
33
|
+
label?: JSX.Element;
|
|
34
|
+
/** Optional label shown next to the icon while playing. */
|
|
35
|
+
activeLabel?: JSX.Element;
|
|
36
|
+
/** Visual style. Defaults to `'ghost'`. */
|
|
37
|
+
variant?: IconMorphButtonVariant;
|
|
38
|
+
class?: string;
|
|
39
|
+
'aria-label'?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Play/pause toggle button whose icon morphs Play → Pause.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* <PlayPauseButton defaultPressed={false} aria-label="Play" />
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function PlayPauseButton(props: PlayPauseButtonProps): JSX.Element;
|
|
50
|
+
export interface MuteButtonProps {
|
|
51
|
+
/** Controlled muted state. Omit to let the button manage its own state. */
|
|
52
|
+
pressed?: boolean;
|
|
53
|
+
/** Initial muted state when uncontrolled. Defaults to `false`. */
|
|
54
|
+
defaultPressed?: boolean;
|
|
55
|
+
/** Fired with the next muted state, controlled or uncontrolled. */
|
|
56
|
+
onChange?: (pressed: boolean) => void;
|
|
57
|
+
/** Optional label shown next to the icon while unmuted. */
|
|
58
|
+
label?: JSX.Element;
|
|
59
|
+
/** Optional label shown next to the icon while muted. */
|
|
60
|
+
activeLabel?: JSX.Element;
|
|
61
|
+
/** Visual style. Defaults to `'ghost'`. */
|
|
62
|
+
variant?: IconMorphButtonVariant;
|
|
63
|
+
class?: string;
|
|
64
|
+
'aria-label'?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Mute toggle button whose icon morphs Volume2 → VolumeX.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```tsx
|
|
71
|
+
* <MuteButton defaultPressed={false} aria-label="Mute" />
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function MuteButton(props: MuteButtonProps): JSX.Element;
|
|
75
|
+
export interface LockButtonProps {
|
|
76
|
+
/** Controlled locked state. Omit to let the button manage its own state. */
|
|
77
|
+
pressed?: boolean;
|
|
78
|
+
/** Initial locked state when uncontrolled. Defaults to `false`. */
|
|
79
|
+
defaultPressed?: boolean;
|
|
80
|
+
/** Fired with the next locked state, controlled or uncontrolled. */
|
|
81
|
+
onChange?: (pressed: boolean) => void;
|
|
82
|
+
/** Optional label shown next to the icon while unlocked. */
|
|
83
|
+
label?: JSX.Element;
|
|
84
|
+
/** Optional label shown next to the icon while locked. */
|
|
85
|
+
activeLabel?: JSX.Element;
|
|
86
|
+
/** Visual style. Defaults to `'ghost'`. */
|
|
87
|
+
variant?: IconMorphButtonVariant;
|
|
88
|
+
class?: string;
|
|
89
|
+
'aria-label'?: string;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Lock toggle button whose icon morphs Unlock → Lock.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```tsx
|
|
96
|
+
* <LockButton defaultPressed={false} aria-label="Lock" />
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function LockButton(props: LockButtonProps): JSX.Element;
|
|
100
|
+
export interface ThemeMorphButtonProps {
|
|
101
|
+
/** Controlled dark-mode state. Omit to let the button manage its own state. */
|
|
102
|
+
pressed?: boolean;
|
|
103
|
+
/** Initial dark-mode state when uncontrolled. Defaults to `false`. */
|
|
104
|
+
defaultPressed?: boolean;
|
|
105
|
+
/** Fired with the next dark-mode state, controlled or uncontrolled. */
|
|
106
|
+
onChange?: (pressed: boolean) => void;
|
|
107
|
+
/** Optional label shown next to the icon in light mode. */
|
|
108
|
+
label?: JSX.Element;
|
|
109
|
+
/** Optional label shown next to the icon in dark mode. */
|
|
110
|
+
activeLabel?: JSX.Element;
|
|
111
|
+
/** Visual style. Defaults to `'ghost'`. */
|
|
112
|
+
variant?: IconMorphButtonVariant;
|
|
113
|
+
class?: string;
|
|
114
|
+
'aria-label'?: string;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Purely presentational theme toggle whose icon morphs Sun → Moon. Does not
|
|
118
|
+
* touch the theme system itself — wire `pressed`/`onChange` to your theme
|
|
119
|
+
* state.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```tsx
|
|
123
|
+
* <ThemeMorphButton pressed={isDark()} onChange={setIsDark} aria-label="Toggle theme" />
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export declare function ThemeMorphButton(props: ThemeMorphButtonProps): JSX.Element;
|
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.
|
|
11
|
+
export const A4UI_VERSION = '0.31.0'
|
|
12
12
|
|
|
13
13
|
// Helpers (src/lib) — generic, framework-level utilities.
|
|
14
14
|
export { cn } from './lib/cn'
|
|
@@ -179,6 +179,18 @@ export { FocusBlurGroup, type FocusBlurGroupProps } from './ui/FocusBlurGroup'
|
|
|
179
179
|
export { CardSpread, type CardSpreadProps, type CardSpreadLayout } from './ui/CardSpread'
|
|
180
180
|
export { Carousel3D, type Carousel3DProps, type Carousel3DVariant } from './ui/Carousel3D'
|
|
181
181
|
export { TimeMachineStack, type TimeMachineStackProps } from './ui/TimeMachineStack'
|
|
182
|
+
export {
|
|
183
|
+
CopyButton,
|
|
184
|
+
type CopyButtonProps,
|
|
185
|
+
PlayPauseButton,
|
|
186
|
+
type PlayPauseButtonProps,
|
|
187
|
+
MuteButton,
|
|
188
|
+
type MuteButtonProps,
|
|
189
|
+
LockButton,
|
|
190
|
+
type LockButtonProps,
|
|
191
|
+
ThemeMorphButton,
|
|
192
|
+
type ThemeMorphButtonProps,
|
|
193
|
+
} from './ui/MorphPresets'
|
|
182
194
|
|
|
183
195
|
// Phase 2 — code, search, navigation, master-detail.
|
|
184
196
|
export { CodeTabs, type CodeTabsProps, type CodeTab } from './ui/CodeTabs'
|
package/src/ui/CardSpread.tsx
CHANGED
|
@@ -48,10 +48,12 @@ function pseudoRandom(i: number): number {
|
|
|
48
48
|
return (s - Math.floor(s)) * 2 - 1
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
function restTransform(i: number): CardTransform {
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
|
|
51
|
+
function restTransform(i: number, n: number): CardTransform {
|
|
52
|
+
// Resting deck: a gentle symmetric peek-fan (cards' edges show) so the pile
|
|
53
|
+
// reads as multiple cards even before it opens, without stealing the reveal.
|
|
54
|
+
const mid = (n - 1) / 2
|
|
55
|
+
const d = i - mid
|
|
56
|
+
return { x: d * 7, y: Math.abs(d) * 2.5, rotate: d * 2.5, origin: 'bottom center' }
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
function openTransform(layout: CardSpreadLayout, i: number, n: number, spread: number): CardTransform {
|
|
@@ -131,7 +133,7 @@ export function CardSpread(props: CardSpreadProps): JSX.Element {
|
|
|
131
133
|
|
|
132
134
|
cardRefs.forEach((card, i) => {
|
|
133
135
|
if (!card) return
|
|
134
|
-
const t = open ? openTransform(layout(), i, n, spread()) : restTransform(i)
|
|
136
|
+
const t = open ? openTransform(layout(), i, n, spread()) : restTransform(i, n)
|
|
135
137
|
card.style.transformOrigin = t.origin
|
|
136
138
|
card.style.zIndex = String(open ? i : n - i)
|
|
137
139
|
|
|
@@ -183,7 +185,7 @@ export function CardSpread(props: CardSpreadProps): JSX.Element {
|
|
|
183
185
|
<div
|
|
184
186
|
ref={(el) => (cardRefs[i()] = el)}
|
|
185
187
|
class="absolute inset-x-0 top-0 h-56 w-40 rounded-xl border border-border bg-card text-card-foreground shadow-sm will-change-transform"
|
|
186
|
-
style={{ transform: transformString(restTransform(i())) }}
|
|
188
|
+
style={{ transform: transformString(restTransform(i(), props.items.length)) }}
|
|
187
189
|
>
|
|
188
190
|
{item}
|
|
189
191
|
</div>
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// MorphPresets — thin, opinionated wrappers over IconMorphButton for common
|
|
2
|
+
// two-state icon toggles (copy, play/pause, mute, lock, theme). Each preset
|
|
3
|
+
// only supplies icons + sensible defaults; the morph animation itself lives
|
|
4
|
+
// in IconMorphButton and is not reimplemented here.
|
|
5
|
+
import type { JSX } from 'solid-js'
|
|
6
|
+
import { Check, Copy, Lock, Moon, Pause, Play, Sun, Unlock, Volume2, VolumeX } from 'lucide-solid'
|
|
7
|
+
|
|
8
|
+
import { IconMorphButton, type IconMorphButtonVariant } from './IconMorphButton'
|
|
9
|
+
|
|
10
|
+
export interface CopyButtonProps {
|
|
11
|
+
/** Text copied to the clipboard when the button is pressed. */
|
|
12
|
+
value: string
|
|
13
|
+
/** Optional label shown next to the icon before copying. */
|
|
14
|
+
label?: JSX.Element
|
|
15
|
+
/** Optional label shown next to the icon once copied. */
|
|
16
|
+
copiedLabel?: JSX.Element
|
|
17
|
+
/** Visual style. Defaults to `'ghost'`. */
|
|
18
|
+
variant?: IconMorphButtonVariant
|
|
19
|
+
class?: string
|
|
20
|
+
'aria-label'?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Copy-to-clipboard button that morphs Copy → Check and auto-reverts after
|
|
25
|
+
* 1.5s.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* <CopyButton value="npm install @a4ui/core" aria-label="Copy install command" />
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function CopyButton(props: CopyButtonProps): JSX.Element {
|
|
33
|
+
return (
|
|
34
|
+
<IconMorphButton
|
|
35
|
+
inactive={<Copy size={18} />}
|
|
36
|
+
active={<Check size={18} />}
|
|
37
|
+
label={props.label}
|
|
38
|
+
activeLabel={props.copiedLabel}
|
|
39
|
+
variant={props.variant}
|
|
40
|
+
class={props.class}
|
|
41
|
+
aria-label={props['aria-label']}
|
|
42
|
+
revertAfter={1500}
|
|
43
|
+
onChange={(pressed) => {
|
|
44
|
+
if (pressed) void navigator.clipboard?.writeText(props.value)
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface PlayPauseButtonProps {
|
|
51
|
+
/** Controlled playing state. Omit to let the button manage its own state. */
|
|
52
|
+
pressed?: boolean
|
|
53
|
+
/** Initial playing state when uncontrolled. Defaults to `false`. */
|
|
54
|
+
defaultPressed?: boolean
|
|
55
|
+
/** Fired with the next playing state, controlled or uncontrolled. */
|
|
56
|
+
onChange?: (pressed: boolean) => void
|
|
57
|
+
/** Optional label shown next to the icon while paused. */
|
|
58
|
+
label?: JSX.Element
|
|
59
|
+
/** Optional label shown next to the icon while playing. */
|
|
60
|
+
activeLabel?: JSX.Element
|
|
61
|
+
/** Visual style. Defaults to `'ghost'`. */
|
|
62
|
+
variant?: IconMorphButtonVariant
|
|
63
|
+
class?: string
|
|
64
|
+
'aria-label'?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Play/pause toggle button whose icon morphs Play → Pause.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```tsx
|
|
72
|
+
* <PlayPauseButton defaultPressed={false} aria-label="Play" />
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function PlayPauseButton(props: PlayPauseButtonProps): JSX.Element {
|
|
76
|
+
return (
|
|
77
|
+
<IconMorphButton
|
|
78
|
+
inactive={<Play size={18} />}
|
|
79
|
+
active={<Pause size={18} />}
|
|
80
|
+
pressed={props.pressed}
|
|
81
|
+
defaultPressed={props.defaultPressed}
|
|
82
|
+
onChange={props.onChange}
|
|
83
|
+
label={props.label}
|
|
84
|
+
activeLabel={props.activeLabel}
|
|
85
|
+
variant={props.variant}
|
|
86
|
+
class={props.class}
|
|
87
|
+
aria-label={props['aria-label']}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface MuteButtonProps {
|
|
93
|
+
/** Controlled muted state. Omit to let the button manage its own state. */
|
|
94
|
+
pressed?: boolean
|
|
95
|
+
/** Initial muted state when uncontrolled. Defaults to `false`. */
|
|
96
|
+
defaultPressed?: boolean
|
|
97
|
+
/** Fired with the next muted state, controlled or uncontrolled. */
|
|
98
|
+
onChange?: (pressed: boolean) => void
|
|
99
|
+
/** Optional label shown next to the icon while unmuted. */
|
|
100
|
+
label?: JSX.Element
|
|
101
|
+
/** Optional label shown next to the icon while muted. */
|
|
102
|
+
activeLabel?: JSX.Element
|
|
103
|
+
/** Visual style. Defaults to `'ghost'`. */
|
|
104
|
+
variant?: IconMorphButtonVariant
|
|
105
|
+
class?: string
|
|
106
|
+
'aria-label'?: string
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Mute toggle button whose icon morphs Volume2 → VolumeX.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```tsx
|
|
114
|
+
* <MuteButton defaultPressed={false} aria-label="Mute" />
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export function MuteButton(props: MuteButtonProps): JSX.Element {
|
|
118
|
+
return (
|
|
119
|
+
<IconMorphButton
|
|
120
|
+
inactive={<Volume2 size={18} />}
|
|
121
|
+
active={<VolumeX size={18} />}
|
|
122
|
+
pressed={props.pressed}
|
|
123
|
+
defaultPressed={props.defaultPressed}
|
|
124
|
+
onChange={props.onChange}
|
|
125
|
+
label={props.label}
|
|
126
|
+
activeLabel={props.activeLabel}
|
|
127
|
+
variant={props.variant}
|
|
128
|
+
class={props.class}
|
|
129
|
+
aria-label={props['aria-label']}
|
|
130
|
+
/>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface LockButtonProps {
|
|
135
|
+
/** Controlled locked state. Omit to let the button manage its own state. */
|
|
136
|
+
pressed?: boolean
|
|
137
|
+
/** Initial locked state when uncontrolled. Defaults to `false`. */
|
|
138
|
+
defaultPressed?: boolean
|
|
139
|
+
/** Fired with the next locked state, controlled or uncontrolled. */
|
|
140
|
+
onChange?: (pressed: boolean) => void
|
|
141
|
+
/** Optional label shown next to the icon while unlocked. */
|
|
142
|
+
label?: JSX.Element
|
|
143
|
+
/** Optional label shown next to the icon while locked. */
|
|
144
|
+
activeLabel?: JSX.Element
|
|
145
|
+
/** Visual style. Defaults to `'ghost'`. */
|
|
146
|
+
variant?: IconMorphButtonVariant
|
|
147
|
+
class?: string
|
|
148
|
+
'aria-label'?: string
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Lock toggle button whose icon morphs Unlock → Lock.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```tsx
|
|
156
|
+
* <LockButton defaultPressed={false} aria-label="Lock" />
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
export function LockButton(props: LockButtonProps): JSX.Element {
|
|
160
|
+
return (
|
|
161
|
+
<IconMorphButton
|
|
162
|
+
inactive={<Unlock size={18} />}
|
|
163
|
+
active={<Lock size={18} />}
|
|
164
|
+
pressed={props.pressed}
|
|
165
|
+
defaultPressed={props.defaultPressed}
|
|
166
|
+
onChange={props.onChange}
|
|
167
|
+
label={props.label}
|
|
168
|
+
activeLabel={props.activeLabel}
|
|
169
|
+
variant={props.variant}
|
|
170
|
+
class={props.class}
|
|
171
|
+
aria-label={props['aria-label']}
|
|
172
|
+
/>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface ThemeMorphButtonProps {
|
|
177
|
+
/** Controlled dark-mode state. Omit to let the button manage its own state. */
|
|
178
|
+
pressed?: boolean
|
|
179
|
+
/** Initial dark-mode state when uncontrolled. Defaults to `false`. */
|
|
180
|
+
defaultPressed?: boolean
|
|
181
|
+
/** Fired with the next dark-mode state, controlled or uncontrolled. */
|
|
182
|
+
onChange?: (pressed: boolean) => void
|
|
183
|
+
/** Optional label shown next to the icon in light mode. */
|
|
184
|
+
label?: JSX.Element
|
|
185
|
+
/** Optional label shown next to the icon in dark mode. */
|
|
186
|
+
activeLabel?: JSX.Element
|
|
187
|
+
/** Visual style. Defaults to `'ghost'`. */
|
|
188
|
+
variant?: IconMorphButtonVariant
|
|
189
|
+
class?: string
|
|
190
|
+
'aria-label'?: string
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Purely presentational theme toggle whose icon morphs Sun → Moon. Does not
|
|
195
|
+
* touch the theme system itself — wire `pressed`/`onChange` to your theme
|
|
196
|
+
* state.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```tsx
|
|
200
|
+
* <ThemeMorphButton pressed={isDark()} onChange={setIsDark} aria-label="Toggle theme" />
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
export function ThemeMorphButton(props: ThemeMorphButtonProps): JSX.Element {
|
|
204
|
+
return (
|
|
205
|
+
<IconMorphButton
|
|
206
|
+
inactive={<Sun size={18} />}
|
|
207
|
+
active={<Moon size={18} />}
|
|
208
|
+
pressed={props.pressed}
|
|
209
|
+
defaultPressed={props.defaultPressed}
|
|
210
|
+
onChange={props.onChange}
|
|
211
|
+
label={props.label}
|
|
212
|
+
activeLabel={props.activeLabel}
|
|
213
|
+
variant={props.variant}
|
|
214
|
+
class={props.class}
|
|
215
|
+
aria-label={props['aria-label']}
|
|
216
|
+
/>
|
|
217
|
+
)
|
|
218
|
+
}
|