shelving 1.282.2 → 1.284.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.
Files changed (240) hide show
  1. package/package.json +2 -2
  2. package/ui/README.md +2 -1
  3. package/ui/block/Address.d.ts +7 -7
  4. package/ui/block/Address.js +6 -6
  5. package/ui/block/Address.tsx +10 -9
  6. package/ui/block/Block.d.ts +3 -3
  7. package/ui/block/Block.js +3 -2
  8. package/ui/block/Block.tsx +5 -4
  9. package/ui/block/Blockquote.d.ts +3 -3
  10. package/ui/block/Blockquote.js +2 -2
  11. package/ui/block/Blockquote.tsx +4 -3
  12. package/ui/block/Caption.d.ts +3 -3
  13. package/ui/block/Caption.js +3 -3
  14. package/ui/block/Caption.tsx +5 -4
  15. package/ui/block/Card.d.ts +3 -2
  16. package/ui/block/Card.js +2 -2
  17. package/ui/block/Card.tsx +4 -2
  18. package/ui/block/Column.d.ts +3 -3
  19. package/ui/block/Column.js +2 -2
  20. package/ui/block/Column.tsx +4 -3
  21. package/ui/block/Definitions.d.ts +3 -3
  22. package/ui/block/Definitions.js +2 -2
  23. package/ui/block/Definitions.tsx +4 -3
  24. package/ui/block/Details.d.ts +3 -2
  25. package/ui/block/Details.js +2 -2
  26. package/ui/block/Details.tsx +4 -3
  27. package/ui/block/Divider.d.ts +3 -2
  28. package/ui/block/Divider.js +2 -2
  29. package/ui/block/Divider.tsx +4 -2
  30. package/ui/block/Heading.d.ts +3 -3
  31. package/ui/block/Heading.js +2 -2
  32. package/ui/block/Heading.tsx +4 -3
  33. package/ui/block/Image.d.ts +3 -2
  34. package/ui/block/Image.js +2 -2
  35. package/ui/block/Image.tsx +4 -2
  36. package/ui/block/Label.d.ts +1 -1
  37. package/ui/block/Label.js +2 -2
  38. package/ui/block/Label.tsx +2 -1
  39. package/ui/block/List.d.ts +3 -2
  40. package/ui/block/List.js +4 -4
  41. package/ui/block/List.tsx +6 -4
  42. package/ui/block/Panel.d.ts +3 -3
  43. package/ui/block/Panel.js +2 -2
  44. package/ui/block/Panel.tsx +4 -3
  45. package/ui/block/Paragraph.d.ts +3 -3
  46. package/ui/block/Paragraph.js +2 -2
  47. package/ui/block/Paragraph.tsx +4 -3
  48. package/ui/block/Preformatted.d.ts +3 -3
  49. package/ui/block/Preformatted.js +2 -2
  50. package/ui/block/Preformatted.tsx +4 -3
  51. package/ui/block/Row.d.ts +3 -3
  52. package/ui/block/Row.js +2 -2
  53. package/ui/block/Row.tsx +4 -3
  54. package/ui/block/Section.js +1 -1
  55. package/ui/block/Section.tsx +1 -0
  56. package/ui/block/Subheading.d.ts +1 -1
  57. package/ui/block/Subheading.js +2 -2
  58. package/ui/block/Subheading.tsx +2 -1
  59. package/ui/block/Title.d.ts +1 -1
  60. package/ui/block/Title.js +2 -2
  61. package/ui/block/Title.tsx +2 -1
  62. package/ui/block/Video.d.ts +3 -3
  63. package/ui/block/Video.js +2 -2
  64. package/ui/block/Video.tsx +4 -3
  65. package/ui/button/Button.d.ts +3 -2
  66. package/ui/button/Button.js +2 -2
  67. package/ui/button/Button.md +28 -0
  68. package/ui/button/Button.module.css +23 -5
  69. package/ui/button/Button.tsx +4 -3
  70. package/ui/button/Clickable.d.ts +3 -3
  71. package/ui/button/Clickable.js +6 -4
  72. package/ui/button/Clickable.tsx +9 -9
  73. package/ui/button/FullscreenButton.d.ts +3 -5
  74. package/ui/button/FullscreenButton.tsx +3 -2
  75. package/ui/button/RetryButton.d.ts +2 -2
  76. package/ui/button/RetryButton.tsx +2 -2
  77. package/ui/button/SubmitButton.d.ts +3 -3
  78. package/ui/button/SubmitButton.js +3 -2
  79. package/ui/button/SubmitButton.tsx +5 -3
  80. package/ui/dialog/Dialog.d.ts +6 -4
  81. package/ui/dialog/Dialog.js +7 -4
  82. package/ui/dialog/Dialog.tsx +19 -7
  83. package/ui/dialog/Modal.d.ts +3 -3
  84. package/ui/dialog/Modal.js +3 -3
  85. package/ui/dialog/Modal.tsx +5 -5
  86. package/ui/docs/DocumentationButtons.d.ts +3 -2
  87. package/ui/docs/DocumentationButtons.js +2 -2
  88. package/ui/docs/DocumentationButtons.tsx +4 -1
  89. package/ui/form/Field.d.ts +3 -3
  90. package/ui/form/Field.js +2 -2
  91. package/ui/form/Field.tsx +4 -3
  92. package/ui/form/Form.d.ts +2 -2
  93. package/ui/form/Form.js +3 -2
  94. package/ui/form/Form.tsx +5 -3
  95. package/ui/form/FormFields.d.ts +1 -1
  96. package/ui/form/FormFields.js +2 -2
  97. package/ui/form/FormFields.tsx +2 -2
  98. package/ui/form/FormFooter.d.ts +3 -3
  99. package/ui/form/FormFooter.js +2 -2
  100. package/ui/form/FormFooter.tsx +4 -4
  101. package/ui/form/FormMessage.d.ts +2 -2
  102. package/ui/form/FormMessage.tsx +2 -3
  103. package/ui/form/Progress.d.ts +3 -2
  104. package/ui/form/Progress.js +2 -2
  105. package/ui/form/Progress.tsx +4 -2
  106. package/ui/inline/Code.d.ts +3 -3
  107. package/ui/inline/Code.js +2 -2
  108. package/ui/inline/Code.tsx +4 -3
  109. package/ui/inline/Deleted.d.ts +3 -3
  110. package/ui/inline/Deleted.js +2 -2
  111. package/ui/inline/Deleted.tsx +4 -3
  112. package/ui/inline/Emphasis.d.ts +3 -3
  113. package/ui/inline/Emphasis.js +2 -2
  114. package/ui/inline/Emphasis.tsx +4 -3
  115. package/ui/inline/Inserted.d.ts +3 -3
  116. package/ui/inline/Inserted.js +2 -2
  117. package/ui/inline/Inserted.tsx +4 -3
  118. package/ui/inline/Link.d.ts +3 -2
  119. package/ui/inline/Link.js +2 -2
  120. package/ui/inline/Link.tsx +4 -2
  121. package/ui/inline/Mark.d.ts +3 -3
  122. package/ui/inline/Mark.js +2 -2
  123. package/ui/inline/Mark.tsx +4 -3
  124. package/ui/inline/Small.d.ts +3 -3
  125. package/ui/inline/Small.js +2 -2
  126. package/ui/inline/Small.tsx +4 -3
  127. package/ui/inline/Span.d.ts +3 -3
  128. package/ui/inline/Span.js +3 -2
  129. package/ui/inline/Span.tsx +5 -4
  130. package/ui/inline/Strong.d.ts +3 -3
  131. package/ui/inline/Strong.js +2 -2
  132. package/ui/inline/Strong.tsx +4 -3
  133. package/ui/inline/Subscript.d.ts +3 -3
  134. package/ui/inline/Subscript.js +3 -3
  135. package/ui/inline/Subscript.tsx +5 -5
  136. package/ui/inline/Superscript.d.ts +3 -3
  137. package/ui/inline/Superscript.js +3 -3
  138. package/ui/inline/Superscript.tsx +5 -5
  139. package/ui/input/ArrayInput.js +2 -2
  140. package/ui/input/ArrayInput.tsx +2 -1
  141. package/ui/input/ArrayRadioInputs.d.ts +1 -1
  142. package/ui/input/ArrayRadioInputs.js +2 -2
  143. package/ui/input/ArrayRadioInputs.tsx +2 -1
  144. package/ui/input/ButtonInput.d.ts +1 -1
  145. package/ui/input/ButtonInput.js +2 -2
  146. package/ui/input/ButtonInput.tsx +2 -1
  147. package/ui/input/CheckboxInput.d.ts +1 -1
  148. package/ui/input/CheckboxInput.js +2 -2
  149. package/ui/input/CheckboxInput.tsx +2 -0
  150. package/ui/input/ChoiceRadioInputs.js +2 -2
  151. package/ui/input/ChoiceRadioInputs.tsx +2 -1
  152. package/ui/input/DataInput.js +2 -2
  153. package/ui/input/DataInput.tsx +2 -1
  154. package/ui/input/DateInput.d.ts +1 -1
  155. package/ui/input/DateInput.js +2 -2
  156. package/ui/input/DateInput.tsx +2 -1
  157. package/ui/input/DictionaryInput.js +2 -2
  158. package/ui/input/DictionaryInput.tsx +2 -1
  159. package/ui/input/FileInput.d.ts +1 -1
  160. package/ui/input/FileInput.js +3 -2
  161. package/ui/input/FileInput.tsx +3 -1
  162. package/ui/input/Input.d.ts +10 -3
  163. package/ui/input/Input.js +2 -2
  164. package/ui/input/Input.tsx +11 -4
  165. package/ui/input/NumberInput.d.ts +1 -1
  166. package/ui/input/NumberInput.js +2 -2
  167. package/ui/input/NumberInput.tsx +2 -1
  168. package/ui/input/OutputInput.d.ts +1 -1
  169. package/ui/input/OutputInput.js +2 -2
  170. package/ui/input/OutputInput.tsx +2 -2
  171. package/ui/input/Popover.d.ts +4 -2
  172. package/ui/input/Popover.js +4 -3
  173. package/ui/input/Popover.tsx +6 -3
  174. package/ui/input/PopoverButton.d.ts +28 -0
  175. package/ui/input/{ButtonPopover.js → PopoverButton.js} +10 -8
  176. package/ui/input/PopoverButton.tsx +44 -0
  177. package/ui/input/PopoverButtonInput.d.ts +27 -0
  178. package/ui/input/{ButtonInputPopover.js → PopoverButtonInput.js} +10 -8
  179. package/ui/input/PopoverButtonInput.tsx +44 -0
  180. package/ui/input/QueryInput.d.ts +1 -1
  181. package/ui/input/QueryInput.js +2 -2
  182. package/ui/input/QueryInput.tsx +2 -0
  183. package/ui/input/RadioInput.d.ts +1 -1
  184. package/ui/input/RadioInput.js +2 -2
  185. package/ui/input/RadioInput.tsx +2 -0
  186. package/ui/input/SchemaInput.d.ts +1 -1
  187. package/ui/input/SchemaInput.js +2 -2
  188. package/ui/input/SchemaInput.tsx +2 -2
  189. package/ui/input/SelectInput.js +2 -2
  190. package/ui/input/SelectInput.tsx +2 -1
  191. package/ui/input/TextInput.d.ts +1 -1
  192. package/ui/input/TextInput.js +3 -3
  193. package/ui/input/TextInput.tsx +3 -2
  194. package/ui/input/index.d.ts +2 -2
  195. package/ui/input/index.js +2 -2
  196. package/ui/input/index.ts +2 -2
  197. package/ui/menu/Menu.d.ts +5 -5
  198. package/ui/menu/Menu.js +4 -4
  199. package/ui/menu/Menu.tsx +7 -6
  200. package/ui/misc/Icon.d.ts +2 -1
  201. package/ui/misc/Icon.js +2 -1
  202. package/ui/misc/Icon.tsx +3 -1
  203. package/ui/misc/Loading.d.ts +2 -3
  204. package/ui/misc/Loading.js +2 -2
  205. package/ui/misc/Loading.tsx +10 -3
  206. package/ui/misc/Tag.d.ts +3 -2
  207. package/ui/misc/Tag.js +2 -2
  208. package/ui/misc/Tag.tsx +4 -3
  209. package/ui/notice/Message.d.ts +3 -3
  210. package/ui/notice/Message.js +2 -2
  211. package/ui/notice/Message.tsx +4 -3
  212. package/ui/notice/Notice.d.ts +3 -3
  213. package/ui/notice/Notice.js +2 -2
  214. package/ui/notice/Notice.tsx +4 -2
  215. package/ui/notice/Notices.d.ts +3 -2
  216. package/ui/notice/Notices.js +2 -2
  217. package/ui/notice/Notices.tsx +4 -3
  218. package/ui/table/Cell.d.ts +3 -3
  219. package/ui/table/Cell.js +2 -2
  220. package/ui/table/Cell.tsx +4 -3
  221. package/ui/table/Table.d.ts +3 -3
  222. package/ui/table/Table.js +2 -2
  223. package/ui/table/Table.tsx +4 -3
  224. package/ui/tree/TreeBreadcrumbs.d.ts +3 -2
  225. package/ui/tree/TreeBreadcrumbs.js +2 -2
  226. package/ui/tree/TreeBreadcrumbs.tsx +4 -2
  227. package/ui/tree/TreeButton.d.ts +2 -1
  228. package/ui/tree/TreeButton.tsx +2 -1
  229. package/ui/util/ClassProps.md +56 -0
  230. package/ui/util/css.d.ts +1 -1
  231. package/ui/util/css.js +2 -2
  232. package/ui/util/css.test.ts +29 -0
  233. package/ui/util/css.ts +2 -2
  234. package/ui/util/props.d.ts +12 -0
  235. package/ui/util/props.test.tsx +100 -0
  236. package/ui/util/props.ts +13 -0
  237. package/ui/input/ButtonInputPopover.d.ts +0 -25
  238. package/ui/input/ButtonInputPopover.tsx +0 -41
  239. package/ui/input/ButtonPopover.d.ts +0 -25
  240. package/ui/input/ButtonPopover.tsx +0 -40
