@tanishqkancharla/maui 0.0.57 → 0.0.59
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/components/Button.js +32 -23
- package/package.json +1 -1
- package/skills/maui/references/components/buttons.md +1 -1
- package/src/components/Button.tsx +37 -37
- package/src/pages/ButtonsPage.tsx +3 -2
- package/src/pages/Maui.tsx +6 -3
- package/src/pages/ReadmePage.tsx +83 -0
- package/src/pages/AboutPage.tsx +0 -19
|
@@ -20,6 +20,7 @@ const disabledRaised = {
|
|
|
20
20
|
opacity: 1,
|
|
21
21
|
color: colors.gray[8],
|
|
22
22
|
backgroundColor: backgroundColor.element,
|
|
23
|
+
backgroundImage: "none",
|
|
23
24
|
boxShadow: buttonShadow,
|
|
24
25
|
};
|
|
25
26
|
const disabledQuiet = {
|
|
@@ -141,40 +142,48 @@ function darkerFill(color) {
|
|
|
141
142
|
function onSolidText(color) {
|
|
142
143
|
return `oklch(from ${color} clamp(0.2, (0.75 - l) * 100, 0.99) 0 0)`;
|
|
143
144
|
}
|
|
145
|
+
/** Darker shade of a fill (OKLCH lightness offset) at an alpha. */
|
|
146
|
+
function fillShade(color, lightnessOffset, alpha = 1) {
|
|
147
|
+
return `oklch(from ${color} calc(l - ${lightnessOffset}) c h / ${alpha})`;
|
|
148
|
+
}
|
|
149
|
+
/** Fill-tinted edge and lift; no neutral gray, so the shadow matches any fill. */
|
|
144
150
|
function primaryButtonShadow(color) {
|
|
145
|
-
return `inset 0 1px #ffffff18, inset 0 0 1px .5px #fff2, 0
|
|
151
|
+
return `inset 0 1px #ffffff18, inset 0 0 1px .5px #fff2, 0 0 0 .5px ${fillShade(color, 0.1)}, 0 1px 2px ${fillShade(color, 0, 0.35)}, 0 3px 8px -3px ${fillShade(color, 0, 0.6)}`;
|
|
146
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Diagonal from a slightly darker, more saturated, warmer-rotated fill to the
|
|
155
|
+
* fill. The offsets are the OKLCH delta between Campsite's #FB432C → #FF591E.
|
|
156
|
+
*/
|
|
157
|
+
function primaryButtonGradient(color) {
|
|
158
|
+
return `linear-gradient(to top left, oklch(from ${color} calc(l - 0.027) calc(c * 1.05) calc(h - 6.8)), ${color})`;
|
|
159
|
+
}
|
|
160
|
+
/** Hover lightens the top of the gradient instead of replacing the fill. */
|
|
161
|
+
const primaryHoverSheen = `linear-gradient(to bottom, rgb(255 255 255 / 0.12), rgb(255 255 255 / 0))`;
|
|
162
|
+
const primaryClass = memoize((fill, textColor) => {
|
|
163
|
+
const primaryShadow = primaryButtonShadow(fill);
|
|
164
|
+
const gradient = primaryButtonGradient(fill);
|
|
165
|
+
return style(buttonBaseClass, focusRing("&:focus-visible", primaryShadow), {
|
|
166
|
+
color: textColor,
|
|
167
|
+
backgroundColor: fill,
|
|
168
|
+
backgroundImage: gradient,
|
|
169
|
+
boxShadow: primaryShadow,
|
|
170
|
+
"&:hover:not(:disabled)": {
|
|
171
|
+
backgroundImage: `${primaryHoverSheen}, ${gradient}`,
|
|
172
|
+
},
|
|
173
|
+
"&:disabled": disabledRaised,
|
|
174
|
+
});
|
|
175
|
+
});
|
|
147
176
|
const coloredButtonClass = memoize((variant, color) => {
|
|
148
177
|
if (isCssColor(color)) {
|
|
149
178
|
const fill = opaqueColor(color);
|
|
150
179
|
if (variant === "primary") {
|
|
151
|
-
|
|
152
|
-
const primaryShadow = primaryButtonShadow(fill);
|
|
153
|
-
return style(buttonBaseClass, focusRing("&:focus-visible", primaryShadow), {
|
|
154
|
-
color: onSolidText(fill),
|
|
155
|
-
backgroundColor: fill,
|
|
156
|
-
boxShadow: primaryShadow,
|
|
157
|
-
"&:hover:not(:disabled)": {
|
|
158
|
-
backgroundColor: hover,
|
|
159
|
-
},
|
|
160
|
-
"&:disabled": disabledRaised,
|
|
161
|
-
});
|
|
180
|
+
return primaryClass(fill, onSolidText(fill));
|
|
162
181
|
}
|
|
163
182
|
return quietClass(fill, darkerFill(fill), fill);
|
|
164
183
|
}
|
|
165
184
|
const scale = paletteFill(color);
|
|
166
185
|
if (variant === "primary") {
|
|
167
|
-
|
|
168
|
-
const hover = surfaceWash(colors.gray[12], surfaceMixPercent.hover, scale[9]);
|
|
169
|
-
return style(buttonBaseClass, focusRing("&:focus-visible", primaryShadow), {
|
|
170
|
-
color: darkTextOnSolid.has(color) ? scale[12] : "white",
|
|
171
|
-
backgroundColor: scale[9],
|
|
172
|
-
boxShadow: primaryShadow,
|
|
173
|
-
"&:hover:not(:disabled)": {
|
|
174
|
-
backgroundColor: hover,
|
|
175
|
-
},
|
|
176
|
-
"&:disabled": disabledRaised,
|
|
177
|
-
});
|
|
186
|
+
return primaryClass(scale[9], darkTextOnSolid.has(color) ? scale[12] : "white");
|
|
178
187
|
}
|
|
179
188
|
return quietClass(scale[11], scale[12], paletteAlpha(color)[9]);
|
|
180
189
|
});
|
package/package.json
CHANGED
|
@@ -30,7 +30,7 @@ Styled React Aria `Button` (default `type="button"`). Consumes `ButtonContext`,
|
|
|
30
30
|
| `onClick` | Still supported. React Aria also exposes `onPress` |
|
|
31
31
|
| `children` | Text is wrapped for cap-height trim; SVG icons sit beside text. Icon-only needs `aria-label` |
|
|
32
32
|
|
|
33
|
-
Primary: fill step 9,
|
|
33
|
+
Primary: fill step 9, light text (`onAccent`, or step 12 on amber/lime/mint/sky/yellow). Custom CSS fill drops alpha; text is white or near-black from lightness. The fill is a diagonal gradient (`to top left`) from the fill shifted by OKLCH `l − 0.027`, `c × 1.05`, `h − 6.8` to the fill itself. The shadow keeps the white inset highlight and tints a 0.5px edge (`l − 0.1`) and a soft lift from the fill — no neutral gray. Hover layers a white-to-transparent sheen over the gradient; disabled drops the gradient. Quiet: no fill; `variantColor` tints the label/icon. Hover/press mix `grayAlpha[9]` (or that color’s alpha 9, or the hex) into transparent at 6% light / 9% dark; press is 2×. Hover fills snap.
|
|
34
34
|
|
|
35
35
|
While a menu or overlay is open, the trigger keeps the active fill via RAC `data-pressed` / `aria-expanded` (same tokens as `:active`: `elementActive`, or primary step 10).
|
|
36
36
|
|
|
@@ -31,6 +31,7 @@ const disabledRaised = {
|
|
|
31
31
|
opacity: 1,
|
|
32
32
|
color: colors.gray[8],
|
|
33
33
|
backgroundColor: backgroundColor.element,
|
|
34
|
+
backgroundImage: "none",
|
|
34
35
|
boxShadow: buttonShadow,
|
|
35
36
|
} as const
|
|
36
37
|
|
|
@@ -187,34 +188,48 @@ function onSolidText(color: string) {
|
|
|
187
188
|
return `oklch(from ${color} clamp(0.2, (0.75 - l) * 100, 0.99) 0 0)`
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
/** Darker shade of a fill (OKLCH lightness offset) at an alpha. */
|
|
192
|
+
function fillShade(color: string, lightnessOffset: number, alpha = 1) {
|
|
193
|
+
return `oklch(from ${color} calc(l - ${lightnessOffset}) c h / ${alpha})`
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Fill-tinted edge and lift; no neutral gray, so the shadow matches any fill. */
|
|
190
197
|
function primaryButtonShadow(color: string) {
|
|
191
|
-
return `inset 0 1px #ffffff18, inset 0 0 1px .5px #fff2, 0
|
|
198
|
+
return `inset 0 1px #ffffff18, inset 0 0 1px .5px #fff2, 0 0 0 .5px ${fillShade(color, 0.1)}, 0 1px 2px ${fillShade(color, 0, 0.35)}, 0 3px 8px -3px ${fillShade(color, 0, 0.6)}`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Diagonal from a slightly darker, more saturated, warmer-rotated fill to the
|
|
203
|
+
* fill. The offsets are the OKLCH delta between Campsite's #FB432C → #FF591E.
|
|
204
|
+
*/
|
|
205
|
+
function primaryButtonGradient(color: string) {
|
|
206
|
+
return `linear-gradient(to top left, oklch(from ${color} calc(l - 0.027) calc(c * 1.05) calc(h - 6.8)), ${color})`
|
|
192
207
|
}
|
|
193
208
|
|
|
209
|
+
/** Hover lightens the top of the gradient instead of replacing the fill. */
|
|
210
|
+
const primaryHoverSheen = `linear-gradient(to bottom, rgb(255 255 255 / 0.12), rgb(255 255 255 / 0))`
|
|
211
|
+
|
|
212
|
+
const primaryClass = memoize((fill: string, textColor: string) => {
|
|
213
|
+
const primaryShadow = primaryButtonShadow(fill)
|
|
214
|
+
const gradient = primaryButtonGradient(fill)
|
|
215
|
+
return style(buttonBaseClass, focusRing("&:focus-visible", primaryShadow), {
|
|
216
|
+
color: textColor,
|
|
217
|
+
backgroundColor: fill,
|
|
218
|
+
backgroundImage: gradient,
|
|
219
|
+
boxShadow: primaryShadow,
|
|
220
|
+
"&:hover:not(:disabled)": {
|
|
221
|
+
backgroundImage: `${primaryHoverSheen}, ${gradient}`,
|
|
222
|
+
},
|
|
223
|
+
"&:disabled": disabledRaised,
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
|
|
194
227
|
const coloredButtonClass = memoize(
|
|
195
228
|
(variant: "primary" | "quiet", color: ButtonVariantColor) => {
|
|
196
229
|
if (isCssColor(color)) {
|
|
197
230
|
const fill = opaqueColor(color)
|
|
198
231
|
if (variant === "primary") {
|
|
199
|
-
|
|
200
|
-
colors.gray[12],
|
|
201
|
-
surfaceMixPercent.hover,
|
|
202
|
-
fill,
|
|
203
|
-
)
|
|
204
|
-
const primaryShadow = primaryButtonShadow(fill)
|
|
205
|
-
return style(
|
|
206
|
-
buttonBaseClass,
|
|
207
|
-
focusRing("&:focus-visible", primaryShadow),
|
|
208
|
-
{
|
|
209
|
-
color: onSolidText(fill),
|
|
210
|
-
backgroundColor: fill,
|
|
211
|
-
boxShadow: primaryShadow,
|
|
212
|
-
"&:hover:not(:disabled)": {
|
|
213
|
-
backgroundColor: hover,
|
|
214
|
-
},
|
|
215
|
-
"&:disabled": disabledRaised,
|
|
216
|
-
},
|
|
217
|
-
)
|
|
232
|
+
return primaryClass(fill, onSolidText(fill))
|
|
218
233
|
}
|
|
219
234
|
|
|
220
235
|
return quietClass(fill, darkerFill(fill), fill)
|
|
@@ -222,24 +237,9 @@ const coloredButtonClass = memoize(
|
|
|
222
237
|
|
|
223
238
|
const scale = paletteFill(color)
|
|
224
239
|
if (variant === "primary") {
|
|
225
|
-
|
|
226
|
-
const hover = surfaceWash(
|
|
227
|
-
colors.gray[12],
|
|
228
|
-
surfaceMixPercent.hover,
|
|
240
|
+
return primaryClass(
|
|
229
241
|
scale[9],
|
|
230
|
-
|
|
231
|
-
return style(
|
|
232
|
-
buttonBaseClass,
|
|
233
|
-
focusRing("&:focus-visible", primaryShadow),
|
|
234
|
-
{
|
|
235
|
-
color: darkTextOnSolid.has(color) ? scale[12] : "white",
|
|
236
|
-
backgroundColor: scale[9],
|
|
237
|
-
boxShadow: primaryShadow,
|
|
238
|
-
"&:hover:not(:disabled)": {
|
|
239
|
-
backgroundColor: hover,
|
|
240
|
-
},
|
|
241
|
-
"&:disabled": disabledRaised,
|
|
242
|
-
},
|
|
242
|
+
darkTextOnSolid.has(color) ? scale[12] : "white",
|
|
243
243
|
)
|
|
244
244
|
}
|
|
245
245
|
|
|
@@ -85,8 +85,9 @@ export function ButtonsPage() {
|
|
|
85
85
|
<Code>variant="primary"</Code> fills with step 9 of{" "}
|
|
86
86
|
<Code>variantColor</Code> (a palette name, default <Code>"accent"</Code>
|
|
87
87
|
). Pass a hex or <Code>rgb()</Code> string to use that color as the fill
|
|
88
|
-
(alpha is dropped).
|
|
89
|
-
|
|
88
|
+
(alpha is dropped). The fill is a subtle diagonal gradient from a
|
|
89
|
+
slightly darker, warmer shade of that color, and the edge and lift
|
|
90
|
+
shadows are tinted from it. Hover adds a white sheen at the top.
|
|
90
91
|
</P>
|
|
91
92
|
<Flex row alignItems="center" gap={4} style={{ flexWrap: "wrap" }}>
|
|
92
93
|
<Button variant="primary">Save</Button>
|
package/src/pages/Maui.tsx
CHANGED
|
@@ -27,7 +27,7 @@ import { type ThemePreference, useTheme } from "../theme/ThemeContext"
|
|
|
27
27
|
import { isScalePreference, useScale } from "../theme/ScaleContext"
|
|
28
28
|
import { LARGE_SCALE } from "../theme/dataScale"
|
|
29
29
|
import { galleryCompactMedia } from "./galleryCompact"
|
|
30
|
-
import {
|
|
30
|
+
import { ReadmePage } from "./ReadmePage"
|
|
31
31
|
import { AvatarPage } from "./AvatarPage"
|
|
32
32
|
import { BadgePage } from "./BadgePage"
|
|
33
33
|
import { BackgroundColorTokenPage } from "./BackgroundColorTokenPage"
|
|
@@ -106,7 +106,7 @@ function navItems(entries: NavEntry[]): NavItem[] {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
const navigation: NavEntry[] = [
|
|
109
|
-
{ label: "
|
|
109
|
+
{ label: "README", path: "/readme", page: ReadmePage },
|
|
110
110
|
{ label: "Editor", path: "/editor", page: JsxEditorPage },
|
|
111
111
|
{
|
|
112
112
|
label: "Tokens",
|
|
@@ -241,7 +241,7 @@ const navigation: NavEntry[] = [
|
|
|
241
241
|
},
|
|
242
242
|
]
|
|
243
243
|
|
|
244
|
-
const defaultPath = "/
|
|
244
|
+
const defaultPath = "/readme"
|
|
245
245
|
|
|
246
246
|
function useGalleryCompact() {
|
|
247
247
|
const [compact, setCompact] = useState(() =>
|
|
@@ -317,6 +317,9 @@ function MauiContent() {
|
|
|
317
317
|
</Route>
|
|
318
318
|
))}
|
|
319
319
|
|
|
320
|
+
<Route path="/about">
|
|
321
|
+
<Redirect to="/readme" />
|
|
322
|
+
</Route>
|
|
320
323
|
<Route path="/patterns/calendar">
|
|
321
324
|
<Redirect to="/apps/calendar" />
|
|
322
325
|
</Route>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Code } from "../components/Code"
|
|
2
|
+
import { CodeBlock } from "../components/CodeBlock"
|
|
3
|
+
import { Prose } from "../components/Prose"
|
|
4
|
+
import { H2, H3, Li, Link, P, Ul } from "../components/Typography"
|
|
5
|
+
|
|
6
|
+
export function ReadmePage() {
|
|
7
|
+
return (
|
|
8
|
+
<Prose style={{ marginBottom: "32px" }}>
|
|
9
|
+
<H2>Maui</H2>
|
|
10
|
+
<P>
|
|
11
|
+
Maui is an open-source, TypeScript-first design system styled with{" "}
|
|
12
|
+
<Link href="https://www.npmjs.com/package/purse-styles">
|
|
13
|
+
purse-styles
|
|
14
|
+
</Link>
|
|
15
|
+
. It is developed as part of{" "}
|
|
16
|
+
<Link href="https://gethalo.dev">Halo</Link>. This site is a gallery of
|
|
17
|
+
its tokens, components, patterns, and demo apps.
|
|
18
|
+
</P>
|
|
19
|
+
<Ul>
|
|
20
|
+
<Li>
|
|
21
|
+
Source:{" "}
|
|
22
|
+
<Link href="https://github.com/tanishqkancharla/maui">
|
|
23
|
+
github.com/tanishqkancharla/maui
|
|
24
|
+
</Link>
|
|
25
|
+
</Li>
|
|
26
|
+
<Li>
|
|
27
|
+
Package:{" "}
|
|
28
|
+
<Link href="https://www.npmjs.com/package/@tanishqkancharla/maui">
|
|
29
|
+
@tanishqkancharla/maui
|
|
30
|
+
</Link>
|
|
31
|
+
</Li>
|
|
32
|
+
</Ul>
|
|
33
|
+
|
|
34
|
+
<H3>Install</H3>
|
|
35
|
+
<CodeBlock lang="sh">{`npm install @tanishqkancharla/maui motion`}</CodeBlock>
|
|
36
|
+
<P>
|
|
37
|
+
<Code>motion</Code> is a required peer dependency, used by components
|
|
38
|
+
with animated presence and transitions. The package ships compiled
|
|
39
|
+
JavaScript and type declarations.
|
|
40
|
+
</P>
|
|
41
|
+
|
|
42
|
+
<H3>Usage</H3>
|
|
43
|
+
<P>
|
|
44
|
+
Wrap your app in <Code>MauiProvider</Code>, then import tokens and
|
|
45
|
+
components:
|
|
46
|
+
</P>
|
|
47
|
+
<CodeBlock lang="tsx">{`import { MauiProvider, Button } from "@tanishqkancharla/maui"
|
|
48
|
+
|
|
49
|
+
function App() {
|
|
50
|
+
return (
|
|
51
|
+
<MauiProvider>
|
|
52
|
+
<Button>Hello</Button>
|
|
53
|
+
</MauiProvider>
|
|
54
|
+
)
|
|
55
|
+
}`}</CodeBlock>
|
|
56
|
+
<P>
|
|
57
|
+
Put the exported <Code>themeFoucScript</Code> in an inline{" "}
|
|
58
|
+
<Code>{"<script>"}</Code> in <Code>{"<head>"}</Code> so the theme is
|
|
59
|
+
correct on first paint.
|
|
60
|
+
</P>
|
|
61
|
+
|
|
62
|
+
<H3>Develop</H3>
|
|
63
|
+
<CodeBlock lang="sh">{`npm install
|
|
64
|
+
npm run dev`}</CodeBlock>
|
|
65
|
+
<P>
|
|
66
|
+
Vite serves this gallery locally. Patterns and demo apps are reference
|
|
67
|
+
implementations, not package components.
|
|
68
|
+
</P>
|
|
69
|
+
|
|
70
|
+
<H3>License</H3>
|
|
71
|
+
<P>
|
|
72
|
+
Maui is available under the{" "}
|
|
73
|
+
<Link href="https://github.com/tanishqkancharla/maui/blob/main/LICENSE">
|
|
74
|
+
MIT License
|
|
75
|
+
</Link>
|
|
76
|
+
.
|
|
77
|
+
</P>
|
|
78
|
+
|
|
79
|
+
<H3>Contact</H3>
|
|
80
|
+
<P>For any questions, email tanishqkancharla3@gmail.com.</P>
|
|
81
|
+
</Prose>
|
|
82
|
+
)
|
|
83
|
+
}
|
package/src/pages/AboutPage.tsx
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Prose } from "../components/Prose"
|
|
2
|
-
import { H2, Link, P } from "../components/Typography"
|
|
3
|
-
|
|
4
|
-
export function AboutPage() {
|
|
5
|
-
return (
|
|
6
|
-
<Prose>
|
|
7
|
-
<H2>About</H2>
|
|
8
|
-
<P>
|
|
9
|
-
Maui is an open-source design system. It is developed as part of{" "}
|
|
10
|
-
<Link href="https://gethalo.dev">Halo</Link>. It is available under the{" "}
|
|
11
|
-
<Link href="https://github.com/tanishqkancharla/maui/blob/main/LICENSE">
|
|
12
|
-
MIT License
|
|
13
|
-
</Link>
|
|
14
|
-
.
|
|
15
|
-
</P>
|
|
16
|
-
<P>For any questions, email tanishqkancharla3@gmail.com.</P>
|
|
17
|
-
</Prose>
|
|
18
|
-
)
|
|
19
|
-
}
|