@tanishqkancharla/maui 0.0.38 → 0.0.40

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.
Files changed (57) hide show
  1. package/dist/components/Button.js +14 -9
  2. package/dist/components/Checkbox.js +2 -2
  3. package/dist/components/Code.d.ts +5 -1
  4. package/dist/components/Code.js +5 -2
  5. package/dist/components/Input.d.ts +11 -4
  6. package/dist/components/Input.js +59 -16
  7. package/dist/components/Radio.js +2 -2
  8. package/dist/components/Select.js +2 -2
  9. package/dist/components/Slider.js +2 -2
  10. package/dist/components/Switch.js +2 -2
  11. package/dist/components/Table.js +2 -2
  12. package/dist/maui.d.ts +2 -2
  13. package/dist/tokens/shadow.d.ts +2 -0
  14. package/dist/tokens/shadow.js +2 -0
  15. package/package.json +1 -1
  16. package/skills/maui/references/components/buttons.md +2 -3
  17. package/skills/maui/references/components/form-controls.md +6 -4
  18. package/skills/maui/references/design_conventions.md +5 -3
  19. package/skills/maui/references/tokens.md +6 -4
  20. package/src/components/Button.tsx +42 -28
  21. package/src/components/Checkbox.tsx +2 -2
  22. package/src/components/Code.tsx +19 -2
  23. package/src/components/Input.tsx +129 -23
  24. package/src/components/Radio.tsx +2 -2
  25. package/src/components/Select.tsx +3 -3
  26. package/src/components/Slider.tsx +2 -2
  27. package/src/components/Switch.tsx +2 -2
  28. package/src/components/Table.tsx +2 -2
  29. package/src/maui.ts +3 -1
  30. package/src/pages/AboutPage.tsx +6 -4
  31. package/src/pages/AssistantMessagePage.tsx +4 -3
  32. package/src/pages/AvatarPage.tsx +3 -2
  33. package/src/pages/BackgroundColorTokenPage.tsx +13 -13
  34. package/src/pages/BordersTokenPage.tsx +14 -13
  35. package/src/pages/ButtonsPage.tsx +11 -10
  36. package/src/pages/ColorTokenPage.tsx +18 -14
  37. package/src/pages/CornerRadiusTokenPage.tsx +19 -18
  38. package/src/pages/CrossfadePage.tsx +6 -5
  39. package/src/pages/DrawerPage.tsx +9 -8
  40. package/src/pages/EditorPage.tsx +8 -7
  41. package/src/pages/FlexTokenPage.tsx +52 -52
  42. package/src/pages/FocusRingTokenPage.tsx +4 -3
  43. package/src/pages/FuzzyStringPage.tsx +3 -3
  44. package/src/pages/IconsPage.tsx +13 -12
  45. package/src/pages/LayoutTokenPage.tsx +9 -9
  46. package/src/pages/LayoutUtilitiesPage.tsx +13 -12
  47. package/src/pages/LoadingScreenPage.tsx +4 -3
  48. package/src/pages/Maui.tsx +82 -2
  49. package/src/pages/MenuPage.tsx +2 -1
  50. package/src/pages/MotionTokenPage.tsx +3 -3
  51. package/src/pages/ProsePage.tsx +4 -3
  52. package/src/pages/ShadowTokenPage.tsx +20 -10
  53. package/src/pages/SidebarPage.tsx +2 -1
  54. package/src/pages/SizingTokenPage.tsx +6 -5
  55. package/src/pages/SpacingTokenPage.tsx +3 -2
  56. package/src/pages/TextTokenPage.tsx +24 -23
  57. package/src/tokens/shadow.ts +2 -0
@@ -10,17 +10,18 @@ import { colors, } from "../tokens/colors";
10
10
  import { focusRing } from "../tokens/focusRing";
11
11
  import { radius } from "../tokens/radius";
12
12
  import { motion } from "../tokens/motion";
13
- import { shadow, shadowVars, tintedSubtle } from "../tokens/shadow";
13
+ import { shadowVars } from "../tokens/shadow";
14
14
  import { controlSize } from "../tokens/sizing";
15
15
  import { spacing } from "../tokens/spacing";
16
16
  import { text } from "../tokens/text";
17
17
  import { memoize } from "../utils/memoize";
18
+ const buttonShadow = shadowVars.control;
18
19
  const disabledRaised = {
19
20
  cursor: "default",
20
21
  opacity: 1,
21
22
  color: colors.gray[8],
22
23
  backgroundColor: backgroundColor.element,
23
- boxShadow: shadowVars.subtle,
24
+ boxShadow: buttonShadow,
24
25
  };