@@ -10,8 +10,8 @@ const SUBHEADING_CLASS = getModuleClass(SUBHEADING_CSS, "subheading");
10
10
  * @kind component
11
11
  * @see https://shelving.cc/ui/Subheading
12
12
  */
13
- export function Subheading({ level = "3", children, ...props }) {
13
+ export function Subheading({ level = "3", children, className, ...props }) {
14
14
  const Element = `h${level}`;
15
15
  return (_jsx(Element, { className: getClass(SUBHEADING_CLASS, //
16
- getBlockClass(props)), children: children }));
16
+ getBlockClass(props), className), children: children }));
17
17
  }
@@ -20,13 +20,14 @@ export type SubheadingProps = HeadingProps;
20
20
  * @kind component
21
21
  * @see https://shelving.cc/ui/Subheading
22
22
  */
23
- export function Subheading({ level = "3", children, ...props }: SubheadingProps): ReactElement {
23
+ export function Subheading({ level = "3", children, className, ...props }: SubheadingProps): ReactElement {
24
24
  const Element: `h${typeof level}` = `h${level}`;
25
25
  return (
26
26
  <Element
27
27
  className={getClass(
28
28
  SUBHEADING_CLASS, //
29
29
  getBlockClass(props),
30
+ className,
30
31
  )}
31
32
  >
32
33
  {children}
@@ -13,4 +13,4 @@ export type TitleProps = HeadingProps;
13
13
  * @kind component
14
14
  * @see https://shelving.cc/ui/Title
15
15
  */
16
- export declare function Title({ level, children, ...props }: TitleProps): ReactElement;
16
+ export declare function Title({ level, children, className, ...props }: TitleProps): ReactElement;
package/ui/block/Title.js CHANGED
@@ -10,8 +10,8 @@ const TITLE_CLASS = getModuleClass(TITLE_CSS, "title");
10
10
  * @kind component
11
11
  * @see https://shelving.cc/ui/Title
12
12
  */
13
- export function Title({ level = "1", children, ...props }) {
13
+ export function Title({ level = "1", children, className, ...props }) {
14
14
  const Element = `h${level}`;
15
15
  return (_jsx(Element, { className: getClass(TITLE_CLASS, //
16
- getBlockClass(props)), children: children }));
16
+ getBlockClass(props), className), children: children }));
17
17
  }
