@ahrowe/ui 0.23.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/esm/common/alert/alert.types.mjs.map +1 -1
  2. package/dist/esm/common/badge/badge.types.mjs.map +1 -1
  3. package/dist/esm/common/button/button.mjs +1 -1
  4. package/dist/esm/common/button/button.mjs.map +1 -1
  5. package/dist/esm/common/button/button.module.mjs +1 -1
  6. package/dist/esm/common/button/button.module.mjs.map +1 -1
  7. package/dist/esm/common/button/button.types.mjs +1 -1
  8. package/dist/esm/common/button/button.types.mjs.map +1 -1
  9. package/dist/esm/common/card/Card.mjs +1 -1
  10. package/dist/esm/common/card/Card.mjs.map +1 -1
  11. package/dist/esm/common/card/card.types.mjs.map +1 -1
  12. package/dist/esm/common/carousel/carousel.mjs +1 -1
  13. package/dist/esm/common/carousel/carousel.mjs.map +1 -1
  14. package/dist/esm/common/chip/chip.mjs +1 -1
  15. package/dist/esm/common/chip/chip.mjs.map +1 -1
  16. package/dist/esm/common/chip/chip.types.mjs.map +1 -1
  17. package/dist/esm/common/dropZone/dropZone.types.mjs.map +1 -1
  18. package/dist/esm/common/floatingMenu/floatingMenu.types.mjs.map +1 -1
  19. package/dist/esm/common/hooks/useCoarsePointer.mjs +2 -0
  20. package/dist/esm/common/hooks/useCoarsePointer.mjs.map +1 -0
  21. package/dist/esm/common/hooks/useSwipeDismiss.mjs +1 -1
  22. package/dist/esm/common/hooks/useSwipeDismiss.mjs.map +1 -1
  23. package/dist/esm/common/iconPicker/iconPicker.mjs +1 -1
  24. package/dist/esm/common/iconPicker/iconPicker.mjs.map +1 -1
  25. package/dist/esm/common/input/input.types.mjs.map +1 -1
  26. package/dist/esm/common/interactableDiv/interactableDiv.mjs +1 -1
  27. package/dist/esm/common/interactableDiv/interactableDiv.mjs.map +1 -1
  28. package/dist/esm/common/numberInput/index.mjs.map +1 -1
  29. package/dist/esm/common/numberInput/numberInput.mjs +1 -1
  30. package/dist/esm/common/numberInput/numberInput.mjs.map +1 -1
  31. package/dist/esm/common/numberInput/numberInput.module.mjs +1 -1
  32. package/dist/esm/common/numberInput/numberInput.module.mjs.map +1 -1
  33. package/dist/esm/common/numberInput/useStepRepeat.mjs +2 -0
  34. package/dist/esm/common/numberInput/useStepRepeat.mjs.map +1 -0
  35. package/dist/esm/common/otpInput/otpInput.types.mjs.map +1 -1
  36. package/dist/esm/common/popover/popover.types.mjs.map +1 -1
  37. package/dist/esm/common/radioGroup/radioGroup.types.mjs.map +1 -1
  38. package/dist/esm/common/sectionHeader/sectionHeader.types.mjs.map +1 -1
  39. package/dist/index.cjs +6 -6
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/style.css +1 -1
  42. package/dist/types/package/common/alert/alert.types.d.ts +1 -1
  43. package/dist/types/package/common/animatedLogo/animatedLogo.types.d.ts +11 -3
  44. package/dist/types/package/common/avatar/avatar.types.d.ts +1 -1
  45. package/dist/types/package/common/badge/badge.types.d.ts +1 -1
  46. package/dist/types/package/common/button/button.types.d.ts +10 -4
  47. package/dist/types/package/common/card/card.types.d.ts +1 -1
  48. package/dist/types/package/common/carousel/carousel.types.d.ts +1 -1
  49. package/dist/types/package/common/checkbox/checkbox.types.d.ts +1 -1
  50. package/dist/types/package/common/chip/chip.types.d.ts +2 -0
  51. package/dist/types/package/common/colorPicker/colorPicker.types.d.ts +1 -1
  52. package/dist/types/package/common/configProvider/configProvider.types.d.ts +1 -1
  53. package/dist/types/package/common/dropZone/dropZone.types.d.ts +1 -1
  54. package/dist/types/package/common/dropdown/dropdown.types.d.ts +1 -1
  55. package/dist/types/package/common/errorBoundary/errorBoundary.types.d.ts +1 -1
  56. package/dist/types/package/common/floatingMenu/floatingMenu.types.d.ts +1 -1
  57. package/dist/types/package/common/hooks/useCoarsePointer.d.ts +10 -0
  58. package/dist/types/package/common/iconPicker/iconPicker.types.d.ts +1 -1
  59. package/dist/types/package/common/input/input.types.d.ts +2 -2
  60. package/dist/types/package/common/inputDropdown/inputDropdown.types.d.ts +1 -1
  61. package/dist/types/package/common/interactableDiv/interactableDiv.d.ts +2 -2
  62. package/dist/types/package/common/kanbanBoard/kanbanBoard.types.d.ts +1 -1
  63. package/dist/types/package/common/numberInput/index.d.ts +1 -0
  64. package/dist/types/package/common/numberInput/numberInput.d.ts +3 -32
  65. package/dist/types/package/common/numberInput/numberInput.types.d.ts +61 -0
  66. package/dist/types/package/common/numberInput/useStepRepeat.d.ts +17 -0
  67. package/dist/types/package/common/optionPicker/optionPicker.types.d.ts +1 -1
  68. package/dist/types/package/common/otpInput/otpInput.types.d.ts +1 -1
  69. package/dist/types/package/common/popover/popover.types.d.ts +1 -1
  70. package/dist/types/package/common/radioGroup/radioGroup.types.d.ts +1 -1
  71. package/dist/types/package/common/rating/rating.types.d.ts +1 -1
  72. package/dist/types/package/common/searchInput/searchInput.types.d.ts +1 -1
  73. package/dist/types/package/common/sectionHeader/sectionHeader.types.d.ts +1 -1
  74. package/dist/types/package/common/slider/slider.types.d.ts +1 -1
  75. package/dist/types/package/common/switch/switch.types.d.ts +1 -1
  76. package/dist/types/package/common/tabHeader/tabHeader.types.d.ts +1 -1
  77. package/dist/types/package/common/timeInput/timeInput.types.d.ts +1 -1
  78. package/dist/types/package/common/types/baseProps.types.d.ts +20 -5
  79. package/dist/types/package/common/types/slots.types.d.ts +1 -1
  80. package/docs/Button.md +23 -2
  81. package/docs/Chip.md +1 -0
  82. package/docs/NumberInput.md +66 -2
  83. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