25
26
  const disabledQuiet = {
26
27
  cursor: "default",
@@ -29,7 +30,7 @@ const disabledQuiet = {
29
30
  backgroundColor: "transparent",
30
31
  boxShadow: "none",
31
32
  };
32
- const buttonBaseClass = style(text({ size: "xs", fontWeight: 400, color: "highContrast" }), focusRing("&:focus-visible", shadowVars.subtle), motion.standard("box-shadow", "background-color", "color"), shadow.subtle, spacing.padding({ x: 6, y: 4 }), radius.sm, {
33
+ const buttonBaseClass = style(text({ size: "xs", fontWeight: 400, color: "highContrast" }), focusRing("&:focus-visible", buttonShadow), motion.standard("box-shadow", "background-color", "color"), spacing.padding({ x: 6, y: 4 }), radius.sm, {
33
34
  display: "inline-flex",
34
35
  alignItems: "center",
35
36
  justifyContent: "center",
@@ -38,6 +39,7 @@ const buttonBaseClass = style(text({ size: "xs", fontWeight: 400, color: "highCo
38
39
  height: controlSize.height,
39
40
  minWidth: controlSize.minTarget,
40
41
  border: "none",
42
+ boxShadow: buttonShadow,
41
43
  textOverflow: "ellipsis",
42
44
  overflow: "hidden",
43
45
  whiteSpace: "nowrap",
@@ -129,16 +131,19 @@ function darkerFill(color) {
129
131
  function onSolidText(color) {
130
132
  return `oklch(from ${color} clamp(0.2, (0.75 - l) * 100, 0.99) 0 0)`;
131
133
  }
134
+ function primaryButtonShadow(color) {
135
+ return `inset 0 1px #ffffff18, inset 0 0 1px .5px #fff2, 0 1px .5px #0003, 0 0 3px -1px color-mix(in oklch, ${color}, black 35%)`;
136
+ }
132
137
  const coloredButtonClass = memoize((variant, color) => {
133
138
  if (isCssColor(color)) {
134
139
  const fill = opaqueColor(color);
135
140
  if (variant === "primary") {
136
- const edge = tintedSubtle(fill);
137
141
  const hover = darkerFill(fill);
138
- return style(buttonBaseClass, focusRing("&:focus-visible", edge), {
142
+ const primaryShadow = primaryButtonShadow(fill);
143
+ return style(buttonBaseClass, focusRing("&:focus-visible", primaryShadow), {
139
144
  color: onSolidText(fill),
140
145
  backgroundColor: fill,
141
- boxShadow: edge,
146
+ boxShadow: primaryShadow,
142
147
  "&:hover:not(:disabled)": {
143
148
  backgroundColor: hover,
144
149
  },
@@ -152,11 +157,11 @@ const coloredButtonClass = memoize((variant, color) => {
152
157
  }
153
158
  const scale = paletteFill(color);
154
159
  if (variant === "primary") {
155
- const edge = tintedSubtle(scale[9]);
156
- return style(buttonBaseClass, focusRing("&:focus-visible", edge), {
160
+ const primaryShadow = primaryButtonShadow(scale[9]);
161
+ return style(buttonBaseClass, focusRing("&:focus-visible", primaryShadow), {
157
162
  color: darkTextOnSolid.has(color) ? scale[12] : "white",
158
163
  backgroundColor: scale[9],
159
- boxShadow: edge,
164
+ boxShadow: primaryShadow,
160
165
  "&:hover:not(:disabled)": {
161
166
  backgroundColor: scale[10],
162
167
  },
@@ -18,7 +18,7 @@ const checkboxSize = defineVars({
18
18
  box: { default: "14px", [LARGE_SCALE]: "20px" },
19
19
  checkmark: { default: "10px", [LARGE_SCALE]: "14px" },
20
20
  });
21
- const checkboxClass = style(flex({ alignItems: "center", gap: 3 }), focusRing("& .checkbox-input:focus-visible + .checkbox-toggle", shadowVars.subtle), {
21
+ const checkboxClass = style(flex({ alignItems: "center", gap: 3 }), focusRing("& .checkbox-input:focus-visible + .checkbox-toggle", shadowVars.control), {
22
22
  position: "relative",
23
23
  width: "fit-content",
24
24
  minHeight: controlSize.minTarget,
@@ -34,7 +34,7 @@ const checkboxClass = style(flex({ alignItems: "center", gap: 3 }), focusRing("&
34
34
  transform: "scale(1)",
35
35
  },
36
36
  });
37
- const checkboxToggleClass = style(flex({ alignItems: "center", justifyContent: "center" }), radius["2xs"], motion.standard("background-color"), shadow.subtle, {
37
+ const checkboxToggleClass = style(flex({ alignItems: "center", justifyContent: "center" }), radius["2xs"], motion.standard("background-color"), shadow.control, {
38
38
  pointerEvents: "none",
39
39
  flexShrink: 0,
40
40
  width: checkboxSize.box,
@@ -1,5 +1,9 @@
1
1
  import type React from "react";
2
2
  type CodeProps = React.HTMLAttributes<HTMLElement>;
3
+ export type KbdVariant = "default" | "quiet";
4
+ export type KbdProps = CodeProps & {
5
+ variant?: KbdVariant;
6
+ };
3
7
  export declare function Code({ className, children, ...props }: CodeProps): React.JSX.Element;
4
- export declare function Kbd({ className, children, ...props }: CodeProps): React.JSX.Element;
8
+ export declare function Kbd({ className, children, variant, ...props }: KbdProps): React.JSX.Element;
5
9
  export {};
@@ -22,12 +22,15 @@ const kbdClass = style(text({ size: "xs", fontWeight: 400, color: "lowContrast"
22
22
  paddingBlock: 0,
23
23
  whiteSpace: "nowrap",
24
24
  });
25
+ const quietKbdClass = style(kbdClass, {
26
+ backgroundColor: "transparent",
27
+ });
25
28
  export function Code({ className, children, ...props }) {
26
29
  const codeClassName = useStyles(codeClass);
27
30
  return (_jsx("code", { ...props, className: joinClassNames(codeClassName, className), children: children }));
28
31
  }
29
- export function Kbd({ className, children, ...props }) {
30
- const kbdClassName = useStyles(kbdClass);
32
+ export function Kbd({ className, children, variant = "default", ...props }) {
33
+ const kbdClassName = useStyles(variant === "quiet" ? quietKbdClass : kbdClass);
31
34
  return (_jsx("kbd", { ...props, className: joinClassNames(kbdClassName, className), children: children }));
32
35
  }
33
36
  function joinClassNames(...classNames) {
@@ -1,7 +1,14 @@
1
1
  import { AriaNumberFieldProps, AriaSearchFieldProps, AriaTextFieldOptions } from "react-aria";
2
- type InputProps = AriaTextFieldOptions<"input">;
3
- export declare function TextField(props: InputProps): import("react").JSX.Element;
4
- export declare function SearchField(props: AriaSearchFieldProps): import("react").JSX.Element;
2
+ type KeyboardHintProps = {
3
+ /** A single-character shortcut displayed at the inline end while unfocused. */
4
+ keyboardHint?: string;
5
+ };
6
+ export type TextFieldProps = AriaTextFieldOptions<"input"> & KeyboardHintProps;
7
+ export type SearchFieldProps = AriaSearchFieldProps & KeyboardHintProps & {
8
+ "aria-keyshortcuts"?: string;
9
+ };
10
+ export declare function TextField(props: TextFieldProps): import("react").JSX.Element;
11
+ export declare function SearchField(props: SearchFieldProps): import("react").JSX.Element;
5
12
  export declare function NumberField(props: AriaNumberFieldProps): import("react").JSX.Element;
6
- export declare function QuietTextField(props: InputProps): import("react").JSX.Element;
13
+ export declare function QuietTextField(props: TextFieldProps): import("react").JSX.Element;
7
14
  export {};
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef } from "react";
3
- import { useButton, useLocale, useNumberField, useSearchField, useTextField, } from "react-aria";
2
+ import { useRef, useState } from "react";
3
+ import { mergeProps, useButton, useLocale, useNumberField, useSearchField, useTextField, } from "react-aria";
4
4
  import { useNumberFieldState, useSearchFieldState } from "react-stately";
5
5
  import { defineVars, style, useStyles } from "purse-styles";
6
6
  import { LARGE_SCALE } from "../theme/dataScale";
@@ -12,11 +12,12 @@ import { radius } from "../tokens/radius";
12
12
  import { shadow, shadowVars } from "../tokens/shadow";
13
13
  import { controlSize, iconSizeValues } from "../tokens/sizing";
14
14
  import { spacing } from "../tokens/spacing";
15
- import { text } from "../tokens/text";
15
+ import { monospace, text } from "../tokens/text";
16
+ import { Kbd } from "./Code";
16
17
  import { Icons } from "./Icons";
17
18
  const inputText = text({ size: "sm", fontWeight: 400, color: "highContrast" });
18
19
  const numberFieldDivider = `color-mix(in oklch, ${colors.gray[12]} 5%, ${backgroundColor.element})`;
19
- const inputClass = style(inputText, focusRing("&:focus-visible", shadowVars.subtle), motion.standard("background", "border-color"), radius.sm, spacing.padding({ x: 4, y: 2 }), shadow.subtle, {
20
+ const inputClass = style(inputText, focusRing("&:focus-visible", shadowVars.control), motion.standard("background", "border-color"), radius.sm, spacing.padding({ x: 4, y: 2 }), shadow.control, {
20
21
  width: "100%",
21
22
  minWidth: 0,
22
23
  height: controlSize.height,
@@ -31,18 +32,52 @@ const inputClass = style(inputText, focusRing("&:focus-visible", shadowVars.subt
31
32
  background: colors.gray[2],
32
33
  },
33
34
  "&[aria-invalid='true']:not(:focus-visible)": {
34
- boxShadow: `0 0 0 1px light-dark(#ce2c31, #e5484d), ${shadowVars.subtle}`,
35
+ boxShadow: `0 0 0 1px light-dark(#ce2c31, #e5484d), ${shadowVars.control}`,
35
36
  },
36
37
  "&::placeholder": {
37
38
  fontStyle: "italic",
38
39
  color: colors.gray[8],
39
40
  },
40
41
  });
42
+ const inputFrameClass = style({
43
+ position: "relative",
44
+ width: "100%",
45
+ });
46
+ const inputWithHintClass = style({
47
+ paddingRight: "28px",
48
+ });
49
+ const keyboardHintClass = style(monospace, {
50
+ position: "absolute",
51
+ top: "50%",
52
+ right: spacing.value(4),
53
+ transform: "translateY(-50%)",
54
+ pointerEvents: "none",
55
+ });
56
+ function useKeyboardHint() {
57
+ const [isFocused, setIsFocused] = useState(false);
58
+ return {
59
+ isFocused,
60
+ focusProps: {
61
+ onFocus: () => setIsFocused(true),
62
+ onBlur: () => setIsFocused(false),
63
+ },
64
+ };
65
+ }
66
+ function KeyboardHint(props) {
67
+ const className = useStyles(keyboardHintClass);
68
+ return (_jsx(Kbd, { variant: "quiet", className: className, "aria-hidden": "true", children: props.children }));
69
+ }
41
70
  export function TextField(props) {
71
+ const { keyboardHint, ...fieldProps } = props;
42
72
  const ref = useRef(null);
43
- const { inputProps } = useTextField({ ...props }, ref);
44
- const className = useStyles(inputClass);
45
- return _jsx("input", { className: className, ref: ref, ...inputProps });
73
+ const { inputProps } = useTextField(fieldProps, ref);
74
+ const { isFocused, focusProps } = useKeyboardHint();
75
+ const frameClassName = useStyles(inputFrameClass);
76
+ const className = useStyles(inputClass, keyboardHint ? inputWithHintClass : undefined);
77
+ const input = (_jsx("input", { className: className, ref: ref, ...mergeProps(inputProps, focusProps) }));
78
+ if (!keyboardHint)
79
+ return input;
80
+ return (_jsxs("div", { className: frameClassName, children: [input, !isFocused ? _jsx(KeyboardHint, { children: keyboardHint }) : null] }));
46
81
  }
47
82
  const searchFieldSize = defineVars({
48
83
  clearSize: { default: "16px", [LARGE_SCALE]: "40px" },
@@ -85,17 +120,19 @@ const searchFieldClass = style(focusRing("& button:focus-visible"), {
85
120
  },
86
121
  });
87
122
  export function SearchField(props) {
123
+ const { keyboardHint, "aria-keyshortcuts": ariaKeyShortcuts, ...fieldProps } = props;
88
124
  const ref = useRef(null);
89
- const state = useSearchFieldState(props);
90
- const { inputProps } = useSearchField(props, state, ref);
125
+ const state = useSearchFieldState(fieldProps);
126
+ const { inputProps } = useSearchField(fieldProps, state, ref);
127
+ const { isFocused, focusProps } = useKeyboardHint();
91
128
  const inputClassName = useStyles(inputClass);
92
129
  const searchClassName = useStyles(searchFieldClass);
93
- return (_jsxs("div", { className: searchClassName, children: [_jsx("input", { className: inputClassName, ref: ref, ...inputProps }), state.value !== "" && (_jsx("button", { "aria-label": "Clear search", type: "button", onMouseDown: (event) => event.preventDefault(), onClick: () => state.setValue(""), children: _jsx(Icons.CircleX, {}) }))] }));
130
+ return (_jsxs("div", { className: searchClassName, children: [_jsx("input", { className: inputClassName, ref: ref, ...mergeProps(inputProps, focusProps), "aria-keyshortcuts": ariaKeyShortcuts }), keyboardHint && !isFocused && state.value === "" ? (_jsx(KeyboardHint, { children: keyboardHint })) : null, state.value !== "" && (_jsx("button", { "aria-label": "Clear search", type: "button", onMouseDown: (event) => event.preventDefault(), onClick: () => state.setValue(""), children: _jsx(Icons.CircleX, {}) }))] }));
94
131
  }
95
132
  const numberFieldSize = defineVars({
96
133
  stepperWidth: { default: "24px", [LARGE_SCALE]: "40px" },
97
134
  });
98
- const numberFieldClass = style(focusRing("&:has(:focus-visible)", shadowVars.subtle), motion.standard("background", "border-color"), radius.sm, shadow.subtle, {
135
+ const numberFieldClass = style(focusRing("&:has(:focus-visible)", shadowVars.control), motion.standard("background", "border-color"), radius.sm, shadow.control, {
99
136
  display: "flex",
100
137
  alignItems: "center",
101
138
  width: "100%",
@@ -108,7 +145,7 @@ const numberFieldClass = style(focusRing("&:has(:focus-visible)", shadowVars.sub
108
145
  background: colors.gray[2],
109
146
  },
110
147
  "&:has(input[aria-invalid='true']):not(:has(:focus-visible))": {
111
- boxShadow: `0 0 0 1px light-dark(#ce2c31, #e5484d), ${shadowVars.subtle}`,
148
+ boxShadow: `0 0 0 1px light-dark(#ce2c31, #e5484d), ${shadowVars.control}`,
112
149
  },
113
150
  "& .number-stepper": {
114
151
  display: "flex",
@@ -195,8 +232,14 @@ const quietInputClass = style(inputText, focusRing(), motion.standard("backgroun
195
232
  },
196
233
  });
197
234
  export function QuietTextField(props) {
235
+ const { keyboardHint, ...fieldProps } = props;
198
236
  const ref = useRef(null);
199
- const { inputProps } = useTextField({ ...props }, ref);
200
- const className = useStyles(quietInputClass);
201
- return _jsx("input", { className: className, ...inputProps });
237
+ const { inputProps } = useTextField(fieldProps, ref);
238
+ const { isFocused, focusProps } = useKeyboardHint();
239
+ const frameClassName = useStyles(inputFrameClass);
240
+ const className = useStyles(quietInputClass, keyboardHint ? inputWithHintClass : undefined);
241
+ const input = (_jsx("input", { className: className, ref: ref, ...mergeProps(inputProps, focusProps) }));
242
+ if (!keyboardHint)
243
+ return input;
244
+ return (_jsxs("div", { className: frameClassName, children: [input, !isFocused ? _jsx(KeyboardHint, { children: keyboardHint }) : null] }));
202
245
  }
@@ -21,7 +21,7 @@ export function RadioOptionGroup(props) {
21
21
  const className = useStyles(radioGroupClass);
22
22
  return (_jsx(RadioOptionGroupContext.Provider, { value: state, children: _jsxs("div", { className: className, ...groupProps, children: [_jsx(Label, { ...labelProps, children: label }), children] }) }));
23
23
  }
24
- const radioClass = style(flex({ alignItems: "center", gap: 3 }), focusRing("& .radio-input:focus-visible + .radio-toggle", shadowVars.subtle), {
24
+ const radioClass = style(flex({ alignItems: "center", gap: 3 }), focusRing("& .radio-input:focus-visible + .radio-toggle", shadowVars.control), {
25
25
  position: "relative",
26
26
  width: "fit-content",
27
27
  "&:hover .radio-toggle": {
@@ -34,7 +34,7 @@ const radioClass = style(flex({ alignItems: "center", gap: 3 }), focusRing("& .r
34
34
  transform: "scale(1)",
35
35
  },
36
36
  });
37
- const radioToggleClass = style(radius.circle, motion.standard("background-color"), shadow.subtle, {
37
+ const radioToggleClass = style(radius.circle, motion.standard("background-color"), shadow.control, {
38
38
  pointerEvents: "none",
39
39
  position: "relative",
40
40
  width: "14px",
@@ -19,7 +19,7 @@ const selectClass = style({
19
19
  gap: spacing.value(3),
20
20
  width: "100%",
21
21
  });
22
- const triggerClass = style(text({ size: "sm", fontWeight: 400, color: "highContrast" }), focusRing("&:focus-visible", shadowVars.subtle), motion.standard("background", "border-color"), radius.sm, spacing.padding({ x: 4, y: 2 }), shadow.subtle, {
22
+ const triggerClass = style(text({ size: "sm", fontWeight: 400, color: "highContrast" }), focusRing("&:focus-visible", shadowVars.control), motion.standard("background", "border-color"), radius.sm, spacing.padding({ x: 4, y: 2 }), shadow.control, {
23
23
  display: "flex",
24
24
  alignItems: "center",
25
25
  height: controlSize.height,
@@ -39,7 +39,7 @@ const triggerClass = style(text({ size: "sm", fontWeight: 400, color: "highContr
39
39
  background: colors.gray[2],
40
40
  },
41
41
  "&[data-invalid]:not(:focus-visible)": {
42
- boxShadow: `0 0 0 1px light-dark(#ce2c31, #e5484d), ${shadowVars.subtle}`,
42
+ boxShadow: `0 0 0 1px light-dark(#ce2c31, #e5484d), ${shadowVars.control}`,
43
43
  },
44
44
  });
45
45
  const valueClass = style({
@@ -12,7 +12,7 @@ const sliderSize = defineVars({
12
12
  thumb: { default: "12px", [LARGE_SCALE]: "20px" },
13
13
  track: { default: "18px", [LARGE_SCALE]: "40px" },
14
14
  });
15
- const sliderClass = style(focusRing("& .slider-thumb:has(input:focus-visible)", shadowVars.subtle), {
15
+ const sliderClass = style(focusRing("& .slider-thumb:has(input:focus-visible)", shadowVars.control), {
16
16
  width: "240px",
17
17
  "& .slider-label-row": {
18
18
  display: "flex",
@@ -49,7 +49,7 @@ const sliderClass = style(focusRing("& .slider-thumb:has(input:focus-visible)",
49
49
  height: sliderSize.thumb,
50
50
  borderRadius: "100%",
51
51
  background: colors.gray[12],
52
- boxShadow: shadowVars.subtle,
52
+ boxShadow: shadowVars.control,
53
53
  transform: "translateX(-50%)",
54
54
  },
55
55
  "& .slider-thumb[data-dragging='true']": {
@@ -30,7 +30,7 @@ const switchOff = defineVars({
30
30
  [DARK_THEME]: colors.gray[2],
31
31
  },
32
32
  });
33
- const switchClass = style(flex({ alignItems: "center", gap: 3 }), focusRing("& .switch-input:focus-visible + .switch-toggle", shadowVars.subtle), {
33
+ const switchClass = style(flex({ alignItems: "center", gap: 3 }), focusRing("& .switch-input:focus-visible + .switch-toggle", shadowVars.control), {
34
34
  position: "relative",
35
35
  width: "fit-content",
36
36
  minHeight: controlSize.minTarget,
@@ -44,7 +44,7 @@ const switchClass = style(flex({ alignItems: "center", gap: 3 }), focusRing("& .
44
44
  transform: `translateX(calc(${switchSize.width} - ${switchSize.thumb} - 4px))`,
45
45
  },
46
46
  });
47
- const switchToggleClass = style(radius.pill, motion.standard("background-color"), shadow.subtle, {
47
+ const switchToggleClass = style(radius.pill, motion.standard("background-color"), shadow.control, {
48
48
  position: "relative",
49
49
  flexShrink: 0,
50
50
  width: switchSize.width,
@@ -82,7 +82,7 @@ const tableCaptionClass = style(text({ size: "xs", fontWeight: 400, color: "lowC
82
82
  const emptyStateClass = style(text({ size: "sm", fontWeight: 400, color: "lowContrast" }), spacing.padding({ x: 4, y: 8 }), {
83
83
  textAlign: "center",
84
84
  });
85
- const tableSelectionCheckboxClass = style(flex({ alignItems: "center", justifyContent: "center" }), focusRing("&[data-focus-visible] .checkbox-toggle", shadowVars.subtle), {
85
+ const tableSelectionCheckboxClass = style(flex({ alignItems: "center", justifyContent: "center" }), focusRing("&[data-focus-visible] .checkbox-toggle", shadowVars.control), {
86
86
  position: "relative",
87
87
  width: "fit-content",
88
88
  cursor: "default",
@@ -108,7 +108,7 @@ const tableSelectionCheckboxClass = style(flex({ alignItems: "center", justifyCo
108
108
  transform: "scale(1)",
109
109
  },
110
110
  });
111
- const tableSelectionToggleClass = style(flex({ alignItems: "center", justifyContent: "center" }), radius["2xs"], shadow.subtle, {
111
+ const tableSelectionToggleClass = style(flex({ alignItems: "center", justifyContent: "center" }), radius["2xs"], shadow.control, {
112
112
  pointerEvents: "none",
113
113
  width: "14px",
114
114
  height: "14px",
package/dist/maui.d.ts CHANGED
@@ -25,7 +25,7 @@ export { Checkbox } from "./components/Checkbox";
25
25
  export { Switch } from "./components/Switch";
26
26
  export { Slider } from "./components/Slider";
27
27
  export { RadioOptionGroup, RadioOption } from "./components/Radio";
28
- export { TextField, SearchField, NumberField, QuietTextField, } from "./components/Input";
28
+ export { TextField, SearchField, NumberField, QuietTextField, type TextFieldProps, type SearchFieldProps, } from "./components/Input";
29
29
  export { Select, SelectItem, type SelectProps, type SelectItemProps, } from "./components/Select";
30
30
  export { Dialog } from "./components/Dialog";
31
31
  export { Tooltip } from "./components/Tooltip";
@@ -45,6 +45,6 @@ export { Flex, Gap, Spacer, Divider, type FlexShadow, type FlexRadius, type Flex
45
45
  export { Text, type TextProps } from "./components/Text";
46
46
  export { H1, H2, H3, H4, P, Label, labelText, Blockquote, Ul, Ol, Li, Link, } from "./components/Typography";
47
47
  export { CodeBlock } from "./components/CodeBlock";
48
- export { Code, Kbd } from "./components/Code";
48
+ export { Code, Kbd, type KbdProps, type KbdVariant } from "./components/Code";
49
49
  export { CollectionPopover, type CollectionPopoverProps, } from "./components/CollectionPopover";
50
50
  export { navigationItem } from "./components/navigationItem";
@@ -1,4 +1,5 @@
1
1
  export declare const shadowVars: import("purse-styles").VariableGroup<{
2
+ readonly control: "inset 0 1px #ffffff08, inset 0 0 1px .5px #fff1, 0 1px .5px #00000018, 0 0 3px -1px #000a";
2
3
  readonly subtle: `rgba(var(--${string}), 0.08) 0px 0px 0px 1px, rgba(0, 0, 0, var(--${string})) 0px 1px 1px -0.5px, rgba(0, 0, 0, var(--${string})) 0px 3px 3px -1.5px`;
3
4
  readonly medium: `rgba(var(--${string}), 0.08) 0px 0px 0px 1px, rgba(0, 0, 0, var(--${string})) 0px 1px 1px -0.5px, rgba(0, 0, 0, var(--${string})) 0px 3px 3px -1.5px, rgba(0, 0, 0, var(--${string})) 0px 6px 6px -3px`;
4
5
  readonly strong: `rgba(var(--${string}), 0.08) 0px 0px 0px 1px, rgba(0, 0, 0, var(--${string})) 0px 1px 1px -0.5px, rgba(0, 0, 0, var(--${string})) 0px 3px 3px -1.5px, rgba(0, 0, 0, var(--${string})) 0px 6px 6px -3px, rgba(0, 0, 0, calc(var(--${string}) * 0.67)) 0px 12px 12px -6px, rgba(0, 0, 0, calc(var(--${string}) * 0.67)) 0px 24px 24px -12px`;
@@ -6,6 +7,7 @@ export declare const shadowVars: import("purse-styles").VariableGroup<{
6
7
  /** Same offsets as `shadowVars.subtle`, with the ring and blurs tinted from `color`. */
7
8
  export declare function tintedSubtle(color: string): string;
8
9
  export declare const shadow: {
10
+ control: import("purse-styles").StyleElement;
9
11
  subtle: import("purse-styles").StyleElement;
10
12
  medium: import("purse-styles").StyleElement;
11
13
  strong: import("purse-styles").StyleElement;
@@ -13,6 +13,7 @@ const shadowParams = defineVars({
13
13
  const rgb = shadowParams.foregroundRgb;
14
14
  const blur = shadowParams.blurOpacity;
15
15
  export const shadowVars = defineVars({
16
+ control: `inset 0 1px #ffffff08, inset 0 0 1px .5px #fff1, 0 1px .5px #00000018, 0 0 3px -1px #000a`,
16
17
  subtle: `rgba(${rgb}, 0.08) 0px 0px 0px 1px, rgba(0, 0, 0, ${blur}) 0px 1px 1px -0.5px, rgba(0, 0, 0, ${blur}) 0px 3px 3px -1.5px`,
17
18
  medium: `rgba(${rgb}, 0.08) 0px 0px 0px 1px, rgba(0, 0, 0, ${blur}) 0px 1px 1px -0.5px, rgba(0, 0, 0, ${blur}) 0px 3px 3px -1.5px, rgba(0, 0, 0, ${blur}) 0px 6px 6px -3px`,
18
19
  strong: `rgba(${rgb}, 0.08) 0px 0px 0px 1px, rgba(0, 0, 0, ${blur}) 0px 1px 1px -0.5px, rgba(0, 0, 0, ${blur}) 0px 3px 3px -1.5px, rgba(0, 0, 0, ${blur}) 0px 6px 6px -3px, rgba(0, 0, 0, calc(${blur} * 0.67)) 0px 12px 12px -6px, rgba(0, 0, 0, calc(${blur} * 0.67)) 0px 24px 24px -12px`,
@@ -22,6 +23,7 @@ export function tintedSubtle(color) {
22
23
  return `oklch(from ${color} l c h / 0.08) 0px 0px 0px 1px, oklch(from ${color} l c h / ${blur}) 0px 1px 1px -0.5px, oklch(from ${color} l c h / ${blur}) 0px 3px 3px -1.5px`;
23
24
  }
24
25
  export const shadow = {
26
+ control: style({ boxShadow: shadowVars.control }),
25
27
  subtle: style({ boxShadow: shadowVars.subtle }),
26
28
  medium: style({ boxShadow: shadowVars.medium }),
27
29
  strong: style({ boxShadow: shadowVars.strong }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanishqkancharla/maui",
3
- "version": "0.0.38",
3
+ "version": "0.0.40",
4
4
  "description": "Maui design system",
5
5
  "type": "module",
6
6
  "main": "./dist/maui.js",
@@ -4,7 +4,7 @@ Gallery: `/components/buttons`. Import from `"maui"`. Overlay and Dialog live on
4
4
 
5
5
  ## `Button`
6
6
 
7
- Styled React Aria `Button` (default `type="button"`). Consumes `ButtonContext`, so it works as a `MenuTrigger`, `Select`, `DialogTrigger`, `ComboBox`, or `DatePicker` child. Height 28px, `shadow.subtle`, `radius` 4px.
7
+ Styled React Aria `Button` (default `type="button"`). Consumes `ButtonContext`, so it works as a `MenuTrigger`, `Select`, `DialogTrigger`, `ComboBox`, or `DatePicker` child. Height 28px, `shadow.control`, `radius` 4px.
8
8
 
9
9
  ```tsx
10
10
  <Button>Save</Button>
@@ -28,7 +28,7 @@ Styled React Aria `Button` (default `type="button"`). Consumes `ButtonContext`,
28
28
  | `onClick` | Still supported. React Aria also exposes `onPress` |
29
29
  | `children` | Text is wrapped for cap-height trim; SVG icons sit beside text. Icon-only needs `aria-label` |
30
30
 
31
- Primary: fill step 9, hover 10, light text (`onAccent`, or step 12 on amber/lime/mint/sky/yellow). Custom CSS fill drops alpha; hover is `l - 0.04`; text is white or near-black from lightness. Edge is `tintedSubtle`. 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.
31
+ Primary: fill step 9, hover 10, light text (`onAccent`, or step 12 on amber/lime/mint/sky/yellow). Custom CSS fill drops alpha; hover is `l - 0.04`; text is white or near-black from lightness. Its raised shadow strengthens the white inset highlight and tints the tight outer edge from the fill. 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.
32
32
 
33
33
  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).
34
34
 
@@ -61,4 +61,3 @@ Full-viewport portal. `onClickOutside` fires when the backdrop itself is the mou
61
61
  ```
62
62
 
63
63
  Edge-anchored mobile nav is [`Drawer`](drawer.md), not this Dialog and not Overlay.
64
-
@@ -2,16 +2,18 @@
2
2
 
3
3
  Gallery: `/components/form-controls`. Import from `"maui"`. [Select](select.md) has its own page.
4
4
 
5
- All fields are 28px tall, full width of the parent, `shadow.subtle` except `QuietTextField`. They take React Aria field props (`value`, `onChange`, `placeholder`, `isDisabled`, `isInvalid`, `aria-label`, `id`, …).
5
+ All fields are 28px tall, full width of the parent, `shadow.control` except `QuietTextField`. They take React Aria field props (`value`, `onChange`, `placeholder`, `isDisabled`, `isInvalid`, `aria-label`, `id`, …).
6
6
 
7
7
  ```tsx
8
- <TextField aria-label="Title" placeholder="Title" value={v} onChange={setV} />
9
- <SearchField aria-label="Search" value={q} onChange={setQ} /> // clear button when non-empty
8
+ <TextField aria-label="Title" placeholder="Title" value={v} onChange={setV} keyboardHint="T" />
9
+ <SearchField aria-label="Search" value={q} onChange={setQ} keyboardHint="/" /> // clear button when non-empty
10
10
  <NumberField aria-label="Count" value={n} onChange={setN} minValue={0} maxValue={10} />
11
11
  <QuietTextField aria-label="Filter" placeholder="Filter" value={f} onChange={setF} />
12
12
  ```
13
13
 
14
- `NumberField` uses tabular numerals so the value does not shift as you step. Invalid (unfocused) adds a red 1px ring on top of `shadow.subtle`. Placeholders are italic `gray[8]`. Cap width in the parent (`maxWidth: 240px` is the gallery default).
14
+ `NumberField` uses tabular numerals so the value does not shift as you step. Invalid (unfocused) adds a red 1px ring on top of `shadow.control`. Placeholders are italic `gray[8]`. Cap width in the parent (`maxWidth: 240px` is the gallery default).
15
+
16
+ `TextField`, `SearchField`, and `QuietTextField` accept `keyboardHint`, a single-character shortcut rendered at the inline end while the field is unfocused. The hint is presentational; the consuming app owns the keyboard handler. `SearchField` hides it when the query is non-empty so the clear button can use the same space.
15
17
 
16
18
  ## `Checkbox`
17
19
 
@@ -12,15 +12,17 @@ Patterns, demo apps, and the gallery `Panel` preview frame are not package compo
12
12
 
13
13
  ## Shadows
14
14
 
15
- Use the three-level elevation scale:
15
+ Use `shadow.control` for interactive control surfaces. It combines two inset
16
+ highlights with a compact outer edge. Use the three-level elevation scale for
17
+ other raised surfaces:
16
18
 
17
- - `shadow.subtle` — controls, cards, and ordinary raised surfaces
19
+ - `shadow.subtle` — cards and ordinary raised surfaces
18
20
  - `shadow.medium` — tooltips and larger floating panels
19
21
  - `shadow.strong` — dropdowns, popovers, and dominant overlays
20
22
 
21
23
  All three include a 1px ring. Do not also apply `border()`, `borderColor.outline`, or another ring to the same element.
22
24
 
23
- Buttons and form-control surfaces use `shadow.subtle` by default. For compound controls, apply it once to the outer control boundary instead of to every internal button or segment.
25
+ Buttons and form-control surfaces use `shadow.control` by default. For compound controls, apply it once to the outer control boundary instead of to every internal button or segment.
24
26
 
25
27
  ## Focus
26
28
 
@@ -170,12 +170,14 @@ Controls typically use `sm`. Cards / sidebars use `lg`. Avatars use `circle`.
170
170
  ## Shadows and rings
171
171
 
172
172
  ```ts
173
- shadow.subtle | shadow.medium | shadow.strong // style objects
174
- shadowVars.subtle | .medium | .strong // raw box-shadow strings
173
+ shadow.control // button/form-control style object
174
+ shadow.subtle | shadow.medium | shadow.strong // elevation style objects
175
+ shadowVars.control | .subtle | .medium | .strong // raw box-shadow strings
175
176
  tintedSubtle(color) // subtle ring+blur tinted from a fill
176
177
  ```
177
178
 
178
- Every elevation already includes a 1px ring. **Do not also `border()` the same element.**
179
+ `shadow.control` uses inset highlights and a compact outer edge. Every elevation
180
+ already includes a 1px ring. **Do not also `border()` the same element.**
179
181
 
180
182
  `border(sides, color)`:
181
183
 
@@ -184,7 +186,7 @@ Every elevation already includes a 1px ring. **Do not also `border()` the same e
184
186
 
185
187
  `borderColor.border` / `borderColor.outline` are the raw color strings (hairlines, `borderBottom`).
186
188
 
187
- `focusRing(selector = "&:focus-visible", existingShadow?)` — Radix blue ring. Pass `shadowVars.subtle` as the second argument so a raised control keeps its elevation while focused.
189
+ `focusRing(selector = "&:focus-visible", existingShadow?)` — Radix blue ring. Pass `shadowVars.control` as the second argument so a form control keeps its surface treatment while focused; use the matching elevation variable for other raised surfaces.
188
190
 
189
191
  `visuallyHidden` — clip an input and keep the styled sibling visible (Checkbox / Switch / Radio).
190
192