@@ -20,13 +20,14 @@ export type TitleProps = HeadingProps;
20
20
  * @kind component
21
21
  * @see https://shelving.cc/ui/Title
22
22
  */
23
- export function Title({ level = "1", children, ...props }: TitleProps): ReactElement {
23
+ export function Title({ level = "1", children, className, ...props }: TitleProps): ReactElement {
24
24
  const Element: `h${typeof level}` = `h${level}`;
25
25
  return (
26
26
  <Element
27
27
  className={getClass(
28
28
  TITLE_CLASS, //
29
29
  getBlockClass(props),
30
+ className,
30
31
  )}
31
32
  >
32
33
  {children}
@@ -1,13 +1,13 @@
1
1
  import { type ReactElement } from "react";
2
2
  import type { SpaceVariants } from "../style/Space.js";
3
3
  import type { WidthVariants } from "../style/Width.js";
4
- import type { OptionalChildProps } from "../util/props.js";
4
+ import type { ClassProps, OptionalChildProps } from "../util/props.js";
5
5
  /**
6
6
  * Props for `Video` — space and width variants plus optional `children`.
7
7
  *
8
8
  * @see https://shelving.cc/ui/VideoProps
9
9
  */
10
- export interface VideoProps extends SpaceVariants, WidthVariants, OptionalChildProps {
10
+ export interface VideoProps extends SpaceVariants, WidthVariants, OptionalChildProps, ClassProps {
11
11
  }
12
12
  /**
13
13
  * Video container element.
@@ -18,4 +18,4 @@ export interface VideoProps extends SpaceVariants, WidthVariants, OptionalChildP
18
18
  * @example <Video><video src="/clip.mp4" /></Video>
19
19
  * @see https://shelving.cc/ui/Video
20
20
  */
21
- export declare function Video({ children, ...props }: VideoProps): ReactElement;
21
+ export declare function Video({ children, className, ...props }: VideoProps): ReactElement;
package/ui/block/Video.js CHANGED
@@ -13,8 +13,8 @@ const VIDEO_CLASS = getModuleClass(VIDEO_CSS, "video");
13
13
  * @example <Video><video src="/clip.mp4" /></Video>
14
14
  * @see https://shelving.cc/ui/Video
15
15
  */
16
- export function Video({ children, ...props }) {
16
+ export function Video({ children, className, ...props }) {
17
17
  const ref = useRef(null);
18
18
  return (_jsx("figure", { ref: ref, className: getClass(VIDEO_CLASS, //
19
- getBlockClass(props)), children: children }));
19
+ getBlockClass(props), className), children: children }));
20
20
  }
@@ -3,7 +3,7 @@ import { getBlockClass } from "../style/Block.js";
3
3
  import type { SpaceVariants } from "../style/Space.js";
4
4
  import type { WidthVariants } from "../style/Width.js";
5
5
  import { getClass, getModuleClass } from "../util/css.js";
6
- import type { OptionalChildProps } from "../util/props.js";
6
+ import type { ClassProps, OptionalChildProps } from "../util/props.js";
7
7
  import VIDEO_CSS from "./Video.module.css";
8
8
 
9
9
  const VIDEO_CLASS = getModuleClass(VIDEO_CSS, "video");
@@ -13,7 +13,7 @@ const VIDEO_CLASS = getModuleClass(VIDEO_CSS, "video");
13
13
  *
14
14
  * @see https://shelving.cc/ui/VideoProps
15
15
  */
16
- export interface VideoProps extends SpaceVariants, WidthVariants, OptionalChildProps {}
16
+ export interface VideoProps extends SpaceVariants, WidthVariants, OptionalChildProps, ClassProps {}
17
17
 
18
18
  /**
19
19
  * Video container element.
@@ -24,7 +24,7 @@ export interface VideoProps extends SpaceVariants, WidthVariants, OptionalChildP
24
24
  * @example <Video><video src="/clip.mp4" /></Video>
25
25
  * @see https://shelving.cc/ui/Video
26
26
  */
27
- export function Video({ children, ...props }: VideoProps): ReactElement {
27
+ export function Video({ children, className, ...props }: VideoProps): ReactElement {
28
28
  const ref = useRef<HTMLElement | null>(null);
29
29
 
30
30
  return (
@@ -33,6 +33,7 @@ export function Video({ children, ...props }: VideoProps): ReactElement {
33
33
  className={getClass(
34
34
  VIDEO_CLASS, //
35
35
  getBlockClass(props),
36
+ className,
36
37
  )}
37
38
  >
38
39
  {children}
@@ -2,6 +2,7 @@ import type { ReactElement } from "react";
2
2
  import { type FlexVariants } from "../style/Flex.js";
3
3
  import { type StatusVariants } from "../style/Status.js";
4
4
  import { type TypographyVariants } from "../style/Typography.js";
5
+ import type { ClassProps } from "../util/props.js";
5
6
  import { type ClickableProps } from "./Clickable.js";
6
7
  /**
7
8
  * Styling variants for a `Button`, combining flex, color, status, and typography options with button-specific toggles.
@@ -33,7 +34,7 @@ export declare function getButtonClass(variants: ButtonVariants): string;
33
34
  *
34
35
  * @see https://shelving.cc/ui/ButtonProps
35
36
  */
36
- export interface ButtonProps extends ButtonVariants, ClickableProps {
37
+ export interface ButtonProps extends ButtonVariants, ClickableProps, ClassProps {
37
38
  }
38
39
  /**
39
40
  * Render either a `<button>` or an `<a href="">` styled as a button, based on whether an `onClick` or `href` prop is provided.
@@ -43,4 +44,4 @@ export interface ButtonProps extends ButtonVariants, ClickableProps {
43
44
  * @kind component
44
45
  * @see https://shelving.cc/ui/Button
45
46
  */
46
- export declare function Button(props: ButtonProps): ReactElement;
47
+ export declare function Button({ className, ...props }: ButtonProps): ReactElement;
@@ -23,6 +23,6 @@ export function getButtonClass(variants) {
23
23
  * @kind component
24
24
  * @see https://shelving.cc/ui/Button
25
25
  */
26
- export function Button(props) {
27
- return _jsx(Clickable, { ...props, className: getButtonClass(props) });
26
+ export function Button({ className, ...props }) {
27
+ return _jsx(Clickable, { ...props, className: getClass(getButtonClass(props), className) });
28
28
  }
@@ -8,6 +8,7 @@ A clickable styled as a solid button. Renders an `<a href="">` when given `href`
8
8
  - `strong` marks the default action in a form — a filled background instead of an outline. `plain` and `outline` drop the background until hover/focus.
9
9
  - `color=` / `status=` move the tint anchor, so the background, border and label re-derive from the same ladder; `small` tightens the padding.
10
10
  - `getButtonClass(variants)` returns the same `className` the component composes — use it to style a non-`<button>` element as a button when `Button` itself doesn't fit.
11
+ - `className` attaches an app class to one button, merged after the computed classes so an app stylesheet wins — see `ClassProps`.
11
12
 
12
13
  ## Usage
13
14
 
@@ -33,6 +34,15 @@ import { Row } from "shelving/ui";
33
34
  </Row>
34
35
  ```
35
36
 
37
+ ### A one-off treatment
38
+
39
+ ```tsx
40
+ import { Button } from "shelving/ui";
41
+
42
+ // `.spongy-press` lives in the app's own stylesheet — use it for what the theme hooks below can't express.
43
+ <Button className="spongy-press" strong onClick={claim}>Claim</Button>
44
+ ```
45
+
36
46
  ### Reusing the button class
37
47
 
38
48
  ```tsx
@@ -50,11 +60,14 @@ import { getButtonClass } from "shelving/ui";
50
60
 
51
61
  `--button-padding` and `--button-small-padding` set the `padding` shorthand, so a single value pads both axes equally and a two-value override pads block and inline separately (e.g. `var(--space-small) var(--space-normal)`).
52
62
 
63
+ `--button-shadow`, `--button-hover-transform` and the `--button-active-*` pressed-state hooks are static and apply to every button, with one exception: `plain` and `outline` never paint a box shadow in any state — they have no fill until hover, so a raised edge under them reads broken. The hover and pressed transforms still apply to them, so all buttons move together. `--button-transition` already covers animating the press and release.
64
+
53
65
  | Variable | Styles | Default |
54
66
  |---|---|---|
55
67
  | `--button-background` | Surface fill | `var(--tint-90)` |
56
68
  | `--button-hover-background` | Surface fill on hover / focus | `var(--tint-95)` |
57
69
  | `--button-hover-border` | Border on hover / focus | `var(--button-stroke) solid var(--tint-90)` |
70
+ | `--button-hover-transform` | Transform on hover / focus | `none` |
58
71
  | `--button-text` | Label colour | `var(--tint-50)` |
59
72
  | `--button-border` | Border shorthand | `var(--button-stroke) solid var(--tint-80)` |
60
73
  | `--button-stroke` | Border / outline thickness | `var(--stroke-normal)` (2px) |
@@ -68,6 +81,11 @@ import { getButtonClass } from "shelving/ui";
68
81
  | `--button-weight` | Font weight | `var(--weight-strong)` (700) |
69
82
  | `--button-size` | Font size | `var(--size-normal)` |
70
83
  | `--button-leading` | Line height | `var(--leading)` |
84
+ | `--button-shadow` | Box shadow (never on `plain` / `outline`) | `none` |
85
+ | `--button-active-background` | Surface fill while pressed | `var(--button-hover-background)` |
86
+ | `--button-active-border` | Border while pressed | `var(--button-hover-border)` |
87
+ | `--button-active-shadow` | Box shadow while pressed | `var(--button-shadow)` |
88
+ | `--button-active-transform` | Transform while pressed | `var(--button-hover-transform)` |
71
89
  | `--button-transition` | Transition | `all var(--duration-fast)` (150ms) |
72
90
  | `--button-focus-border` | Focus outline | `var(--stroke-focus) solid var(--color-focus)` |
73
91
  | `--button-disabled-opacity` | Opacity when disabled | `0.5` |
@@ -84,3 +102,13 @@ import { getButtonClass } from "shelving/ui";
84
102
  --button-padding: var(--space-small) var(--space-normal);
85
103
  }
86
104
  ```
105
+
106
+ ```css
107
+ /* Theme: buttons are raised and press down flat — `plain` and `outline` press down too but never cast a shadow. */
108
+ :root {
109
+ --button-shadow: 0 0.25rem 0 var(--tint-80);
110
+ --button-active-transform: translateY(0.2rem);
111
+ --button-active-shadow: 0 0.05rem 0 var(--tint-80);
112
+ --button-transition: all var(--duration-fast) cubic-bezier(0.34, 1.56, 0.64, 1);
113
+ }
114
+ ```
@@ -31,6 +31,7 @@
31
31
  /* Style. */
32
32
  background: var(--button-background, var(--tint-90));
33
33
  color: var(--button-text, var(--tint-50));
34
+ box-shadow: var(--button-shadow, none);
34
35
  transition: var(--button-transition, all var(--duration-fast));
35
36
  cursor: pointer;
36
37
  outline: var(--button-focus-border, var(--stroke-focus) solid var(--color-focus));
@@ -55,6 +56,15 @@
55
56
  &:focus:not(:focus-visible) {
56
57
  border: var(--button-hover-border, var(--button-stroke, var(--stroke-normal)) solid var(--tint-90));
57
58
  background: var(--button-hover-background, var(--tint-95));
59
+ transform: var(--button-hover-transform, none);
60
+ }
61
+
62
+ &:enabled:active,
63
+ &:any-link:active {
64
+ border: var(--button-active-border, var(--button-hover-border, var(--button-stroke, var(--stroke-normal)) solid var(--tint-90)));
65
+ background: var(--button-active-background, var(--button-hover-background, var(--tint-95)));
66
+ transform: var(--button-active-transform, var(--button-hover-transform, none));
67
+ box-shadow: var(--button-active-shadow, var(--button-shadow, none));
58
68
  }
59
69
 
60
70
  /* Variants */
@@ -101,13 +111,21 @@
101
111
  border-color: transparent;
102
112
  }
103
113
 
104
- &.plain:not(:enabled:hover, :any-link:hover, :focus) {
105
- border-color: transparent;
106
- background: transparent;
114
+ &.plain {
115
+ box-shadow: none;
116
+
117
+ &:not(:enabled:hover, :any-link:hover, :focus) {
118
+ border-color: transparent;
119
+ background: transparent;
120
+ }
107
121
  }
108
122
 
109
- &.outline:not(:enabled:hover, :any-link:hover, :focus) {
110
- background: transparent;
123
+ &.outline {
124
+ box-shadow: none;
125
+
126
+ &:not(:enabled:hover, :any-link:hover, :focus) {
127
+ background: transparent;
128
+ }
111
129
  }
112
130
  }
113
131
  }
@@ -3,6 +3,7 @@ import { type FlexVariants, getFlexClass } from "../style/Flex.js";
3
3
  import { getStatusClass, type StatusVariants } from "../style/Status.js";
4
4
  import { getTypographyClass, type TypographyVariants } from "../style/Typography.js";
5
5
  import { getClass, getModuleClass } from "../util/css.js";
6
+ import type { ClassProps } from "../util/props.js";
6
7
  import BUTTON_CSS from "./Button.module.css";
7
8
  import { Clickable, type ClickableProps } from "./Clickable.js";
8
9
 
@@ -45,7 +46,7 @@ export function getButtonClass(variants: ButtonVariants): string {
45
46
  *
46
47
  * @see https://shelving.cc/ui/ButtonProps
47
48
  */
48
- export interface ButtonProps extends ButtonVariants, ClickableProps {}
49
+ export interface ButtonProps extends ButtonVariants, ClickableProps, ClassProps {}
49
50
 
50
51
  /**
51
52
  * Render either a `<button>` or an `<a href="">` styled as a button, based on whether an `onClick` or `href` prop is provided.
@@ -55,6 +56,6 @@ export interface ButtonProps extends ButtonVariants, ClickableProps {}
55
56
  * @kind component
56
57
  * @see https://shelving.cc/ui/Button
57
58
  */
58
- export function Button(props: ButtonProps): ReactElement {
59
- return <Clickable {...props} className={getButtonClass(props)} />;
59
+ export function Button({ className, ...props }: ButtonProps): ReactElement {
60
+ return <Clickable {...props} className={getClass(getButtonClass(props), className)} />;
60
61
  }
@@ -1,7 +1,7 @@
1
1
  import type { MouseEvent, ReactElement, ReactNode } from "react";
2
2
  import type { Path } from "../../util/path.js";
3
3
  import type { ImmutableURI, URIString } from "../../util/uri.js";
4
- import type { OptionalChildProps } from "../util/props.js";
4
+ import type { ClassProps, OptionalChildProps } from "../util/props.js";
5
5
  /**
6
6
  * Handler for a clickable `onClick` event.
7
7
  * - Returned value (if defined) is notified to the user using `notifySuccess()`
@@ -36,12 +36,12 @@ export interface ClickableProps extends OptionalChildProps {
36
36
  *
37
37
  * @see https://shelving.cc/ui/StylableClickableProps
38
38
  */
39
- export interface StylableClickableProps extends ClickableProps {
40
- className?: string | undefined;
39
+ export interface StylableClickableProps extends ClickableProps, ClassProps {
41
40
  }
42
41
  /**
43
42
  * Render either a `<button>`, an `<a href="">`, or a plain `<span>` based on whether an `onClick` or `href` prop is provided.
44
43
  * - `href` renders a `LinkClickable`; `onClick` renders a `ButtonClickable`; neither renders a `SpanClickable`.
44
+ * - Dispatches on defined values (not key presence) so wrappers can always spread `href={href}` through, even when it's `undefined`.
45
45
  *
46
46
  * @kind component
47
47
  * @example <Clickable href="/about">About</Clickable>
@@ -6,10 +6,12 @@ import { isURLActive } from "../../util/index.js";
6
6
  import { getLink } from "../../util/link.js";
7
7
  import { LOADING } from "../misc/Loading.js";
8
8
  import { requireMeta } from "../misc/MetaContext.js";
9
+ import { getClass } from "../util/css.js";
9
10
  import { callNotifiedElement } from "../util/notice.js";
10
11
  /**
11
12
  * Render either a `<button>`, an `<a href="">`, or a plain `<span>` based on whether an `onClick` or `href` prop is provided.
12
13
  * - `href` renders a `LinkClickable`; `onClick` renders a `ButtonClickable`; neither renders a `SpanClickable`.
14
+ * - Dispatches on defined values (not key presence) so wrappers can always spread `href={href}` through, even when it's `undefined`.
13
15
  *
14
16
  * @kind component
15
17
  * @example <Clickable href="/about">About</Clickable>
@@ -17,7 +19,7 @@ import { callNotifiedElement } from "../util/notice.js";
17
19
  * @see https://shelving.cc/ui/Clickable
18
20
  */
19
21
  export function Clickable(props) {
20
- return "href" in props ? (_jsx(LinkClickable, { ...props })) : "onClick" in props ? (_jsx(ButtonClickable, { ...props })) : (_jsx(SpanClickable, { ...props }));
22
+ return props.href !== undefined ? (_jsx(LinkClickable, { ...props })) : props.onClick !== undefined ? (_jsx(ButtonClickable, { ...props })) : (_jsx(SpanClickable, { ...props }));
21
23
  }
22
24
  /**
23
25
  * Render an `<a href="">` element, resolving its `href` against the current page URL and marking it active when it matches.
@@ -33,7 +35,7 @@ export function LinkClickable({ href, disabled = !href, target, download, title,
33
35
  const link = disabled ? undefined : getLink(href, url, root);
34
36
  // Is this link "active" compared to the current URL?
35
37
  const active = isURLActive(link, url);
36
- return (_jsx("a", { href: link?.href, title: title, download: download, target: target, className: className, "aria-current": active ? "page" : undefined, children: children }));
38
+ return (_jsx("a", { href: link?.href, title: title, download: download, target: target, className: getClass(className) || undefined, "aria-current": active ? "page" : undefined, children: children }));
37
39
  }
38
40
  /**
39
41
  * Render a `<button>` element that runs its `onClick` handler through a `BusyStore` and shows a loading spinner while busy.
@@ -48,7 +50,7 @@ export function ButtonClickable({ onClick, disabled = !onClick, title, children
48
50
  const store = useInstance(BusyStore, undefined);
49
51
  // Track the `busy/unbusy` state of the button to show a loading spinner appropriately.
50
52
  const busy = useStore(store.busy).value;
51
- return (_jsx("button", { type: "button", title: title, disabled: busy || disabled, className: className, onClick: disabled
53
+ return (_jsx("button", { type: "button", title: title, disabled: busy || disabled, className: getClass(className) || undefined, onClick: disabled
52
54
  ? undefined
53
55
  : e => {
54
56
  if (!store.busy.value && onClick) {
@@ -65,5 +67,5 @@ export function ButtonClickable({ onClick, disabled = !onClick, title, children
65
67
  */
66
68
  export function SpanClickable({ title, children = "Click", //
67
69
  className, }) {
68
- return (_jsx("span", { title: title, className: className, children: children }));
70
+ return (_jsx("span", { title: title, className: getClass(className) || undefined, children: children }));
69
71
  }
@@ -8,8 +8,9 @@ import type { Path } from "../../util/path.js";
8
8
  import type { ImmutableURI, URIString } from "../../util/uri.js";
9
9
  import { LOADING } from "../misc/Loading.js";
10
10
  import { requireMeta } from "../misc/MetaContext.js";
11
+ import { getClass } from "../util/css.js";
11
12
  import { callNotifiedElement } from "../util/notice.js";
12
- import type { OptionalChildProps } from "../util/props.js";
13
+ import type { ClassProps, OptionalChildProps } from "../util/props.js";
13
14
 
14
15
  /**
15
16
  * Handler for a clickable `onClick` event.
@@ -47,13 +48,12 @@ export interface ClickableProps extends OptionalChildProps {
47
48
  *
48
49
  * @see https://shelving.cc/ui/StylableClickableProps
49
50
  */
50
- export interface StylableClickableProps extends ClickableProps {
51
- className?: string | undefined;
52
- }
51
+ export interface StylableClickableProps extends ClickableProps, ClassProps {}
53
52
 
54
53
  /**
55
54
  * Render either a `<button>`, an `<a href="">`, or a plain `<span>` based on whether an `onClick` or `href` prop is provided.
56
55
  * - `href` renders a `LinkClickable`; `onClick` renders a `ButtonClickable`; neither renders a `SpanClickable`.
56
+ * - Dispatches on defined values (not key presence) so wrappers can always spread `href={href}` through, even when it's `undefined`.
57
57
  *
58
58
  * @kind component
59
59
  * @example <Clickable href="/about">About</Clickable>
@@ -61,9 +61,9 @@ export interface StylableClickableProps extends ClickableProps {
61
61
  * @see https://shelving.cc/ui/Clickable
62
62
  */
63
63
  export function Clickable(props: StylableClickableProps): ReactElement {
64
- return "href" in props ? (
64
+ return props.href !== undefined ? (
65
65
  <LinkClickable {...props} />
66
- ) : "onClick" in props ? (
66
+ ) : props.onClick !== undefined ? (
67
67
  <ButtonClickable {...props} />
68
68
  ) : (
69
69
  <SpanClickable {...props} />
@@ -100,7 +100,7 @@ export function LinkClickable({
100
100
  title={title}
101
101
  download={download}
102
102
  target={target}
103
- className={className}
103
+ className={getClass(className) || undefined}
104
104
  aria-current={active ? "page" : undefined}
105
105
  >
106
106
  {children}
@@ -134,7 +134,7 @@ export function ButtonClickable({
134
134
  type="button"
135
135
  title={title}
136
136
  disabled={busy || disabled}
137
- className={className}
137
+ className={getClass(className) || undefined}
138
138
  onClick={
139
139
  disabled
140
140
  ? undefined
@@ -165,7 +165,7 @@ export function SpanClickable({
165
165
  return (
166
166
  <span //
167
167
  title={title}
168
- className={className}
168
+ className={getClass(className) || undefined}
169
169
  >
170
170
  {children}
171
171
  </span>
@@ -1,11 +1,12 @@
1
1
  import { type ReactElement } from "react";
2
+ import type { ClassProps } from "../util/props.js";
2
3
  import { type ButtonVariants } from "./Button.js";
3
4
  /**
4
5
  * Component props for a `<FullscreenButton>`
5
6
  *
6
7
  * @see https://shelving.cc/ui/FullscreenButtonProps
7
8
  */
8
- export interface FullscreenButtonProps extends ButtonVariants {
9
+ export interface FullscreenButtonProps extends ButtonVariants, ClassProps {
9
10
  /**
10
11
  * CSS selector to identify the parent element to toggle fullscreen status on.
11
12
  * @default "body, figure"
@@ -25,7 +26,4 @@ export interface FullscreenButtonProps extends ButtonVariants {
25
26
  * @example <Video><video src="/clip.mp4" /><FullscreenButton /></Video>
26
27
  * @see https://shelving.cc/ui/FullscreenButton
27
28
  */
28
- export declare function FullscreenButton({ target, ...props }: {
29
- [x: string]: any;
30
- target?: string | undefined;
31
- }): ReactElement | null;
29
+ export declare function FullscreenButton({ target, ...props }: FullscreenButtonProps): ReactElement | null;
@@ -1,5 +1,6 @@
1
1
  import { ArrowsPointingInIcon, ArrowsPointingOutIcon } from "@heroicons/react/16/solid";
2
2
  import { type ReactElement, useEffect, useState } from "react";
3
+ import type { ClassProps } from "../util/props.js";
3
4
  import { Button, type ButtonVariants } from "./Button.js";
4
5
 
5
6
  /**
@@ -7,7 +8,7 @@ import { Button, type ButtonVariants } from "./Button.js";
7
8
  *
8
9
  * @see https://shelving.cc/ui/FullscreenButtonProps
9
10
  */
10
- export interface FullscreenButtonProps extends ButtonVariants {
11
+ export interface FullscreenButtonProps extends ButtonVariants, ClassProps {
11
12
  /**
12
13
  * CSS selector to identify the parent element to toggle fullscreen status on.
13
14
  * @default "body, figure"
@@ -28,7 +29,7 @@ export interface FullscreenButtonProps extends ButtonVariants {
28
29
  * @example <Video><video src="/clip.mp4" /><FullscreenButton /></Video>
29
30
  * @see https://shelving.cc/ui/FullscreenButton
30
31
  */
31
- export function FullscreenButton({ target = "body, figure", ...props }): ReactElement | null {
32
+ export function FullscreenButton({ target = "body, figure", ...props }: FullscreenButtonProps): ReactElement | null {
32
33
  const [isFull, setFull] = useState(() => typeof document !== "undefined" && !!document.fullscreenElement);
33
34
 
34
35
  useEffect(() => {
@@ -1,6 +1,6 @@
1
1
  import { type ReactElement } from "react";
2
2
  import type { Callback } from "../../util/function.js";
3
- import type { OptionalChildProps } from "../util/index.js";
3
+ import type { ClassProps, OptionalChildProps } from "../util/index.js";
4
4
  import { type ButtonVariants } from "./Button.js";
5
5
  /**
6
6
  * Context for providing a "retry" callback to descendant `<RetryButton>` elements.
@@ -15,7 +15,7 @@ export declare const RetryContext: import("react").Context<Callback | undefined>
15
15
  *
16
16
  * @see https://shelving.cc/ui/RetryButtonProps
17
17
  */
18
- export interface RetryButtonProps extends ButtonVariants, OptionalChildProps {
18
+ export interface RetryButtonProps extends ButtonVariants, OptionalChildProps, ClassProps {
19
19
  }
20
20
  /**
21
21
  * Button that retries the nearest `<Catcher>` error boundary when clicked.
@@ -1,7 +1,7 @@
1
1
  import { ArrowPathIcon } from "@heroicons/react/16/solid";
2
2
  import { createContext, type ReactElement, use } from "react";
3
3
  import type { Callback } from "../../util/function.js";
4
- import type { OptionalChildProps } from "../util/index.js";
4
+ import type { ClassProps, OptionalChildProps } from "../util/index.js";
5
5
  import { Button, type ButtonVariants } from "./Button.js";
6
6
 
7
7
  /**
@@ -19,7 +19,7 @@ RetryContext.displayName = "RetryContext";
19
19
  *
20
20
  * @see https://shelving.cc/ui/RetryButtonProps
21
21
  */
22
- export interface RetryButtonProps extends ButtonVariants, OptionalChildProps {}
22
+ export interface RetryButtonProps extends ButtonVariants, OptionalChildProps, ClassProps {}
23
23
 
24
24
  const _RETRY_CHILDREN = (
25
25
  <>
@@ -1,5 +1,5 @@
1
1
  import type { ReactElement } from "react";
2
- import type { OptionalChildProps } from "../util/props.js";
2
+ import type { ClassProps, OptionalChildProps } from "../util/props.js";
3
3
  import { type ButtonVariants } from "./Button.js";
4
4
  /**
5
5
  * Component props for `<SubmitButton>`, a form submit button.
@@ -10,7 +10,7 @@ import { type ButtonVariants } from "./Button.js";
10
10
  *
11
11
  * @see https://shelving.cc/ui/SubmitButtonProps
12
12
  */
13
- export interface SubmitButtonProps extends ButtonVariants, OptionalChildProps {
13
+ export interface SubmitButtonProps extends ButtonVariants, OptionalChildProps, ClassProps {
14
14
  }
15
15
  /**
16
16
  * Submit button for a form that disables itself and shows a spinner while the form is busy.
@@ -20,4 +20,4 @@ export interface SubmitButtonProps extends ButtonVariants, OptionalChildProps {
20
20
  * @example <SubmitButton>Save changes</SubmitButton>
21
21
  * @see https://shelving.cc/ui/SubmitButton
22
22
  */
23
- export declare function SubmitButton({ children, strong, color, full, ...props }: SubmitButtonProps): ReactElement;
23
+ export declare function SubmitButton({ children, strong, color, full, className, ...props }: SubmitButtonProps): ReactElement;
@@ -3,6 +3,7 @@ import { ArrowRightIcon } from "@heroicons/react/24/solid";
3
3
  import { useStore } from "../../react/useStore.js";
4
4
  import { requireForm } from "../form/FormContext.js";
5
5
  import { LOADING } from "../misc/Loading.js";
6
+ import { getClass } from "../util/css.js";
6
7
  import { getButtonClass } from "./Button.js";
7
8
  const _SUBMIT_CHILDREN = (_jsxs(_Fragment, { children: ["Save", _jsx(ArrowRightIcon, {})] }));
8
9
  /**
@@ -13,8 +14,8 @@ const _SUBMIT_CHILDREN = (_jsxs(_Fragment, { children: ["Save", _jsx(ArrowRightI
13
14
  * @example <SubmitButton>Save changes</SubmitButton>
14
15
  * @see https://shelving.cc/ui/SubmitButton
15
16
  */
16
- export function SubmitButton({ children = _SUBMIT_CHILDREN, strong = true, color = "primary", full = true, ...props }) {
17
+ export function SubmitButton({ children = _SUBMIT_CHILDREN, strong = true, color = "primary", full = true, className, ...props }) {
17
18
  const form = requireForm();
18
19
  const busy = useStore(form.busy).value;
19
- return (_jsx("button", { type: "submit", disabled: busy, className: getButtonClass({ strong, color, full, ...props }), children: busy ? LOADING : children }));
20
+ return (_jsx("button", { type: "submit", disabled: busy, className: getClass(getButtonClass({ strong, color, full, ...props }), className), children: busy ? LOADING : children }));
20
21
  }
@@ -3,7 +3,8 @@ import type { ReactElement } from "react";
3
3
  import { useStore } from "../../react/useStore.js";
4
4
  import { requireForm } from "../form/FormContext.js";
5
5
  import { LOADING } from "../misc/Loading.js";
6
- import type { OptionalChildProps } from "../util/props.js";
6
+ import { getClass } from "../util/css.js";
7
+ import type { ClassProps, OptionalChildProps } from "../util/props.js";
7
8
  import { type ButtonVariants, getButtonClass } from "./Button.js";
8
9
 
9
10
  /**
@@ -15,7 +16,7 @@ import { type ButtonVariants, getButtonClass } from "./Button.js";
15
16
  *
16
17
  * @see https://shelving.cc/ui/SubmitButtonProps
17
18
  */
18
- export interface SubmitButtonProps extends ButtonVariants, OptionalChildProps {}
19
+ export interface SubmitButtonProps extends ButtonVariants, OptionalChildProps, ClassProps {}
19
20
 
20
21
  const _SUBMIT_CHILDREN = (
21
22
  <>
@@ -37,12 +38,13 @@ export function SubmitButton({
37
38
  strong = true,
38
39
  color = "primary",
39
40
  full = true,
41
+ className,
40
42
  ...props
41
43
  }: SubmitButtonProps): ReactElement {
42
44
  const form = requireForm();
43
45
  const busy = useStore(form.busy).value;
44
46
  return (
45
- <button type="submit" disabled={busy} className={getButtonClass({ strong, color, full, ...props })}>
47
+ <button type="submit" disabled={busy} className={getClass(getButtonClass({ strong, color, full, ...props }), className)}>
46
48
  {busy ? LOADING : children}
47
49
  </button>
48
50
  );
@@ -1,7 +1,7 @@
1
1
  import { type ReactElement } from "react";
2
2
  import type { Callback } from "../../util/function.js";
3
- import type { ButtonVariants } from "../button/Button.js";
4
- import type { OptionalChildProps } from "../util/props.js";
3
+ import { type ButtonVariants } from "../button/Button.js";
4
+ import type { ClassProps, OptionalChildProps } from "../util/props.js";
5
5
  /**
6
6
  * Props for `<Dialog>` — optional `children` content and an `onClose` callback.
7
7
  *
@@ -25,12 +25,14 @@ export declare const Dialog: import("react").MemoExoticComponent<({ children, on
25
25
  *
26
26
  * @see https://shelving.cc/ui/DialogCloseButtonProps
27
27
  */
28
- export interface DialogCloseButtonProps extends ButtonVariants, OptionalChildProps {
28
+ export interface DialogCloseButtonProps extends ButtonVariants, OptionalChildProps, ClassProps {
29
29
  }
30
30
  /**
31
31
  * Button that closes its wrapping `<dialog>`, showing an X icon by default.
32
+ * - Styled with the shared `Button` classes (positioning comes from the `<Dialog>`'s own `.close` wrapper rules).
33
+ * - `plain` by default so only the icon shows until hover or focus — pass `plain={false}` for a solid button.
32
34
  *
33
35
  * @kind component
34
36
  * @see https://shelving.cc/ui/DialogCloseButton
35
37
  */
36
- export declare function DialogCloseButton({ children, ...variants }: DialogCloseButtonProps): ReactElement;
38
+ export declare function DialogCloseButton({ children, plain, className, ...variants }: DialogCloseButtonProps): ReactElement;