alouette 20.0.0 → 20.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alouette",
3
- "version": "20.0.0",
3
+ "version": "20.0.1",
4
4
  "description": "A modern, customizable design system built on top of NativeWind v5 with configurable defaults",
5
5
  "keywords": [
6
6
  "tanstack-intent"
@@ -19,7 +19,8 @@
19
19
  "files": [
20
20
  "src",
21
21
  "dist",
22
- "metro.cjs"
22
+ "metro.cjs",
23
+ "skills"
23
24
  ],
24
25
  "type": "module",
25
26
  "sideEffects": false,
@@ -47,6 +48,10 @@
47
48
  "import": "./dist/index-browser.es.js"
48
49
  }
49
50
  },
51
+ "./global.css": {
52
+ "style": "./src/global.css",
53
+ "default": "./src/global.css"
54
+ },
50
55
  "./metro.cjs": "./metro.cjs"
51
56
  },
52
57
  "dependencies": {
@@ -135,7 +140,8 @@
135
140
  }
136
141
  ],
137
142
  "extraEntries": [
138
- "metro.cjs"
143
+ "metro.cjs",
144
+ "global.css"
139
145
  ],
140
146
  "jsx": true,
141
147
  "typescript": true
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: alouette-actions
3
+ description: >
4
+ Buttons (Button, IconButton, ExternalLinkButton, InternalLinkButton) and
5
+ pressable surfaces (PressableBox, PressableListItem). variant is
6
+ contained|outlined|ghost, size is sm|md, accent defaults to brand. Button
7
+ label is the required text prop (not children); IconButton
8
+ requires aria-label. Interactive hover/focus/active/disabled states are built
9
+ in. Load when adding buttons or custom pressable elements.
10
+ type: core
11
+ library: alouette
12
+ library_version: "20.0.0"
13
+ requires:
14
+ - alouette-theming
15
+ sources:
16
+ - "christophehurpeau/alouette:packages/alouette/src/ui/actions/Button.tsx"
17
+ - "christophehurpeau/alouette:packages/alouette/src/ui/actions/IconButton.tsx"
18
+ - "christophehurpeau/alouette:packages/alouette/src/ui/data/PressableBox.tsx"
19
+ - "christophehurpeau/alouette:packages/alouette/src/ui/data/PressableListItem.tsx"
20
+ ---
21
+
22
+ This skill builds on alouette-theming. Read it first for the accent model.
23
+
24
+ # alouette — Actions
25
+
26
+ Buttons and pressables carry interactive token states (hover/focus/active/
27
+ disabled) automatically. `variant` is `"contained" | "outlined" | "ghost"`;
28
+ `size` is `"sm" | "md"`; `accent` defaults to `"brand"`.
29
+
30
+ ## Setup
31
+
32
+ ```tsx
33
+ import { Button } from "alouette";
34
+ import { CheckRegularIcon } from "alouette-icons/phosphor-icons/CheckRegularIcon";
35
+
36
+ <Button text="Save" icon={<CheckRegularIcon />} onPress={save} />;
37
+ ```
38
+
39
+ ## Core Patterns
40
+
41
+ ### Button variants and accents
42
+
43
+ ```tsx
44
+ <Button text="Save" /> {/* contained, brand */}
45
+ <Button variant="outlined" text="Cancel" />
46
+ <Button variant="ghost" text="Dismiss" />
47
+ <Button accent="danger" text="Delete" />
48
+ <Button size="sm" text="Small" />
49
+ ```
50
+
51
+ ### Icon-only button
52
+
53
+ ```tsx
54
+ import { IconButton } from "alouette";
55
+ import { XRegularIcon } from "alouette-icons/phosphor-icons/XRegularIcon";
56
+
57
+ <IconButton icon={<XRegularIcon />} aria-label="Close" onPress={close} />;
58
+ ```
59
+
60
+ `size` is `"sm" | "md"` or a number (custom diameter in px); `iconSize="fill"`
61
+ makes the icon take 80% of the button.
62
+
63
+ ### Pressable surfaces
64
+
65
+ `PressableBox` is a themed, pressable container (`variant`, `accent`,
66
+ `forceStyle`). `PressableListItem` is a row with a trailing caret.
67
+
68
+ ```tsx
69
+ import { PressableBox, PressableListItem, Text } from "alouette";
70
+
71
+ <PressableBox onPress={open}>
72
+ <Text>Custom card</Text>
73
+ </PressableBox>
74
+
75
+ <PressableListItem onPress={open}>
76
+ <Text>Row label</Text>
77
+ </PressableListItem>;
78
+ ```
79
+
80
+ ### Link buttons
81
+
82
+ ```tsx
83
+ import { ExternalLinkButton, InternalLinkButton } from "alouette";
84
+
85
+ <ExternalLinkButton href="https://example.com" text="Open" />
86
+ <InternalLinkButton href="/settings" text="Settings" />
87
+ ```
88
+
89
+ For full external-link control (in-app browser vs new tab), see
90
+ alouette-external-links/SKILL.md.
91
+
92
+ ## Common Mistakes
93
+
94
+ ### HIGH Passing the button label as children instead of text
95
+
96
+ Wrong:
97
+
98
+ ```tsx
99
+ <Button>Save</Button>
100
+ ```
101
+
102
+ Correct:
103
+
104
+ ```tsx
105
+ <Button text="Save" />
106
+ ```
107
+
108
+ `Button` renders its label from the required `text` prop (plus an optional `icon`
109
+ prop); children are ignored, so `<Button>Save</Button>` shows no label.
110
+
111
+ Source: packages/alouette/src/ui/actions/Button.tsx
112
+
113
+ ### HIGH Using non-existent variant names
114
+
115
+ Wrong:
116
+
117
+ ```tsx
118
+ <Button variant="ghost-contained" text="Cancel" />
119
+ <Button variant="primary" text="Save" />
120
+ ```
121
+
122
+ Correct:
123
+
124
+ ```tsx
125
+ <Button variant="ghost" text="Cancel" />
126
+ <Button accent="brand" text="Save" />
127
+ ```
128
+
129
+ `variant` is only `"contained" | "outlined" | "ghost"`; the accent is chosen via
130
+ the `accent` prop. (`ghost` is a variant value, not a separate boolean prop.)
131
+
132
+ Source: packages/alouette/src/ui/data/PressableBox.tsx, ui/actions/Button.tsx
133
+
134
+ ### MEDIUM IconButton without aria-label
135
+
136
+ Wrong:
137
+
138
+ ```tsx
139
+ <IconButton icon={<XRegularIcon />} onPress={close} />
140
+ ```
141
+
142
+ Correct:
143
+
144
+ ```tsx
145
+ <IconButton icon={<XRegularIcon />} aria-label="Close" onPress={close} />
146
+ ```
147
+
148
+ `IconButton` types `aria-label` as required because it has no text label;
149
+ omitting it is an accessibility failure and a type error.
150
+
151
+ Source: packages/alouette/src/ui/actions/IconButton.tsx
@@ -0,0 +1,214 @@
1
+ ---
2
+ name: alouette-animation
3
+ description: >
4
+ Animate alouette UI two ways: NativeWind transitions (transition-*, duration-*,
5
+ ease-* on state changes like active:/hover:/focus:) and CSS keyframe presence
6
+ animations via PresenceOne / PresenceList (animate-slide-in/out,
7
+ animate-collapse-in/out). exitDurationMs must come from animationDurationsMs so
8
+ the unmount timer matches the keyframe. Both run on native via
9
+ react-native-reanimated. Load when adding transitions or enter/exit animations.
10
+ type: core
11
+ library: alouette
12
+ library_version: "20.0.0"
13
+ sources:
14
+ - "christophehurpeau/alouette:packages/alouette/src/ui/containers/Presence.tsx"
15
+ - "christophehurpeau/alouette:packages/alouette/src/animationDurationsMs.ts"
16
+ - "christophehurpeau/alouette:packages/alouette/src/ui/containers/Presence.stories.tsx"
17
+ ---
18
+
19
+ # alouette — Animation
20
+
21
+ alouette animates with plain CSS — no animation library in your code — and on
22
+ native NativeWind v5 runs it through react-native-reanimated. There are two
23
+ mechanisms:
24
+
25
+ - **Transitions** — `transition-*` + `duration-*` + `ease-*` smooth a property
26
+ change driven by state (`active:`, `hover:`, `focus:`, theme/accent change).
27
+ - **Presence (keyframes)** — `PresenceOne` / `PresenceList` play enter/exit
28
+ keyframe animations on mount/unmount (`animate-slide-in/out`,
29
+ `animate-collapse-in/out`), with durations from `animationDurationsMs`.
30
+
31
+ ## Transitions (state changes)
32
+
33
+ State-change transitions (`transition-*` + `duration-*` + `ease-*`) are already
34
+ built into alouette's interactive components. Use `InteractiveBox` (or
35
+ `PressableBox`, `Button`, `IconButton`) — they animate press/hover/focus
36
+ coherently (e.g. press scale plus background/border color) with the system's
37
+ duration and easing. Don't reach for a raw react-native `Pressable`.
38
+
39
+ ```tsx
40
+ import { InteractiveBox, Text } from "alouette";
41
+
42
+ <InteractiveBox onPress={open} className="bg-surface rounded-sm p-m">
43
+ <Text>Animates on press / hover / focus</Text>
44
+ </InteractiveBox>;
45
+ ```
46
+
47
+ For a custom transition, add `transition-*` utilities on an alouette component
48
+ that forwards `className` so it composes with the built-in ones:
49
+
50
+ ```tsx
51
+ <InteractiveBox className="transition-[background-color] duration-200 ease-in hover:bg-lowered" />
52
+ ```
53
+
54
+ ## Presence: swap a single child
55
+
56
+ ```tsx
57
+ import { PresenceOne, Box, Text, animationDurationsMs } from "alouette";
58
+
59
+ function Swap({ step }: { step: number }) {
60
+ return (
61
+ <Box className="relative h-24 w-64">
62
+ <PresenceOne
63
+ activeKey={step}
64
+ exitDurationMs={animationDurationsMs.slide}
65
+ enterClassName="animate-slide-in"
66
+ exitClassName="animate-slide-out"
67
+ className="absolute inset-0 flex-center bg-surface rounded-md"
68
+ >
69
+ <Box>
70
+ <Text className="font-heading-bold text-2xl">Step {step}</Text>
71
+ </Box>
72
+ </PresenceOne>
73
+ </Box>
74
+ );
75
+ }
76
+ ```
77
+
78
+ ## Core Patterns
79
+
80
+ ### Animated list (add / remove)
81
+
82
+ Each child must have a stable `key`. Adding a key animates that item in; removing
83
+ one animates only that item out before unmounting.
84
+
85
+ ```tsx
86
+ import { PresenceList, InfoMessage, animationDurationsMs } from "alouette";
87
+
88
+ <PresenceList
89
+ exitDurationMs={animationDurationsMs.collapse}
90
+ enterClassName="animate-collapse-in"
91
+ exitClassName="animate-collapse-out"
92
+ className="overflow-hidden"
93
+ >
94
+ {items.map((item) => (
95
+ <InfoMessage
96
+ key={item.id}
97
+ onDismiss={() => remove(item.id)}
98
+ dismissIconAriaLabel="Dismiss"
99
+ >
100
+ {item.label}
101
+ </InfoMessage>
102
+ ))}
103
+ </PresenceList>;
104
+ ```
105
+
106
+ `PresenceOne` swaps a single child and merges the animation classes onto it via
107
+ `cloneElement` (no wrapper); `PresenceList` wraps each child in its own `View`.
108
+
109
+ ## Common Mistakes
110
+
111
+ ### HIGH Hand-rolling transitions on a raw Pressable
112
+
113
+ Wrong:
114
+
115
+ ```tsx
116
+ import { Pressable } from "react-native";
117
+ <Pressable className="transition-[transform] duration-200 active:scale-[0.975]">
118
+ {children}
119
+ </Pressable>
120
+ ```
121
+
122
+ Correct:
123
+
124
+ ```tsx
125
+ import { InteractiveBox } from "alouette";
126
+ <InteractiveBox onPress={open}>{children}</InteractiveBox>
127
+ ```
128
+
129
+ alouette's interactive components already bundle press/hover/focus transitions
130
+ with consistent duration and easing, plus disabled and focus-visible handling.
131
+ Re-implementing them on a bare `Pressable` drifts from the system and misses
132
+ that behavior.
133
+
134
+ Source: packages/alouette/src/ui/containers/Box.tsx (InteractiveBox); ui/data/PressableBox.tsx
135
+
136
+ ### HIGH Hardcoding exitDurationMs instead of animationDurationsMs
137
+
138
+ Wrong:
139
+
140
+ ```tsx
141
+ <PresenceOne activeKey={id} exitDurationMs={600}
142
+ enterClassName="animate-slide-in" exitClassName="animate-slide-out">
143
+ ```
144
+
145
+ Correct:
146
+
147
+ ```tsx
148
+ import { animationDurationsMs } from "alouette";
149
+ <PresenceOne activeKey={id} exitDurationMs={animationDurationsMs.slide}
150
+ enterClassName="animate-slide-in" exitClassName="animate-slide-out">
151
+ ```
152
+
153
+ The unmount timer must equal the CSS keyframe duration. A hardcoded number drifts
154
+ from the framework value, cutting the exit animation short or leaving ghost nodes
155
+ mounted.
156
+
157
+ Source: packages/alouette/src/animationDurationsMs.ts; ui/containers/Presence.stories.tsx
158
+
159
+ ### HIGH PresenceList children without stable keys
160
+
161
+ Wrong:
162
+
163
+ ```tsx
164
+ {items.map((it, i) => <InfoMessage key={i}>{it}</InfoMessage>)}
165
+ ```
166
+
167
+ Correct:
168
+
169
+ ```tsx
170
+ {items.map((it) => <InfoMessage key={it.id}>{it.label}</InfoMessage>)}
171
+ ```
172
+
173
+ `PresenceList` diffs children by key to play add/remove animations. Index keys
174
+ make removed items jump or skip their exit animation.
175
+
176
+ Source: packages/alouette/src/ui/containers/Presence.tsx (usePresenceList)
177
+
178
+ ### MEDIUM PresenceOne child that doesn't forward className
179
+
180
+ Wrong:
181
+
182
+ ```tsx
183
+ <PresenceOne activeKey={id} exitDurationMs={animationDurationsMs.slide}>
184
+ <>{content}</>
185
+ </PresenceOne>
186
+ ```
187
+
188
+ Correct:
189
+
190
+ ```tsx
191
+ <PresenceOne activeKey={id} exitDurationMs={animationDurationsMs.slide}
192
+ enterClassName="animate-slide-in" exitClassName="animate-slide-out">
193
+ <Box>{content}</Box>
194
+ </PresenceOne>
195
+ ```
196
+
197
+ `PresenceOne` merges the animation classes onto the child via `cloneElement`. A
198
+ Fragment or a component that drops `className` gets no animation; use an alouette
199
+ component (or one that forwards `className` to its root view).
200
+
201
+ Source: packages/alouette/src/ui/containers/Presence.tsx (PresenceOne)
202
+
203
+ ### HIGH Expecting animations to run on native without reanimated
204
+
205
+ Wrong: relying on the CSS animation while react-native-reanimated (and the
206
+ `react-native-worklets/plugin` babel plugin) are not installed.
207
+
208
+ Correct: install `react-native-reanimated` and add the worklets babel plugin, so
209
+ NativeWind can run CSS transitions/animations on device.
210
+
211
+ NativeWind v5 drives native animations through reanimated; without it, animations
212
+ silently no-op on iOS/Android while still working on web.
213
+
214
+ Source: packages/storybook-native-app/babel.config.js; packages/alouette/package.json (peerDependencies)
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: alouette-external-links
3
+ description: >
4
+ Open external URLs with ExternalLink (wraps expo-web-browser / Linking) or
5
+ ExternalLinkButton, configuring openLinkBehavior per platform (native:
6
+ linking|webBrowser; web: targetBlank|targetSelf). Load when linking out to
7
+ external URLs from alouette UI.
8
+ type: composition
9
+ library: alouette
10
+ library_version: "20.0.0"
11
+ requires:
12
+ - alouette-actions
13
+ sources:
14
+ - "christophehurpeau/alouette:packages/alouette/src/expo/ExternalLink.tsx"
15
+ - "christophehurpeau/alouette:packages/alouette/src/ui/actions/Button.tsx"
16
+ ---
17
+
18
+ This skill builds on alouette-actions. Read it first for button props.
19
+
20
+ # alouette + expo-web-browser — External links
21
+
22
+ `ExternalLink` centralizes outbound-URL behavior per platform: an in-app browser
23
+ sheet (expo-web-browser) or `Linking` on native, and a new tab or same tab on
24
+ web. `ExternalLinkButton` is the button shortcut for the common case.
25
+
26
+ `ExternalOpenLinkBehavior`:
27
+ `{ native: "linking" | "webBrowser"; web: "targetBlank" | "targetSelf" }`.
28
+
29
+ ## Setup
30
+
31
+ ```tsx
32
+ import { ExternalLinkButton } from "alouette";
33
+
34
+ <ExternalLinkButton href="https://example.com" text="Open docs" />;
35
+ ```
36
+
37
+ ## Core Patterns
38
+
39
+ ### Wrap any component with explicit per-platform behavior
40
+
41
+ `ExternalLink` takes the target component via `as` and forwards remaining props.
42
+
43
+ ```tsx
44
+ import { ExternalLink, Button } from "alouette";
45
+
46
+ <ExternalLink
47
+ as={Button}
48
+ href="https://example.com"
49
+ openLinkBehavior={{ native: "webBrowser", web: "targetBlank" }}
50
+ text="Open"
51
+ />;
52
+ ```
53
+
54
+ - `native: "webBrowser"` opens an in-app browser sheet themed with alouette colors.
55
+ - `native: "linking"` hands off to the OS browser via `Linking.openURL`.
56
+ - `web: "targetBlank"` opens a new tab; `"targetSelf"` navigates in place.
57
+
58
+ ## Common Mistakes
59
+
60
+ ### MEDIUM Calling Linking.openURL directly
61
+
62
+ Wrong:
63
+
64
+ ```tsx
65
+ import { Linking } from "react-native";
66
+ <Pressable onPress={() => Linking.openURL(href)} />
67
+ ```
68
+
69
+ Correct:
70
+
71
+ ```tsx
72
+ <ExternalLink
73
+ as={Button}
74
+ href={href}
75
+ openLinkBehavior={{ native: "webBrowser", web: "targetBlank" }}
76
+ text="Open"
77
+ />
78
+ ```
79
+
80
+ `ExternalLink` handles the in-app themed browser sheet on native and the correct
81
+ target behavior on web; calling `Linking` directly loses both.
82
+
83
+ Source: packages/alouette/src/expo/ExternalLink.tsx
84
+
85
+ ### MEDIUM Omitting openLinkBehavior
86
+
87
+ Wrong:
88
+
89
+ ```tsx
90
+ <ExternalLink as={Button} href={href} text="Open" />
91
+ ```
92
+
93
+ Correct:
94
+
95
+ ```tsx
96
+ <ExternalLink
97
+ as={Button}
98
+ href={href}
99
+ openLinkBehavior={{ native: "linking", web: "targetSelf" }}
100
+ text="Open"
101
+ />
102
+ ```
103
+
104
+ `openLinkBehavior` is required and selects the native and web strategies; without
105
+ it the link can't decide how to open and throws on an unsupported branch.
106
+ (`ExternalLinkButton` defaults this for you — use it for the simple case.)
107
+
108
+ Source: packages/alouette/src/expo/ExternalLink.tsx
@@ -0,0 +1,133 @@
1
+ ---
2
+ name: alouette-feedback
3
+ description: >
4
+ Semantic message banners: Message (requires accent + icon) and the presets
5
+ InfoMessage, ConfirmationMessage, WarningMessage. Optional dismiss requires
6
+ onDismiss and dismissIconAriaLabel together; size is sm/md/lg. Load when
7
+ showing inline status, alerts, or dismissible notices.
8
+ type: core
9
+ library: alouette
10
+ library_version: "20.0.0"
11
+ requires:
12
+ - alouette-theming
13
+ sources:
14
+ - "christophehurpeau/alouette:packages/alouette/src/ui/feedback/Message.tsx"
15
+ - "christophehurpeau/alouette:packages/alouette/src/ui/feedback/Message.stories.tsx"
16
+ ---
17
+
18
+ This skill builds on alouette-theming. Read it first for the accent model.
19
+
20
+ # alouette — Feedback messages
21
+
22
+ `Message` is an accent-themed banner with an icon and optional dismiss button.
23
+ Prefer the presets `InfoMessage` / `ConfirmationMessage` / `WarningMessage`,
24
+ which set the accent and icon for you.
25
+
26
+ ## Setup
27
+
28
+ ```tsx
29
+ import { InfoMessage } from "alouette";
30
+
31
+ <InfoMessage>Your changes were saved.</InfoMessage>;
32
+ ```
33
+
34
+ ## Core Patterns
35
+
36
+ ### Presets
37
+
38
+ ```tsx
39
+ import { InfoMessage, ConfirmationMessage, WarningMessage } from "alouette";
40
+
41
+ <InfoMessage>Heads up.</InfoMessage> {/* accent="info" */}
42
+ <ConfirmationMessage>Saved.</ConfirmationMessage> {/* accent="success" */}
43
+ <WarningMessage>Careful.</WarningMessage> {/* accent="warning" */}
44
+ ```
45
+
46
+ ### Dismissible message
47
+
48
+ `onDismiss` and `dismissIconAriaLabel` must be provided together:
49
+
50
+ ```tsx
51
+ <WarningMessage onDismiss={hide} dismissIconAriaLabel="Dismiss">
52
+ Low disk space.
53
+ </WarningMessage>
54
+ ```
55
+
56
+ ### Custom Message (other accents / icons)
57
+
58
+ For accents without a preset (e.g. `danger`), use the base `Message` with an
59
+ explicit `accent` and `icon`. `size` is `"sm" | "md" | "lg"`.
60
+
61
+ ```tsx
62
+ import { Message } from "alouette";
63
+ import { XCircleRegularIcon } from "alouette-icons/phosphor-icons/XCircleRegularIcon";
64
+
65
+ <Message accent="danger" icon={<XCircleRegularIcon />} size="lg">
66
+ Payment failed.
67
+ </Message>;
68
+ ```
69
+
70
+ ## Common Mistakes
71
+
72
+ ### MEDIUM onDismiss without dismissIconAriaLabel
73
+
74
+ Wrong:
75
+
76
+ ```tsx
77
+ <InfoMessage onDismiss={hide}>Saved</InfoMessage>
78
+ ```
79
+
80
+ Correct:
81
+
82
+ ```tsx
83
+ <InfoMessage onDismiss={hide} dismissIconAriaLabel="Dismiss">
84
+ Saved
85
+ </InfoMessage>
86
+ ```
87
+
88
+ `Message` types `onDismiss` and `dismissIconAriaLabel` as a required pair;
89
+ providing one without the other is a type error and leaves the dismiss button
90
+ without an accessible label.
91
+
92
+ Source: packages/alouette/src/ui/feedback/Message.tsx
93
+
94
+ ### MEDIUM Rendering the base Message without accent/icon
95
+
96
+ Wrong:
97
+
98
+ ```tsx
99
+ <Message>Heads up</Message>
100
+ ```
101
+
102
+ Correct:
103
+
104
+ ```tsx
105
+ <WarningMessage>Heads up</WarningMessage>
106
+ ```
107
+
108
+ The base `Message` requires both `accent` and `icon`. For the common cases use a
109
+ preset, which sets them.
110
+
111
+ Source: packages/alouette/src/ui/feedback/Message.tsx
112
+
113
+ ### MEDIUM Hand-building a colored banner instead of Message
114
+
115
+ Wrong:
116
+
117
+ ```tsx
118
+ <Box className="bg-yellow-100"><Text>Warning</Text></Box>
119
+ ```
120
+
121
+ Correct:
122
+
123
+ ```tsx
124
+ <WarningMessage>Warning</WarningMessage>
125
+ ```
126
+
127
+ A manual banner loses accent theming, the leading icon, dark-mode support and
128
+ the dismiss a11y that `Message` provides.
129
+
130
+ Source: packages/alouette/src/ui/feedback/Message.tsx
131
+
132
+ See also: alouette-animation/SKILL.md — render messages in PresenceList for
133
+ animated add/remove.