- import { CSSProperties } from 'react';
1
+ import { default as React, CSSProperties } from 'react';
2
2
  import { IconContent } from '../types/icon.types';
3
- import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
3
+ import { SlotClassNames, SlotStyles, DataProps } from '../types/slots.types';
4
4
  export declare enum ButtonStyleType {
5
5
  Primary = "primary",
6
6
  Default = "default",
@@ -10,7 +10,9 @@ export declare enum ButtonSize {
10
10
  Default = "default",
11
11
  Small = "small",
12
12
  Medium = "medium",
13
- Large = "large"
13
+ Large = "large",
14
+ /** Square, padding-free button for a lone icon. Give it its own width/height. */
15
+ Icon = "icon"
14
16
  }
15
17
  export declare enum ButtonType {
16
18
  Button = "button",
@@ -18,7 +20,11 @@ export declare enum ButtonType {
18
20
  Submit = "submit"
19
21
  }
20
22
  export type ButtonSlots = 'background' | 'content' | 'loadingContainer' | 'label';
21
- export interface ButtonProps extends HtmlProps {
23
+ /**
24
+ * `DataProps` supplies the `data-*` index signature, which `ButtonHTMLAttributes` doesn't cover.
25
+ * `type` and `onClick` below deliberately narrow their inherited counterparts.
26
+ */
27
+ export interface ButtonProps extends DataProps, React.ButtonHTMLAttributes<HTMLButtonElement> {
22
28
  className?: string;
23
29
  style?: CSSProperties;
24
30
  type?: ButtonType;
@@ -7,7 +7,7 @@ export declare enum CardStyleType {
7
7
  Outlined = "outlined",
8
8
  Filled = "filled"
9
9
  }
10
- export interface CardProps extends HtmlProps {
10
+ export interface CardProps extends Omit<HtmlProps, 'tabIndex'> {
11
11
  className?: string | undefined;
12
12
  children?: ReactNode;
13
13
  styleType?: CardStyleType;
@@ -1,7 +1,7 @@
1
1
  import { CSSProperties, ReactNode } from 'react';
2
2
  import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
3
3
  export type CarouselSlots = 'root' | 'track' | 'slide' | 'prevButton' | 'nextButton' | 'dots' | 'dot';
4
- export interface CarouselProps extends HtmlProps {
4
+ export interface CarouselProps extends Omit<HtmlProps, 'onChange'> {
5
5
  children?: ReactNode;
6
6
  currentIndex?: number;
7
7
  defaultIndex?: number;
@@ -2,7 +2,7 @@ import { CSSProperties, ReactNode } from 'react';
2
2
  import { FormValidator } from '../../services/formValidation';
3
3
  import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
4
4
  export type CheckboxSlots = 'icon' | 'checkmark' | 'label';
5
- export interface CheckboxProps extends HtmlProps {
5
+ export interface CheckboxProps extends Omit<HtmlProps, 'onToggle'> {
6
6
  label?: string;
7
7
  selected?: boolean;
8
8
  onToggle?: (isToggled: boolean, event?: React.MouseEvent | React.KeyboardEvent) => void;
@@ -12,6 +12,8 @@ export interface ChipProps extends HtmlProps {
12
12
  thumbnail?: React.ReactNode;
13
13
  styleType?: ChipStyleType;
14
14
  showRemoveIcon?: boolean;
15
+ /** Accessible name for the remove button. Default `'Remove'`. */
16
+ removeLabel?: string;
15
17
  onClick?: () => void;
16
18
  onRemove?: () => void;
17
19
  }
@@ -1,5 +1,5 @@
1
1
  import { HtmlProps } from '../types/slots.types';
2
- export interface ColorPickerProps extends HtmlProps {
2
+ export interface ColorPickerProps extends Omit<HtmlProps, 'onChange'> {
3
3
  className?: string;
4
4
  value?: string;
5
5
  onChange?: (hex: string) => void;
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
2
  import { InputProps } from '../input/input.types';
3
3
  import { TextareaProps } from '../textarea/textarea.types';
4
- import { NumberInputProps } from '../numberInput/numberInput';
4
+ import { NumberInputProps } from '../numberInput/numberInput.types';
5
5
  import { DropdownProps } from '../dropdown/dropdown.types';
6
6
  import { InputDropdownProps } from '../inputDropdown/inputDropdown.types';
7
7
  import { CheckboxProps } from '../checkbox/checkbox.types';
@@ -48,7 +48,7 @@ export interface DropZoneRenderState {
48
48
  disabled: boolean;
49
49
  }
50
50
  export type DropZoneSlots = 'root' | 'content' | 'fileList' | 'fileItem' | 'input';
51
- export interface DropZoneProps extends HtmlProps {
51
+ export interface DropZoneProps extends Omit<HtmlProps, 'children'> {
52
52
  /** Static content, or a render function that receives the live drop state. */
53
53
  children?: React.ReactNode | ((state: DropZoneRenderState) => React.ReactNode);
54
54
  /**
@@ -6,7 +6,7 @@ export interface DropdownItem {
6
6
  label: ReactNode;
7
7
  disabled?: boolean;
8
8
  }
9
- export interface DropdownProps extends HtmlProps {
9
+ export interface DropdownProps extends Omit<HtmlProps, 'onFocus' | 'onBlur' | 'onMouseEnter' | 'onMouseLeave'> {
10
10
  formValidator?: FormValidator | null;
11
11
  onMouseEnter?: ((event: React.MouseEvent) => void) | null;
12
12
  onMouseLeave?: ((event: React.MouseEvent) => void) | null;
@@ -24,7 +24,7 @@ export interface ErrorBoundaryLabels {
24
24
  /** Toast shown if the copy fails (only visible if `ToastProvider` is mounted). */
25
25
  copyErrorToast?: string;
26
26
  }
27
- export interface ErrorBoundaryProps extends HtmlProps {
27
+ export interface ErrorBoundaryProps extends Omit<HtmlProps, 'title' | 'onError'> {
28
28
  children?: ReactNode;
29
29
  /** Heading shown above the error message. Default: "Something went wrong". */
30
30
  title?: ReactNode;
@@ -6,7 +6,7 @@ export declare enum Align {
6
6
  Center = "center"
7
7
  }
8
8
  export type FloatingMenuSlots = 'root' | 'trigger' | 'menu' | 'menuContainer';
9
- export interface FloatingMenuProps extends HtmlProps {
9
+ export interface FloatingMenuProps extends Omit<HtmlProps, 'content'> {
10
10
  align?: Align;
11
11
  children?: ReactNode;
12
12
  content?: ReactNode;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `true` when the primary pointing device is coarse (touch), kept current so a device that
3
+ * switches input modes re-renders.
4
+ *
5
+ * `useSyncExternalStore` rather than `useState` + `useEffect`: callers pick *markup* with this,
6
+ * so hydration has to start from the server's answer. Reading `matchMedia` in a `useState`
7
+ * initialiser makes the client's first render disagree with the server's HTML.
8
+ */
9
+ export declare function useCoarsePointer(): boolean;
10
+ export default useCoarsePointer;
@@ -9,7 +9,7 @@ export interface IconPickerItem {
9
9
  icon?: IconDefinition;
10
10
  customIcon?: React.ReactNode;
11
11
  }
12
- export interface IconPickerProps extends HtmlProps {
12
+ export interface IconPickerProps extends Omit<HtmlProps, 'onChange'> {
13
13
  data?: IconPickerItem[];
14
14
  selectedId?: string | null;
15
15
  label?: string;
@@ -1,6 +1,6 @@
1
1
  import { default as React } from 'react';
2
2
  import { FormValidator } from '../../services/formValidation';
3
- import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
3
+ import { SlotClassNames, SlotStyles, DataProps } from '../types/slots.types';
4
4
  export declare enum InputType {
5
5
  Edit = "edit",
6
6
  Phone = "phone",
@@ -17,7 +17,7 @@ export declare enum InputIconPosition {
17
17
  }
18
18
  export type InputSlots = 'label' | 'container' | 'input' | 'suffix' | 'upperRightLabel' | 'fieldset';
19
19
  type OverriddenInputAttributes = 'onChange' | 'onFocus' | 'onBlur' | 'onMouseEnter' | 'onMouseLeave' | 'onClick' | 'type' | 'value' | 'min' | 'max' | 'autoComplete';
20
- export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, OverriddenInputAttributes>, HtmlProps {
20
+ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, OverriddenInputAttributes>, DataProps {
21
21
  onMouseEnter?: ((event: React.MouseEvent) => void) | null;
22
22
  onMouseLeave?: ((event: React.MouseEvent) => void) | null;
23
23
  onFocus?: ((event: React.FocusEvent) => void) | null;
@@ -6,7 +6,7 @@ export interface InputDropdownItem {
6
6
  label?: string;
7
7
  value: string;
8
8
  }
9
- export interface InputDropdownProps extends HtmlProps {
9
+ export interface InputDropdownProps extends Omit<HtmlProps, 'onSelect'> {
10
10
  label?: string;
11
11
  value?: string;
12
12
  formValidator?: FormValidator | null;
@@ -1,6 +1,6 @@
1
1
  import { default as React } from 'react';
2
- import { HtmlProps } from '../types/baseProps.types';
3
- interface Properties extends HtmlProps, Omit<React.HTMLAttributes<HTMLDivElement>, 'tabIndex'> {
2
+ import { DataProps } from '../types/baseProps.types';
3
+ interface Properties extends DataProps, Omit<React.HTMLAttributes<HTMLDivElement>, 'tabIndex'> {
4
4
  tabIndex?: number | null;
5
5
  }
6
6
  declare const InteractableDiv: React.ForwardRefExoticComponent<Properties & React.RefAttributes<HTMLDivElement>>;
@@ -25,7 +25,7 @@ export interface KanbanColumnDef {
25
25
  hasMore?: boolean;
26
26
  }
27
27
  export type KanbanBoardSlots = 'dragOverlay';
28
- export interface KanbanBoardProps<T = unknown> extends HtmlProps {
28
+ export interface KanbanBoardProps<T = unknown> extends Omit<HtmlProps, 'onChange'> {
29
29
  /** Ordered list of column definitions. Each column owns its itemIds. */
30
30
  columns: KanbanColumnDef[];
31
31
  /** Flat map of all items keyed by their id. */
@@ -1,2 +1,3 @@
1
1
  import { default as NumberInput } from './numberInput';
2
+ export type { NumberInputProps, NumberInputArrowsLayout } from './numberInput.types';
2
3
  export default NumberInput;
@@ -1,34 +1,5 @@
1
- import { ReactElement, default as React } from 'react';
2
- import { NumericFormatProps } from 'react-number-format';
3
- import { FormValidator } from '../../services/formValidation';
4
- export interface NumberInputProps extends Omit<NumericFormatProps, 'onValueChange' | 'value' | 'customInput' | 'onChange' | 'step' | 'onKeyDown'> {
5
- value?: number | string;
6
- onChange?: (value: number | undefined) => void;
7
- formValidator?: FormValidator | null;
8
- label?: string;
9
- error?: string;
10
- className?: string;
11
- allowNegative?: boolean;
12
- decimalSeparator?: string;
13
- thousandSeparator?: string | boolean;
14
- prefix?: string;
15
- suffix?: string;
16
- decimalScale?: number;
17
- /**
18
- * Whether to pad the value to exactly `decimalScale` decimals. Default `true`
19
- * (e.g. with `decimalScale={2}`, `12` displays as `12.00`). Set `false` to make
20
- * `decimalScale` an upper limit only — the user may type fewer decimals and they
21
- * are kept as-is (`12` stays `12`, `12.1` stays `12.1`), while anything beyond
22
- * `decimalScale` is still truncated.
23
- */
24
- fixedDecimalScale?: boolean;
25
- placeholder?: string;
26
- sign?: '+' | '-';
27
- showArrows?: boolean;
28
- step?: number;
29
- readOnly?: boolean;
30
- isRequired?: boolean;
31
- onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
32
- }
1
+ import { ReactElement } from 'react';
2
+ import { NumberInputProps } from './numberInput.types';
3
+ export type { NumberInputProps, NumberInputArrowsLayout } from './numberInput.types';
33
4
  declare function NumberInput(props: NumberInputProps): ReactElement;
34
5
  export default NumberInput;
@@ -0,0 +1,61 @@
1
+ import { default as React } from 'react';
2
+ import { NumericFormatProps } from 'react-number-format';
3
+ import { FormValidator } from '../../services/formValidation';
4
+ /**
5
+ * How the stepper buttons are laid out.
6
+ *
7
+ * - `stacked` — chevrons stacked inside the field's right edge. Compact; suits a mouse.
8
+ * - `split` — square `−` / `+` buttons flanking the field, each as tall and wide as the field.
9
+ * Far apart with the field between them, so a mis-tap can't step the value the wrong way.
10
+ * - `auto` — `split` on a coarse (touch) pointer, `stacked` otherwise.
11
+ */
12
+ export type NumberInputArrowsLayout = 'auto' | 'stacked' | 'split';
13
+ export interface NumberInputProps extends Omit<NumericFormatProps, 'onValueChange' | 'value' | 'customInput' | 'onChange' | 'step' | 'onKeyDown' | 'min' | 'max'> {
14
+ value?: number | string;
15
+ onChange?: (value: number | undefined) => void;
16
+ formValidator?: FormValidator | null;
17
+ label?: string;
18
+ error?: string;
19
+ className?: string;
20
+ allowNegative?: boolean;
21
+ decimalSeparator?: string;
22
+ thousandSeparator?: string | boolean;
23
+ prefix?: string;
24
+ suffix?: string;
25
+ decimalScale?: number;
26
+ /**
27
+ * Whether to pad the value to exactly `decimalScale` decimals. Default `true`
28
+ * (e.g. with `decimalScale={2}`, `12` displays as `12.00`). Set `false` to make
29
+ * `decimalScale` an upper limit only — the user may type fewer decimals and they
30
+ * are kept as-is (`12` stays `12`, `12.1` stays `12.1`), while anything beyond
31
+ * `decimalScale` is still truncated.
32
+ */
33
+ fixedDecimalScale?: boolean;
34
+ placeholder?: string;
35
+ sign?: '+' | '-';
36
+ showArrows?: boolean;
37
+ step?: number;
38
+ /**
39
+ * Lower bound. Constrains the steppers and the ArrowDown key, disables the decrease button
40
+ * once reached, and clamps a typed value on blur and on Enter — the two moments it stops
41
+ * being mid-edit. Typing is never blocked; see {@link NumberInputProps.max}.
42
+ */
43
+ min?: number;
44
+ /**
45
+ * Upper bound. Behaves exactly like {@link NumberInputProps.min} in the other direction.
46
+ *
47
+ * Neither bound rejects a keystroke as it is typed — an edit passes through out-of-range
48
+ * states on its way somewhere valid — and an empty field is never clamped. Use a
49
+ * `formValidator` if out-of-range input must be *rejected* rather than corrected.
50
+ */
51
+ max?: number;
52
+ /** Stepper layout. Default `'auto'`. */
53
+ arrowsLayout?: NumberInputArrowsLayout;
54
+ /** Accessible name for the increase button. Default `'Increase'`. */
55
+ increaseLabel?: string;
56
+ /** Accessible name for the decrease button. Default `'Decrease'`. */
57
+ decreaseLabel?: string;
58
+ readOnly?: boolean;
59
+ isRequired?: boolean;
60
+ onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
61
+ }
@@ -0,0 +1,17 @@
1
+ export interface UseStepRepeatResult {
2
+ /** Fires one step immediately, then starts the hold-to-repeat timer. */
3
+ start: (direction: 1 | -1, multiplier: number) => void;
4
+ /** Cancels a running repeat. Safe to call when nothing is running. */
5
+ stop: () => void;
6
+ }
7
+ /**
8
+ * Hold-to-repeat for the stepper buttons: one step on press, then an accelerating repeat.
9
+ *
10
+ * The release is watched on `window` rather than by capturing the pointer, which would retarget
11
+ * the following click past the node Button's ripple listens on. Capture would have guaranteed a
12
+ * `pointerup`, so the extra listeners cover the ways a press can end without one.
13
+ *
14
+ * `onStep` returns whether anything changed; `false` ends the repeat, so a hold at a bound stops.
15
+ */
16
+ export declare function useStepRepeat(onStep: (delta: number) => boolean): UseStepRepeatResult;
17
+ export default useStepRepeat;
@@ -5,7 +5,7 @@ export interface Option {
5
5
  key: string;
6
6
  }
7
7
  export type OptionPickerSlots = 'root' | 'chip' | 'option';
8
- export interface OptionPickerProps extends HtmlProps {
8
+ export interface OptionPickerProps extends Omit<HtmlProps, 'onChange'> {
9
9
  className?: string;
10
10
  style?: React.CSSProperties;
11
11
  classNames?: SlotClassNames<OptionPickerSlots>;
@@ -8,7 +8,7 @@ export declare enum OtpInputCharset {
8
8
  /** Letters and digits. */
9
9
  Alphanumeric = "alphanumeric"
10
10
  }
11
- export interface OtpInputProps extends HtmlProps {
11
+ export interface OtpInputProps extends Omit<HtmlProps, 'onChange'> {
12
12
  /** Number of character boxes. Default 6. */
13
13
  length?: number;
14
14
  /** Current code as a plain string, e.g. "123456". */
@@ -20,7 +20,7 @@ export declare enum PopoverTrigger {
20
20
  Focus = "focus"
21
21
  }
22
22
  export type PopoverSlots = 'root' | 'trigger' | 'floating' | 'panel' | 'arrow';
23
- export interface PopoverProps extends HtmlProps {
23
+ export interface PopoverProps extends Omit<HtmlProps, 'content'> {
24
24
  /** The trigger element. */
25
25
  children?: ReactNode;
26
26
  /** The popover body. */
@@ -16,7 +16,7 @@ export interface RadioOption {
16
16
  disabled?: boolean;
17
17
  }
18
18
  export type RadioGroupSlots = 'root' | 'option' | 'radio' | 'dot' | 'label' | 'description';
19
- export interface RadioGroupProps extends HtmlProps {
19
+ export interface RadioGroupProps extends Omit<HtmlProps, 'onChange'> {
20
20
  /** The selectable options. */
21
21
  options: RadioOption[];
22
22
  /** Controlled selected value. Omit for uncontrolled. */
@@ -2,7 +2,7 @@ import { CSSProperties } from 'react';
2
2
  import { IconContent } from '../types/icon.types';
3
3
  import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
4
4
  export type RatingSlots = 'root' | 'item' | 'iconEmpty' | 'iconFilled';
5
- export interface RatingProps extends HtmlProps {
5
+ export interface RatingProps extends Omit<HtmlProps, 'onChange'> {
6
6
  value?: number;
7
7
  defaultValue?: number;
8
8
  onChange?: (value: number) => void;
@@ -1,5 +1,5 @@
1
1
  import { HtmlProps } from '../types/slots.types';
2
- export interface SearchInputProps extends HtmlProps {
2
+ export interface SearchInputProps extends Omit<HtmlProps, 'onChange'> {
3
3
  value: string;
4
4
  onChange: (value: string) => void;
5
5
  onDebounce?: (value: string) => void;
@@ -9,7 +9,7 @@ export declare enum SectionHeaderSize {
9
9
  }
10
10
  export type SectionHeaderAction = IconAction;
11
11
  export type SectionHeaderSlots = 'root' | 'identity' | 'leading' | 'titles' | 'title' | 'subtitle' | 'trailing' | 'left' | 'center' | 'actions';
12
- export interface SectionHeaderProps extends HtmlProps {
12
+ export interface SectionHeaderProps extends Omit<HtmlProps, 'title'> {
13
13
  title?: ReactNode;
14
14
  subtitle?: ReactNode;
15
15
  icon?: IconContent;
@@ -7,7 +7,7 @@ export interface SliderMark {
7
7
  label?: ReactNode;
8
8
  }
9
9
  export type SliderSlots = 'root' | 'track' | 'range' | 'thumb' | 'mark' | 'markLabel' | 'value';
10
- export interface SliderProps extends HtmlProps {
10
+ export interface SliderProps extends Omit<HtmlProps, 'onChange'> {
11
11
  /** Controlled value. Omit for uncontrolled. */
12
12
  value?: number;
13
13
  /** Initial value when uncontrolled (default `min`). */
@@ -2,7 +2,7 @@ import { CSSProperties, ReactNode } from 'react';
2
2
  import { FormValidator } from '../../services/formValidation';
3
3
  import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
4
4
  export type SwitchSlots = 'track' | 'thumb' | 'label';
5
- export interface SwitchProps extends HtmlProps {
5
+ export interface SwitchProps extends Omit<HtmlProps, 'onToggle'> {
6
6
  label?: string;
7
7
  selected?: boolean;
8
8
  onToggle?: (isToggled: boolean, event?: React.MouseEvent | React.KeyboardEvent) => void;
@@ -9,7 +9,7 @@ export interface TabHeaderTab {
9
9
  subtitle?: string;
10
10
  disabled?: boolean;
11
11
  }
12
- export interface TabHeaderProps extends HtmlProps {
12
+ export interface TabHeaderProps extends Omit<HtmlProps, 'onChange'> {
13
13
  tabs: TabHeaderTab[];
14
14
  currentTab: string;
15
15
  onChange: (id: string) => void;
@@ -2,7 +2,7 @@ import { default as React } from 'react';
2
2
  import { FormValidator } from '../../services/formValidation';
3
3
  import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
4
4
  export type TimeInputSlots = 'root' | 'clock' | 'clockHeader' | 'headerSegment' | 'meridiem' | 'clockFace' | 'clockHand' | 'clockNumber' | 'footer';
5
- export interface TimeInputProps extends HtmlProps {
5
+ export interface TimeInputProps extends Omit<HtmlProps, 'onChange' | 'onSelect'> {
6
6
  /** Current time as a "HH:MM" (24-hour) string. */
7
7
  value?: string | null;
8
8
  /** Alias for `value`, to mirror DatePicker. */
@@ -1,8 +1,23 @@
1
1
  import { default as React } from 'react';
2
- /** HTML pass-through props accepted on the root element of every component. */
3
- export type HtmlProps = React.AriaAttributes & {
4
- id?: string;
5
- role?: React.AriaRole;
6
- } & {
2
+ /**
3
+ * TypeScript allows hyphenated attributes on *intrinsic* elements only, so a component's props
4
+ * interface has to declare these itself — no React attribute type provides them.
5
+ */
6
+ type DataAttributes = {
7
7
  [K in `data-${string}`]?: string;
8
8
  };
9
+ /**
10
+ * HTML pass-through props accepted on the root element of every component: the full native
11
+ * attribute set for that element, plus `data-*`.
12
+ *
13
+ * Pass the root's element type for correctly-typed event handlers (`HtmlProps<HTMLDivElement>`);
14
+ * the default is the loose `HTMLElement`. Where the root is not a plain container, extend the
15
+ * matching React type instead and add `DataProps` for the `data-*` half — `Button` does this
16
+ * with `React.ButtonHTMLAttributes<HTMLButtonElement>`.
17
+ *
18
+ * A component prop that collides with a native one of the same name must `Omit` the native one.
19
+ */
20
+ export type HtmlProps<E extends HTMLElement = HTMLElement> = React.HTMLAttributes<E> & DataAttributes;
21
+ /** The `data-*` half of {@link HtmlProps}, for components that source attributes elsewhere. */
22
+ export type DataProps = DataAttributes;
23
+ export {};
@@ -1,4 +1,4 @@
1
1
  import { CSSProperties } from 'react';
2
2
  export type SlotClassNames<Keys extends string> = Partial<Record<Keys, string>>;
3
3
  export type SlotStyles<Keys extends string> = Partial<Record<Keys, CSSProperties>>;
4
- export type { HtmlProps } from './baseProps.types';
4
+ export type { HtmlProps, DataProps } from './baseProps.types';
package/docs/Button.md CHANGED
@@ -29,6 +29,18 @@ import { faSave } from '@fortawesome/free-solid-svg-icons';
29
29
  // Small
30
30
  <Button size={ButtonSize.Small}>Cancel</Button>
31
31
 
32
+ // Icon-only — padding-free and square; give it the box size you want
33
+ <Button size={ButtonSize.Icon} icon={faPlus} aria-label="Add" style={{ width: 44, height: 44 }} />
34
+
35
+ // Press-and-hold: act on press rather than release
36
+ <Button
37
+ icon={faPlus}
38
+ aria-label="Increase"
39
+ onPointerDown={startRepeat}
40
+ onPointerUp={stopRepeat}
41
+ onPointerCancel={stopRepeat}
42
+ />
43
+
32
44
  // Submit button in a form
33
45
  <Button type={ButtonType.Submit} styleType={ButtonStyleType.Primary}>Submit</Button>
34
46
  ```
@@ -36,7 +48,7 @@ import { faSave } from '@fortawesome/free-solid-svg-icons';
36
48
  **Enums:**
37
49
 
38
50
  - `ButtonStyleType`: `Primary` | `Default` | `Delete`
39
- - `ButtonSize`: `Default` | `Small` | `Medium` | `Large`
51
+ - `ButtonSize`: `Default` | `Small` | `Medium` | `Large` | `Icon`
40
52
  - `ButtonType`: `Button` | `Reset` | `Submit`
41
53
 
42
54
  **Key props:**
@@ -44,13 +56,19 @@ import { faSave } from '@fortawesome/free-solid-svg-icons';
44
56
  | Prop | Type | Description |
45
57
  |------|------|-------------|
46
58
  | `styleType` | `ButtonStyleType` | Visual variant |
47
- | `size` | `ButtonSize` | Size variant |
59
+ | `size` | `ButtonSize` | Size variant. `Icon` strips all padding, inherits the surrounding text size, and centres a lone icon — set the width/height yourself |
48
60
  | `type` | `ButtonType` | HTML button type |
49
61
  | `icon` | `IconDefinition \| ReactElement` | FontAwesome icon or any React element, shown before label |
50
62
  | `isLoading` | `boolean` | Replaces content with a spinner |
51
63
  | `disabled` | `boolean` | |
52
64
  | `onClick` | `(e: MouseEvent) => void \| Promise<void>` | |
53
65
 
66
+ `ButtonProps` extends `React.ButtonHTMLAttributes<HTMLButtonElement>`, so every native `<button>`
67
+ attribute is accepted and forwarded — pointer handlers (`onPointerDown` and friends, for
68
+ press-and-hold controls that act on press rather than release), `onContextMenu`, `tabIndex`,
69
+ `name`, `value`, `form`, `autoFocus`, and the rest. `type` and `onClick` are narrowed: `type`
70
+ takes the `ButtonType` enum, and `onClick` may return a promise.
71
+
54
72
  **Theming (default style):** the `Default` style is transparent text-coloured by default. Override it theme-wide (or per button via `style`) without touching `Primary`/`Delete`:
55
73
 
56
74
  - `--button-default-background` — fill (default transparent)
@@ -73,3 +91,6 @@ const theme: Theme = {
73
91
  ```
74
92
 
75
93
  **Slots:** `background` `content` `loadingContainer` `label`
94
+
95
+ **Sizing in a flex row:** a Button never shrinks below its own content (`min-width: min-content`),
96
+ so an icon is never cut off by a flexible sibling. A multi-word label still wraps above that floor.
package/docs/Chip.md CHANGED
@@ -56,6 +56,7 @@ import { Chip, ChipContainer, ChipStyleType } from '@ahrowe/ui';
56
56
  | `text` | `string` | Chip label |
57
57
  | `styleType` | `ChipStyleType` | Colour variant (default `Default`) |
58
58
  | `thumbnail` | `string` | Avatar image URL |
59
+ | `removeLabel` | `string` | Accessible name for the remove button. Default: `'Remove'` |
59
60
  | `showRemoveIcon` | `boolean` | Show × icon |
60
61
  | `onRemove` | `() => void` | Remove button handler |
61
62
  | `onClick` | `() => void` | Makes chip clickable |
@@ -2,7 +2,7 @@
2
2
 
3
3
  **When to use:** Numeric input with formatting — prices, quantities, percentages, phone numbers with separators. Wraps `react-number-format` with the same floating-label style as `Input`.
4
4
 
5
- **Keywords:** currency input, quantity field, numeric field
5
+ **Keywords:** currency input, quantity field, numeric field, spinner, stepper, up down arrows, increment, decrement, plus minus, range, bounds, disabled
6
6
 
7
7
  **Import:** `import { NumberInput } from '@ahrowe/ui'`
8
8
 
@@ -32,7 +32,18 @@ const priceValidator = new FormValidator<number | undefined>(undefined, [Validat
32
32
  <NumberInput label="Price" formValidator={priceValidator} prefix="€" decimalScale={2} />
33
33
 
34
34
  // Percentage
35
- <NumberInput label="Discount" suffix="%" decimalScale={1} max={100} />
35
+ <NumberInput label="Discount" suffix="%" decimalScale={1} />
36
+
37
+ // Stepper buttons — hold to repeat, shift for ±10, clamped to min/max
38
+ <NumberInput
39
+ label="Guests"
40
+ value={guests}
41
+ onChange={setGuests}
42
+ decimalScale={0}
43
+ showArrows
44
+ min={0}
45
+ max={10}
46
+ />
36
47
 
37
48
  // Max decimals without padding — cap at 2 decimals, but keep what the user typed
38
49
  // (12 stays "12", 12.1 stays "12.1"; 12.111 is truncated to "12.11")
@@ -58,5 +69,58 @@ const priceValidator = new FormValidator<number | undefined>(undefined, [Validat
58
69
  | `isRequired` | `boolean` | Shows the required mark (`*`) next to the label. Also inferred automatically when `formValidator` has a `required` validator. Default: `false` |
59
70
  | `placeholder` | `string` | |
60
71
  | `error` | `string` | Manual error message |
72
+ | `showArrows` | `boolean` | Show stepper buttons. Default: `false` |
73
+ | `step` | `number` | Amount one step changes the value by. Default: `1` (hold shift for ×10) |
74
+ | `min` | `number` | Lower bound — see the note below |
75
+ | `max` | `number` | Upper bound — see the note below |
76
+ | `arrowsLayout` | `'auto' \| 'stacked' \| 'split'` | Stepper layout. Default: `'auto'` |
77
+ | `increaseLabel` | `string` | Accessible name for the increase button. Default: `'Increase'` |
78
+ | `decreaseLabel` | `string` | Accessible name for the decrease button. Default: `'Decrease'` |
79
+
80
+ **Steppers (`showArrows`):** two layouts, picked by `arrowsLayout`:
81
+
82
+ - `'stacked'` — chevrons inside the field's right edge, each roughly 27x17 at the default text
83
+ size. Compact and mouse-oriented: two targets stacked inside a 34px field cannot reach the 24px
84
+ minimum that WCAG 2.5.8 asks for, which is why `'auto'` prefers `'split'` on touch.
85
+ - `'split'` — full-height `−` / `+` buttons flanking the field, each a 44px touch target. The two
86
+ are far apart, so a mis-tap can't step the value the wrong way.
87
+ - `'auto'` (default) — `split` on a coarse (touch) pointer, `stacked` otherwise, detected with
88
+ `matchMedia('(pointer: coarse)')` and re-evaluated when the pointer type changes.
89
+
90
+ The split buttons are the shared `Button` component, so press feedback is the same ripple as everywhere else in the library.
91
+
92
+ Press and hold a button to repeat: one step immediately, then a repeat after 400ms that accelerates
93
+ after about a second. Hold shift for ×10. The field itself carries `role="spinbutton"` with
94
+ `aria-valuenow` / `aria-valuemin` / `aria-valuemax`, so ↑/↓ stepping is announced; the buttons stay
95
+ out of the tab order rather than adding two tab stops per field.
96
+
97
+ **`min` / `max` clamp, they do not block typing.** Both bounds constrain the steppers and the
98
+ ↑/↓ keys, disable the corresponding button once reached, and pull a typed value back into range
99
+ when it stops being mid-edit: on blur, and on Enter (which submits a surrounding form without ever
100
+ blurring the field). An empty field is never clamped, so blurring an untouched field does not fill
101
+ in `min`.
102
+
103
+ Nothing is rejected as it is typed, deliberately. Editing inserts digits into an existing value, so
104
+ the intermediate state routinely leaves the range even when the destination is inside it — with
105
+ `max={10}`, putting the caret in front of `8` and typing `1` on the way to `18` would simply do
106
+ nothing, with no feedback. Blocking input suits *format* rules (digits only, a fixed length), where
107
+ every rejected keystroke is unambiguously wrong; a value range is not one of those.
108
+
109
+ The Enter clamp is exact for a `formValidator`-driven field, because `formValidator.set` is
110
+ imperative and lands before the submit handler runs. For a plain `value`/`onChange` field it is
111
+ best-effort: the consumer's own state update may not have flushed by the time their `onSubmit`
112
+ reads it. If an out-of-range value must be *impossible* to submit rather than merely corrected, add
113
+ a validator — `@ahrowe/form-validation` has no numeric range validator (`minLength`/`maxLength` are
114
+ string length), so write one:
115
+
116
+ ```tsx
117
+ const inRange = (min: number, max: number): ValidatorFunction => (value) =>
118
+ typeof value === 'number' && (value < min || value > max)
119
+ ? { id: 'range', message: `Must be between ${min} and ${max}` }
120
+ : null;
121
+
122
+ const qty = new FormValidator<number | undefined>(undefined, [inRange(10, 100)]);
123
+ <NumberInput label="Quantity" formValidator={qty} min={10} max={100} showArrows decimalScale={0} />
124
+ ```
61
125
 
62
126
  **Mobile keyboard layout:** shows the numeric on-screen keyboard automatically (`inputMode="numeric"` when `decimalScale={0}`, `"decimal"` otherwise, so a decimal separator key is available whenever fractional values are allowed). Pass `inputMode` directly to override.