@ahrowe/ui 0.37.0 → 0.38.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 (95) hide show
  1. package/README.md +4 -1
  2. package/dist/cjs-types/common/commandPalette/commandPalette.d.ts +4 -0
  3. package/dist/cjs-types/common/commandPalette/commandPalette.types.d.ts +42 -0
  4. package/dist/cjs-types/common/commandPalette/filterCommands.d.ts +15 -0
  5. package/dist/cjs-types/common/commandPalette/index.d.ts +2 -0
  6. package/dist/cjs-types/common/configProvider/configProvider.types.d.ts +6 -0
  7. package/dist/cjs-types/common/copyButton/copyButton.d.ts +4 -0
  8. package/dist/cjs-types/common/copyButton/copyButton.types.d.ts +36 -0
  9. package/dist/cjs-types/common/copyButton/index.d.ts +2 -0
  10. package/dist/cjs-types/common/errorBoundary/errorBoundary.d.ts +0 -3
  11. package/dist/cjs-types/common/errorBoundary/errorBoundary.types.d.ts +2 -3
  12. package/dist/cjs-types/common/hooks/index.d.ts +2 -0
  13. package/dist/cjs-types/common/hooks/useClipboard.d.ts +16 -0
  14. package/dist/cjs-types/common/inputDropdown/inputDropdown.types.d.ts +7 -1
  15. package/dist/cjs-types/common/menu/menu.types.d.ts +2 -0
  16. package/dist/cjs-types/common/themeProvider/themeProvider.d.ts +8 -7
  17. package/dist/cjs-types/common/weatherIcon/index.d.ts +3 -0
  18. package/dist/cjs-types/common/weatherIcon/parts.d.ts +26 -0
  19. package/dist/cjs-types/common/weatherIcon/weatherIcon.d.ts +4 -0
  20. package/dist/cjs-types/common/weatherIcon/weatherIcon.types.d.ts +44 -0
  21. package/dist/cjs-types/index.d.ts +6 -0
  22. package/dist/esm/common/buttonGroup/buttonGroup.module.mjs.map +1 -1
  23. package/dist/esm/common/commandPalette/commandPalette.mjs +2 -0
  24. package/dist/esm/common/commandPalette/commandPalette.mjs.map +1 -0
  25. package/dist/esm/common/commandPalette/commandPalette.module.mjs +2 -0
  26. package/dist/esm/common/commandPalette/commandPalette.module.mjs.map +1 -0
  27. package/dist/esm/common/commandPalette/filterCommands.mjs +2 -0
  28. package/dist/esm/common/commandPalette/filterCommands.mjs.map +1 -0
  29. package/dist/esm/common/copyButton/copyButton.mjs +2 -0
  30. package/dist/esm/common/copyButton/copyButton.mjs.map +1 -0
  31. package/dist/esm/common/copyButton/copyButton.module.mjs +2 -0
  32. package/dist/esm/common/copyButton/copyButton.module.mjs.map +1 -0
  33. package/dist/esm/common/dropdown/dropdown.mjs +1 -1
  34. package/dist/esm/common/dropdown/dropdown.module.mjs.map +1 -1
  35. package/dist/esm/common/errorBoundary/errorBoundary.mjs +2 -2
  36. package/dist/esm/common/errorBoundary/errorBoundary.mjs.map +1 -1
  37. package/dist/esm/common/errorBoundary/errorBoundary.module.mjs +1 -1
  38. package/dist/esm/common/errorBoundary/errorBoundary.module.mjs.map +1 -1
  39. package/dist/esm/common/hooks/useClipboard.mjs +2 -0
  40. package/dist/esm/common/hooks/useClipboard.mjs.map +1 -0
  41. package/dist/esm/common/inputDropdown/inputDropdown.mjs +1 -1
  42. package/dist/esm/common/inputDropdown/inputDropdown.mjs.map +1 -1
  43. package/dist/esm/common/inputDropdown/inputDropdown.module.mjs +1 -1
  44. package/dist/esm/common/inputDropdown/inputDropdown.module.mjs.map +1 -1
  45. package/dist/esm/common/menu/menu.mjs +1 -1
  46. package/dist/esm/common/menu/menu.mjs.map +1 -1
  47. package/dist/esm/common/menu/menu.types.mjs.map +1 -1
  48. package/dist/esm/common/themeProvider/themeProvider.mjs +1 -1
  49. package/dist/esm/common/themeProvider/themeProvider.mjs.map +1 -1
  50. package/dist/esm/common/timer/timer.mjs +1 -1
  51. package/dist/esm/common/timer/timer.mjs.map +1 -1
  52. package/dist/esm/common/weatherIcon/parts.mjs +2 -0
  53. package/dist/esm/common/weatherIcon/parts.mjs.map +1 -0
  54. package/dist/esm/common/weatherIcon/weatherIcon.mjs +2 -0
  55. package/dist/esm/common/weatherIcon/weatherIcon.mjs.map +1 -0
  56. package/dist/esm/common/weatherIcon/weatherIcon.module.mjs +2 -0
  57. package/dist/esm/common/weatherIcon/weatherIcon.module.mjs.map +1 -0
  58. package/dist/esm/common/weatherIcon/weatherIcon.types.mjs +2 -0
  59. package/dist/esm/common/weatherIcon/weatherIcon.types.mjs.map +1 -0
  60. package/dist/esm/index.mjs +1 -1
  61. package/dist/index.cjs +3 -3
  62. package/dist/index.cjs.map +1 -1
  63. package/dist/style.css +1 -1
  64. package/dist/types/common/commandPalette/commandPalette.d.ts +4 -0
  65. package/dist/types/common/commandPalette/commandPalette.types.d.ts +42 -0
  66. package/dist/types/common/commandPalette/filterCommands.d.ts +15 -0
  67. package/dist/types/common/commandPalette/index.d.ts +2 -0
  68. package/dist/types/common/configProvider/configProvider.types.d.ts +6 -0
  69. package/dist/types/common/copyButton/copyButton.d.ts +4 -0
  70. package/dist/types/common/copyButton/copyButton.types.d.ts +36 -0
  71. package/dist/types/common/copyButton/index.d.ts +2 -0
  72. package/dist/types/common/errorBoundary/errorBoundary.d.ts +0 -3
  73. package/dist/types/common/errorBoundary/errorBoundary.types.d.ts +2 -3
  74. package/dist/types/common/hooks/index.d.ts +2 -0
  75. package/dist/types/common/hooks/useClipboard.d.ts +16 -0
  76. package/dist/types/common/inputDropdown/inputDropdown.types.d.ts +7 -1
  77. package/dist/types/common/menu/menu.types.d.ts +2 -0
  78. package/dist/types/common/themeProvider/themeProvider.d.ts +8 -7
  79. package/dist/types/common/weatherIcon/index.d.ts +3 -0
  80. package/dist/types/common/weatherIcon/parts.d.ts +26 -0
  81. package/dist/types/common/weatherIcon/weatherIcon.d.ts +4 -0
  82. package/dist/types/common/weatherIcon/weatherIcon.types.d.ts +44 -0
  83. package/dist/types/index.d.ts +6 -0
  84. package/docs/ButtonGroup.md +2 -0
  85. package/docs/CLAUDE.md +4 -1
  86. package/docs/CommandPalette.md +84 -0
  87. package/docs/ConfigProvider.md +2 -2
  88. package/docs/CopyButton.md +45 -0
  89. package/docs/ErrorBoundary.md +4 -4
  90. package/docs/Fab.md +1 -2
  91. package/docs/Hooks.md +39 -3
  92. package/docs/InputDropdown.md +9 -4
  93. package/docs/Menu.md +5 -3
  94. package/docs/WeatherIcon.md +49 -0
  95. package/package.json +1 -2
@@ -0,0 +1,4 @@
1
+ import { ReactElement } from 'react';
2
+ import { CommandPaletteProps } from './commandPalette.types.js';
3
+ declare function CommandPalette(props: CommandPaletteProps): ReactElement;
4
+ export default CommandPalette;
@@ -0,0 +1,42 @@
1
+ import { CSSProperties } from 'react';
2
+ import { MenuEntry } from '../menu/menu.types.js';
3
+ import { Presentation } from '../configProvider/presentation.types.js';
4
+ import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types.js';
5
+ export type CommandPaletteSlots = 'root' | 'search' | 'input' | 'list' | 'item' | 'itemIcon' | 'itemLabel' | 'itemShortcut' | 'heading' | 'separator' | 'empty';
6
+ export interface CommandPaletteLabels {
7
+ /** Accessible name of the dialog and the search field. Default: "Search commands". */
8
+ label?: string;
9
+ /** Placeholder in the search field. Default: "Type a command or search…". */
10
+ placeholder?: string;
11
+ /** Shown when nothing matches. Default: "No results". */
12
+ empty?: string;
13
+ /** Shown while `isLoading` and nothing matches yet. Default: "Searching…". */
14
+ loading?: string;
15
+ /** Announced to screen readers as the matches change. */
16
+ results?: (count: number) => string;
17
+ }
18
+ export interface CommandPaletteProps extends HtmlProps<HTMLDivElement> {
19
+ /** The commands, in the same shape `Menu` takes. Headings group them; separators split groups. */
20
+ items: MenuEntry[];
21
+ isOpen: boolean;
22
+ onClose: () => void;
23
+ /** Called with the id of the command that was run, after its own `onClick`. */
24
+ onAction?: (id: string) => void;
25
+ /** Called on every keystroke, and with `''` each time the palette opens. */
26
+ onQueryChange?: (query: string) => void;
27
+ /**
28
+ * Filter `items` by the query. Default `true`. Set `false` when `items` are already the results,
29
+ * e.g. fetched from a server in `onQueryChange`.
30
+ */
31
+ filter?: boolean;
32
+ /** Shows the `loading` label instead of `empty` while nothing matches. */
33
+ isLoading?: boolean;
34
+ /** Centred dialog, bottom sheet, or a sheet only on a small touch screen. */
35
+ presentation?: Presentation;
36
+ /** Text this component renders of its own. */
37
+ labels?: CommandPaletteLabels;
38
+ className?: string;
39
+ style?: CSSProperties;
40
+ classNames?: SlotClassNames<CommandPaletteSlots>;
41
+ styles?: SlotStyles<CommandPaletteSlots>;
42
+ }
@@ -0,0 +1,15 @@
1
+ import { ReactNode } from 'react';
2
+ import { MenuAction, MenuEntry } from '../menu/menu.types.js';
3
+ export interface CommandGroup {
4
+ heading?: ReactNode;
5
+ /** Whether a separator started this group, so the divider can be drawn above it. */
6
+ separated: boolean;
7
+ actions: MenuAction[];
8
+ }
9
+ /** Splits entries into groups at each heading and separator, keeping their order. */
10
+ export declare function groupCommands(items: MenuEntry[]): CommandGroup[];
11
+ /**
12
+ * The groups with only the actions matching `query`, best matches first within each group, and
13
+ * the group holding the best match first. A heading moves with its group, never away from it.
14
+ */
15
+ export declare function filterCommands(groups: CommandGroup[], query: string): CommandGroup[];
@@ -0,0 +1,2 @@
1
+ export { default } from './commandPalette.js';
2
+ export * from './commandPalette.types.js';
@@ -15,6 +15,9 @@ import { SearchInputLabels } from '../searchInput/searchInput.types.js';
15
15
  import { TooltipLabels } from '../tooltip/tooltip.types.js';
16
16
  import { VirtualListLabels } from '../virtualList/virtualList.types.js';
17
17
  import { ButtonProps } from '../button/button.types.js';
18
+ import { CopyButtonProps } from '../copyButton/copyButton.types.js';
19
+ import { CommandPaletteProps } from '../commandPalette/commandPalette.types.js';
20
+ import { WeatherIconProps } from '../weatherIcon/weatherIcon.types.js';
18
21
  import { ActionButtonsProps } from '../actionButtons/actionButtons.types.js';
19
22
  import { ConfirmModalProps } from '../confirmModal/confirmModal.types.js';
20
23
  import { BadgeProps } from '../badge/badge.types.js';
@@ -91,6 +94,7 @@ export interface ComponentDefaults {
91
94
  RadioGroup?: Partial<RadioGroupProps>;
92
95
  DatePicker?: Partial<DatePickerProps>;
93
96
  Button?: Partial<ButtonProps>;
97
+ CopyButton?: Partial<CopyButtonProps>;
94
98
  ActionButtons?: Partial<ActionButtonsProps>;
95
99
  ConfirmModal?: Partial<ConfirmModalProps>;
96
100
  Badge?: Partial<BadgeProps>;
@@ -118,6 +122,8 @@ export interface ComponentDefaults {
118
122
  RoomViewer?: Partial<RoomViewerProps>;
119
123
  Popover?: Partial<PopoverProps>;
120
124
  Modal?: Partial<ModalProps>;
125
+ CommandPalette?: Partial<CommandPaletteProps>;
126
+ WeatherIcon?: Partial<WeatherIconProps>;
121
127
  }
122
128
  export interface ConfigProviderProps {
123
129
  children: React.ReactNode;
@@ -0,0 +1,4 @@
1
+ import { ReactElement } from 'react';
2
+ import { CopyButtonProps } from './copyButton.types.js';
3
+ declare function CopyButton(props: CopyButtonProps): ReactElement;
4
+ export default CopyButton;
@@ -0,0 +1,36 @@
1
+ import { CSSProperties, MouseEvent, Ref } from 'react';
2
+ import { IconContent } from '../types/icon.types.js';
3
+ import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types.js';
4
+ export type CopyButtonSlots = 'root' | 'icon';
5
+ export interface CopyButtonLabels {
6
+ /** Accessible name and tooltip before copying. Default: "Copy". */
7
+ copy?: string;
8
+ /** Accessible name and tooltip after a successful copy, also announced. Default: "Copied". */
9
+ copied?: string;
10
+ /** Announced when the copy fails. Default: "Could not copy". */
11
+ failed?: string;
12
+ }
13
+ export interface CopyButtonProps extends Omit<HtmlProps<HTMLDivElement>, 'onCopy' | 'onError'> {
14
+ /** Text written to the clipboard on click. */
15
+ value: string;
16
+ /** How long the copied state shows, in ms. Default: 2000. */
17
+ timeout?: number;
18
+ /** Icon before copying. Default: `faCopy`. */
19
+ icon?: IconContent;
20
+ /** Icon while the copied state shows. Default: `faCheck`. */
21
+ copiedIcon?: IconContent;
22
+ /** Called after a successful copy. */
23
+ onCopy?: (value: string) => void;
24
+ /** Called when the clipboard refuses the write, e.g. outside a secure context. */
25
+ onError?: () => void;
26
+ /** Called on every click, before the copy. */
27
+ onClick?: (event: MouseEvent<HTMLDivElement>) => void;
28
+ /** Text this component renders of its own. */
29
+ labels?: CopyButtonLabels;
30
+ /** Ref to the root element. */
31
+ ref?: Ref<HTMLDivElement>;
32
+ className?: string;
33
+ style?: CSSProperties;
34
+ classNames?: SlotClassNames<CopyButtonSlots>;
35
+ styles?: SlotStyles<CopyButtonSlots>;
36
+ }
@@ -0,0 +1,2 @@
1
+ export { default } from './copyButton.js';
2
+ export * from './copyButton.types.js';
@@ -2,13 +2,10 @@ import { default as React } from 'react';
2
2
  import { ErrorBoundaryProps, ErrorBoundaryState } from './errorBoundary.types.js';
3
3
  declare class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
4
4
  state: ErrorBoundaryState;
5
- private copyResetTimeout;
6
5
  static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState>;
7
6
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
8
7
  componentDidUpdate(prevProps: ErrorBoundaryProps): void;
9
- componentWillUnmount(): void;
10
8
  private reset;
11
- private copyStackTrace;
12
9
  render(): React.ReactNode;
13
10
  }
14
11
  export default ErrorBoundary;
@@ -13,9 +13,9 @@ export interface ErrorBoundaryFallbackProps {
13
13
  export interface ErrorBoundaryLabels {
14
14
  /** Accordion header for the stack trace section. Default: "Stack trace". */
15
15
  stackTrace?: string;
16
- /** Copy button label before copying. Default: "Copy". */
16
+ /** Copy button's tooltip and accessible name before copying. Default: "Copy". */
17
17
  copy?: string;
18
- /** Copy button label right after a successful copy. Default: "Copied". */
18
+ /** Copy button's tooltip and accessible name right after a successful copy. Default: "Copied". */
19
19
  copied?: string;
20
20
  /** Reset button label. Default: "Try again". */
21
21
  reset?: string;
@@ -53,5 +53,4 @@ export interface ErrorBoundaryProps extends Omit<HtmlProps, 'title' | 'onError'>
53
53
  export interface ErrorBoundaryState {
54
54
  error: Error | null;
55
55
  errorInfo: ErrorInfo | null;
56
- copied: boolean;
57
56
  }
@@ -11,3 +11,5 @@ export type { UseSwipeDismissOptions, UseSwipeDismissResult } from './useSwipeDi
11
11
  export { useOverlay } from './useOverlay.js';
12
12
  export type { UseOverlayArgs } from './useOverlay.js';
13
13
  export { tabbablesIn } from './useFocusBoundary.js';
14
+ export { useClipboard } from './useClipboard.js';
15
+ export type { UseClipboardOptions, UseClipboardResult } from './useClipboard.js';
@@ -0,0 +1,16 @@
1
+ export interface UseClipboardOptions {
2
+ /** How long `copied` stays `true` after a successful copy, in ms. Default: 2000. */
3
+ timeout?: number;
4
+ }
5
+ export interface UseClipboardResult {
6
+ /** Writes `text` to the clipboard. Resolves `true` on success, `false` on failure. */
7
+ copy: (text: string) => Promise<boolean>;
8
+ /** `true` for `timeout` ms after a successful copy. */
9
+ copied: boolean;
10
+ /** Why the last copy failed, until the next attempt. */
11
+ error: Error | null;
12
+ /** Clears `copied` and `error` early. */
13
+ reset: () => void;
14
+ }
15
+ export declare function useClipboard({ timeout }?: UseClipboardOptions): UseClipboardResult;
16
+ export default useClipboard;
@@ -1,7 +1,11 @@
1
1
  import { default as React } from 'react';
2
2
  import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types.js';
3
3
  import { FormValidator } from '../../services/formValidation/index.js';
4
- export type InputDropdownSlots = 'root' | 'dropdown' | 'item';
4
+ export type InputDropdownSlots = 'root' | 'dropdown' | 'item' | 'empty';
5
+ export interface InputDropdownLabels {
6
+ /** Shown when nothing matches the typed text and `allowCustomValue` is off (default `No matches`). */
7
+ empty?: string;
8
+ }
5
9
  export interface InputDropdownItem {
6
10
  label?: string;
7
11
  value: string;
@@ -29,6 +33,8 @@ export interface InputDropdownProps extends Omit<HtmlProps, 'onSelect'> {
29
33
  onChange?: (value: unknown) => void;
30
34
  placeholder?: string;
31
35
  readOnly?: boolean;
36
+ /** Overrides the text InputDropdown renders of its own. Merged per key. */
37
+ labels?: InputDropdownLabels;
32
38
  className?: string;
33
39
  style?: React.CSSProperties;
34
40
  classNames?: SlotClassNames<InputDropdownSlots>;
@@ -20,6 +20,8 @@ export interface MenuAction {
20
20
  * into a `useHotkeys` map, so the one spelling covers both ends.
21
21
  */
22
22
  shortcut?: string;
23
+ /** Extra search terms for `CommandPalette`, which also matches a string `label`. `Menu` ignores them. */
24
+ keywords?: string[];
23
25
  disabled?: boolean;
24
26
  /** Renders in the destructive colour. For deleting, discarding, revoking. */
25
27
  danger?: boolean;
@@ -9,6 +9,11 @@ interface State {
9
9
  resolvedVariables: Record<string, string>;
10
10
  colorFadeActive: boolean;
11
11
  currentThemeColor: string | null;
12
+ /** What `resolvedVariables` was resolved from, to tell a theme change from any other render. */
13
+ resolvedFrom: {
14
+ themes: Theme[] | undefined;
15
+ currentThemeId: string | undefined;
16
+ };
12
17
  }
13
18
  declare class ThemeProvider extends Component<Props, State> {
14
19
  state: State;
@@ -16,6 +21,7 @@ declare class ThemeProvider extends Component<Props, State> {
16
21
  private wrapperRef;
17
22
  private themeColorMeta;
18
23
  private createdThemeColorMeta;
24
+ static getDerivedStateFromProps(props: Props, state: State): Partial<State> | null;
19
25
  componentDidMount(): void;
20
26
  componentDidUpdate(prevProps: Props, prevState: State): void;
21
27
  componentWillUnmount(): void;
@@ -26,13 +32,8 @@ declare class ThemeProvider extends Component<Props, State> {
26
32
  * (and removes it again on unmount).
27
33
  */
28
34
  private syncThemeColorMeta;
29
- /**
30
- * Resolves the full variable set for a given theme ID by walking up the
31
- * baseThemeId chain. Returns variables ordered from deepest ancestor to
32
- * the requested theme so callers can merge in order (later entries win).
33
- */
34
- private resolveChain;
35
- private applyTheme;
35
+ /** `--primary-color` as the browser resolved it, which may be a `var()` of another variable. */
36
+ private readThemeColor;
36
37
  render(): React.JSX.Element;
37
38
  }
38
39
  export default ThemeProvider;
@@ -0,0 +1,3 @@
1
+ export { default } from './weatherIcon.js';
2
+ export { WeatherType } from './weatherIcon.types.js';
3
+ export type { WeatherIconLabels, WeatherIconProps } from './weatherIcon.types.js';
@@ -0,0 +1,26 @@
1
+ import { ReactElement } from 'react';
2
+ import { WeatherPartProps } from './weatherIcon.types.js';
3
+ /** Exported so a scene can clip what sits behind the cloud to this same silhouette. */
4
+ export declare const CLOUD_PATH = "M 26.3 62.9 C 15.4 65.0 15.0 48.5 27.8 49.8 C 23.9 43.9 34.2 34.0 43.0 39.6 C 46.0 26.2 73.9 32.0 68.9 46.3 C 85.0 44.3 84.6 64.2 72.4 62.5 Z";
5
+ export declare function Cloud({ className, delayed }: {
6
+ className?: string;
7
+ delayed?: boolean;
8
+ }): ReactElement;
9
+ export declare function Sun({ className }: {
10
+ className?: string;
11
+ }): ReactElement;
12
+ export declare function Moon({ className }: {
13
+ className?: string;
14
+ }): ReactElement;
15
+ export declare enum RainIntensity {
16
+ Light = "light",
17
+ Medium = "medium",
18
+ Heavy = "heavy"
19
+ }
20
+ export declare function Rain({ animated, intensity, }: WeatherPartProps & {
21
+ intensity?: RainIntensity;
22
+ }): ReactElement;
23
+ export declare function Snow({ animated }: WeatherPartProps): ReactElement;
24
+ export declare function Hail({ animated }: WeatherPartProps): ReactElement;
25
+ export declare function Fog(): ReactElement;
26
+ export declare function Lightning(): ReactElement;
@@ -0,0 +1,4 @@
1
+ import { ReactElement } from 'react';
2
+ import { WeatherIconProps } from './weatherIcon.types.js';
3
+ declare function WeatherIcon(props: WeatherIconProps): ReactElement;
4
+ export default WeatherIcon;
@@ -0,0 +1,44 @@
1
+ import { CSSProperties, SVGAttributes } from 'react';
2
+ import { DataProps } from '../types/slots.types.js';
3
+ export declare enum WeatherType {
4
+ Sunny = "sunny",
5
+ ClearNight = "clearNight",
6
+ Cloudy = "cloudy",
7
+ PartlyCloudy = "partlyCloudy",
8
+ PartlyCloudyNight = "partlyCloudyNight",
9
+ RainyLight = "rainyLight",
10
+ Rainy = "rainy",
11
+ RainyHeavy = "rainyHeavy",
12
+ Snowy = "snowy",
13
+ Hail = "hail",
14
+ Foggy = "foggy",
15
+ Thunderstorm = "thunderstorm"
16
+ }
17
+ /** The accessible name of each weather, read out by screen readers. */
18
+ export type WeatherIconLabels = Partial<Record<`${WeatherType}`, string>>;
19
+ /**
20
+ * The root is an `<svg>`, so the native attributes come from `SVGAttributes` rather than
21
+ * `HtmlProps`. Its `type` is the attribute of `<script>` and `<style>`, not of an `<svg>`.
22
+ */
23
+ export interface WeatherIconProps extends DataProps, Omit<SVGAttributes<SVGSVGElement>, 'type'> {
24
+ /**
25
+ * The weather to draw. Takes the enum or its string value, so an API's own enum with the same
26
+ * values passes as it is.
27
+ */
28
+ type: WeatherType | `${WeatherType}`;
29
+ /** Width and height, any CSS size. Default `64`. */
30
+ size?: number | string;
31
+ /**
32
+ * Default `true`. `false` draws a still frame, for small or repeated icons where the motion is
33
+ * too small to see but costs the same. Always still under `prefers-reduced-motion`.
34
+ */
35
+ animated?: boolean;
36
+ /** Text this component renders of its own. */
37
+ labels?: WeatherIconLabels;
38
+ className?: string;
39
+ style?: CSSProperties;
40
+ }
41
+ /** What a falling part needs to know to pick its still frame. */
42
+ export interface WeatherPartProps {
43
+ animated: boolean;
44
+ }
@@ -31,10 +31,14 @@ export { default as Chip } from './common/chip/index.js';
31
31
  export * from './common/chip/index.js';
32
32
  export { default as ColorPicker } from './common/colorPicker/index.js';
33
33
  export * from './common/colorPicker/index.js';
34
+ export { default as CommandPalette } from './common/commandPalette/index.js';
35
+ export * from './common/commandPalette/index.js';
34
36
  export { default as ConfigProvider } from './common/configProvider/index.js';
35
37
  export * from './common/configProvider/index.js';
36
38
  export { default as ConfirmModal } from './common/confirmModal/index.js';
37
39
  export * from './common/confirmModal/index.js';
40
+ export { default as CopyButton } from './common/copyButton/index.js';
41
+ export * from './common/copyButton/index.js';
38
42
  export { default as DatePicker } from './common/datePicker/index.js';
39
43
  export * from './common/datePicker/index.js';
40
44
  export { default as Divider } from './common/divider/index.js';
@@ -146,6 +150,8 @@ export { default as Tree } from './common/tree/index.js';
146
150
  export * from './common/tree/index.js';
147
151
  export { default as VirtualList } from './common/virtualList/index.js';
148
152
  export * from './common/virtualList/index.js';
153
+ export { default as WeatherIcon } from './common/weatherIcon/index.js';
154
+ export * from './common/weatherIcon/index.js';
149
155
  export { default as Wizard } from './common/wizard/index.js';
150
156
  export * from './common/wizard/index.js';
151
157
  export * from './common/hooks/index.js';
@@ -77,6 +77,8 @@ import { Input } from '@ahrowe/ui';
77
77
 
78
78
  **How it works:** the first and last children keep rounded outer corners; every child in between is flattened on both sides. Adjacent borders are collapsed with a `-1px` margin so two 1px borders don't stack into a visible double line, and whichever child is hovered/focused gets a `z-index` bump so its border/focus ring isn't clipped by its neighbors.
79
79
 
80
+ **One border per seam:** the overlap alone keeps both borders, and at a fractional scale (browser zoom, a 125% or 150% display) they round to different device pixels, so one seam reads 2px next to a 1px one. Each child therefore clips away the 1px it shares with the next one, and the later child's border is the one you see, as it always was. A focused child keeps its whole border, and the next child clips its shared 1px instead. This is a `clip-path` on the children, so a `clip-path` of your own on a grouped child is overridden.
81
+
80
82
  Flattening reaches a child's visible border even when that border doesn't live on the child's own root element — the case for `Input`, `Dropdown`, and everything built on either of them (`NumberInput`, `DatePicker`, `TimeInput`, `InputDropdown`, `Textarea`), whose actual border sits on a `fieldset` nested inside a `<label>`, as well as `ColorPicker`'s swatch and `Checkbox`'s checkmark box. Alongside the direct `border-radius` reset it applies to each child's root, `ButtonGroup` also sets `--group-radius-tl`/`-tr`/`-br`/`-bl` custom properties per position. Unlike `border-radius` itself, custom properties inherit through any nesting depth, so a descendant whose own `border-radius` reads `var(--group-radius-tl, var(--default-border-radius))` (etc.) picks up the right corner overrides no matter how deep it's nested — no consumer-facing prop needed.
81
83
 
82
84
  **Separators:** any child with `role="separator"` (what `Divider` sets) is excluded from that border-collapsing margin, on both sides — it stays fully visible between its neighbors instead of being partially hidden under one of them. Use a vertical `Divider` (see [Divider.md](Divider.md)) between two buttons when their style has no visible border of its own to merge (`Primary`, `Delete`), so there's still a clear seam between them.
package/docs/CLAUDE.md CHANGED
@@ -59,8 +59,10 @@ enums and slot names. Do not infer props from a component's name.
59
59
  | Checkbox.md | Checkbox | Boolean toggle input — settings, multi-select lists, accept terms, feature flags |
60
60
  | Chip.md | Chip, ChipContainer, ChipStyleType | Compact tag, label, or filter token — selected items, applied filters, category badges, user avatars with remove |
61
61
  | ColorPicker.md | ColorPicker | Hex colour selection — theme editors, annotation tools, customisation panels |
62
+ | CommandPalette.md | CommandPalette | A search-first dialog for running any command or jumping anywhere in the app from the keyboard, usually opened with ⌘K / Ctrl+K |
62
63
  | ConfigProvider.md | ConfigProvider | Set global **default props** for library components once, near the root of your app, instead of repeating the same prop on every instance |
63
64
  | ConfirmModal.md | ConfirmModal | Confirmation dialog before destructive or irreversible actions — delete, archive, send, overwrite |
65
+ | CopyButton.md | CopyButton | Icon-only button that copies a value to the clipboard: an invoice number, an ID in a table row, an API key or share link inside an `Input` |
64
66
  | DatePicker.md | DatePicker | Date selection input — booking forms, birth date fields, date range pickers, deadline selectors |
65
67
  | Divider.md | Divider, DividerTextPosition, DividerOrientation | A rule that separates sections of content — horizontal by default, filling the width of its parent |
66
68
  | Drawer.md | Drawer | Inline horizontal panel that collapses and expands by animating its width — side panels, navigation rails, detail/filter panes that slide open beside the main content |
@@ -73,7 +75,7 @@ enums and slot names. Do not infer props from a component's name.
73
75
  | FloorPlan.md | emptyFloorPlan, addWall, derivePlan | The shared data model behind `RoomDrawer` and `RoomViewer` |
74
76
  | FormValidator.md | FormValidator, Validators, useFormValidator | Track and validate a single form field's value, dirty/touched state, and errors |
75
77
  | FormValidatorGroup.md | useFormValidatorGroup, FormValidatorGroup, FormValidator, Validators | Manage a whole form — multiple fields, a single submit handler, validate-on-submit, dirty/touched tracking across the form, and mapping server-side errors back onto the right fields |
76
- | Hooks.md | useHotkeys, formatHotkey, useMediaQuery, useCoarsePointer, useVisualViewportSize, useScrollEdges, useSwipeDismiss, useOverlay, tabbablesIn | Standalone React hooks the library uses internally and publishes for the same jobs in your own code: keyboard shortcuts, media queries, the mobile keyboard's viewport, scroll-edge detection, swipe-to-dismiss, and the Escape / focus-trap / scroll-lock behaviour behind a custom overlay |
78
+ | Hooks.md | useHotkeys, formatHotkey, useMediaQuery, useCoarsePointer, useVisualViewportSize, useScrollEdges, useSwipeDismiss, useOverlay, useClipboard, tabbablesIn | Standalone React hooks the library uses internally and publishes for the same jobs in your own code: keyboard shortcuts, media queries, the mobile keyboard's viewport, scroll-edge detection, swipe-to-dismiss, copying to the clipboard, and the Escape / focus-trap / scroll-lock behaviour behind a custom overlay |
77
79
  | IconPicker.md | IconPicker | Pick one item from a visual icon/image grid — category selectors, emoji-style pickers, avatar selectors, anything where each option is best represented by an icon or image rather than text alone |
78
80
  | IdleManager.md | IdleManager | Show something only once the user has stopped interacting — a screen saver, a session-timeout warning, an "are you still there?" prompt, an idle dashboard view |
79
81
  | Input.md | Input, InputType | Text input with floating label, validation state, and type variants |
@@ -119,6 +121,7 @@ enums and slot names. Do not infer props from a component's name.
119
121
  | Tooltip.md | Tooltip | Contextual help, info icons, field explanations, error pointers |
120
122
  | Tree.md | Tree, flattenTree | A hierarchical list the user can expand, collapse and pick from: categories, nested locations, folder structures, org charts |
121
123
  | VirtualList.md | VirtualList | Efficiently render large lists or tables — thousands of rows rendered with a virtualised scroll window so only visible rows are in the DOM |
124
+ | WeatherIcon.md | WeatherIcon, WeatherType | An animated weather symbol for a forecast, a dashboard widget or a day selector, one per condition a forecast reports |
122
125
  | Wizard.md | Wizard | Animated step container for multi-step forms and flows |
123
126
 
124
127
  <!-- components:end -->
@@ -0,0 +1,84 @@
1
+ # CommandPalette
2
+
3
+ **When to use:** A search-first dialog for running any command or jumping anywhere in the app from the keyboard, usually opened with ⌘K / Ctrl+K. It takes the same `MenuEntry[]` as [Menu](Menu.md), so one list can feed the menus, the palette and the shortcuts. For picking a value in a form, use [InputDropdown](InputDropdown.md).
4
+
5
+ **Keywords:** cmd+k, ctrl+k, command menu, quick open, launcher, spotlight, go to anything, jump to, omnibox
6
+
7
+ **Import:** `import { CommandPalette } from '@ahrowe/ui'`
8
+ **Types:** `import type { CommandPaletteProps, CommandPaletteLabels } from '@ahrowe/ui'`
9
+
10
+ **Requires:** `<div id="bodyEnd"></div>` in your app: the palette is a [Modal](Modal.md) and portals there.
11
+
12
+ ```tsx
13
+ import { CommandPalette, menuHotkeys, useHotkeys } from '@ahrowe/ui';
14
+ import type { MenuEntry } from '@ahrowe/ui';
15
+
16
+ const commands: MenuEntry[] = [
17
+ { heading: 'Invoice' },
18
+ { id: 'new-invoice', label: 'New invoice', icon: faPlus, shortcut: 'mod+n', onClick: createInvoice },
19
+ { id: 'export', label: 'Export as PDF', icon: faFilePdf, keywords: ['download', 'print'], onClick: exportPdf },
20
+ { heading: 'Go to' },
21
+ { id: 'customers', label: 'Customers', onClick: () => navigate('/customers') },
22
+ ];
23
+
24
+ function App() {
25
+ const [isOpen, setOpen] = useState(false);
26
+ useHotkeys({ ...menuHotkeys(commands), 'mod+k': () => setOpen(true) });
27
+
28
+ return <CommandPalette items={commands} isOpen={isOpen} onClose={() => setOpen(false)} />;
29
+ }
30
+ ```
31
+
32
+ **Opening it is yours.** The palette binds no shortcut of its own, for the same reason `Menu` does not: if it listened for ⌘K and your app did too, it would open twice. `menuHotkeys` binds each command's `shortcut`, so the hint the palette draws and the key that fires stay one spelling.
33
+
34
+ **Filtering** matches every typed word, in any order, against a string `label` and the command's `keywords`, ignoring case and accents: `uber` finds `Übersicht`. Within each group, a label starting with the query comes first, then a label word or a keyword starting with it, then any other match, and the group holding the best match moves to the top. A heading moves with its group and stays above it while any of it matches; separators are dropped while a query is typed. Typing highlights the best match, so Enter runs it. A `label` that is not a string cannot be read, so give such a command `keywords`.
35
+
36
+ **Server results.** Set `filter={false}` and the palette shows `items` exactly as given, so they can be the results of your own search:
37
+
38
+ ```tsx
39
+ const [results, setResults] = useState<MenuEntry[]>(commands);
40
+ const [isLoading, setLoading] = useState(false);
41
+
42
+ <CommandPalette
43
+ items={results}
44
+ filter={false}
45
+ isLoading={isLoading}
46
+ onQueryChange={(query) => {
47
+ if (!query) return setResults(commands);
48
+ setLoading(true);
49
+ searchCustomers(query).then((hits) => {
50
+ setResults(hits.map((c) => ({ id: c.id, label: c.name, onClick: () => navigate(`/customers/${c.id}`) })));
51
+ setLoading(false);
52
+ });
53
+ }}
54
+ isOpen={isOpen}
55
+ onClose={() => setOpen(false)}
56
+ />;
57
+ ```
58
+
59
+ `onQueryChange` fires on every keystroke and with `''` each time the palette opens, so debounce the request and drop a response that arrives after a newer query. Between keystrokes the highlight follows the command's `id`, so it stays put while results arrive and are replaced around it.
60
+
61
+ **Keyboard:** the focus stays in the search field. ↑ and ↓ move the highlight, wrapping and skipping disabled commands, Enter runs it, and Escape closes. Running a command calls its own `onClick`, then `onAction`, then `onClose`, the same order `Menu` uses. The number of matches is announced to screen readers as it changes.
62
+
63
+ On a small touch screen it can present as a bottom sheet like every `Modal`: pass `presentation={Presentation.Auto}`, or set it app-wide through `ConfigProvider`.
64
+
65
+ **Key props:**
66
+
67
+ | Prop | Type | Description |
68
+ |------|------|-------------|
69
+ | `items` | `MenuEntry[]` | The commands. Headings group them; separators split groups (required) |
70
+ | `isOpen` | `boolean` | Whether the palette is open (required) |
71
+ | `onClose` | `() => void` | Called on Escape, a backdrop click, and after a command runs (required) |
72
+ | `onAction` | `(id: string) => void` | Called with the id of the command that ran, after its own `onClick` |
73
+ | `onQueryChange` | `(query: string) => void` | Called on every keystroke, and with `''` on open |
74
+ | `filter` | `boolean` | Filter `items` by the query (default `true`). `false` shows them as given |
75
+ | `isLoading` | `boolean` | Shows the `loading` label instead of `empty` while nothing matches |
76
+ | `presentation` | `Presentation` | Centred dialog (default), bottom sheet, or a sheet only on a small touch screen |
77
+
78
+ A command is a `MenuAction`: `id`, `label`, `icon`, `shortcut`, `keywords`, `disabled`, `danger` and `onClick`. See [Menu.md](Menu.md).
79
+
80
+ **Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ CommandPalette: { presentation: Presentation.Auto } }}`. See [ConfigProvider.md](ConfigProvider.md).
81
+
82
+ **Slots:** `root` `search` `input` `list` `item` `itemIcon` `itemLabel` `itemShortcut` `heading` `separator` `empty`
83
+
84
+ **Labels:** `label` `placeholder` `empty` `loading` `results(count)`: the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `CommandPaletteLabels`.
@@ -92,8 +92,8 @@ A nested provider inherits it unless it sets its own, the same way `defaultProps
92
92
  Each entry is a `Partial<...Props>`, so any of that component's props can be defaulted:
93
93
 
94
94
  - **Form inputs:** `Input` · `Textarea` · `NumberInput` · `Dropdown` · `InputDropdown` · `Checkbox` · `Switch` · `RadioGroup` · `DatePicker` · `OptionPicker` · `OtpInput`
95
- - **Display:** `Button` · `ActionButtons` · `Badge` · `Chip` · `Card` · `SectionHeader` · `Skeleton` · `Accordion` · `Divider` · `Timer`
96
- - **Overlays:** `ConfirmModal` · `Modal` · `Popover` · `Popover`
95
+ - **Display:** `Button` · `CopyButton` · `ActionButtons` · `Badge` · `Chip` · `Card` · `SectionHeader` · `Skeleton` · `Accordion` · `Divider` · `Timer` · `WeatherIcon`
96
+ - **Overlays:** `ConfirmModal` · `Modal` · `CommandPalette` · `Popover`
97
97
  - **Editors:** `RoomDrawer`, `RoomViewer`
98
98
 
99
99
  ```tsx
@@ -0,0 +1,45 @@
1
+ # CopyButton
2
+
3
+ **When to use:** Icon-only button that copies a value to the clipboard: an invoice number, an ID in a table row, an API key or share link inside an `Input`. For a labelled "Copy link" button, use `Button` with the `useClipboard` hook instead (see [Hooks.md](Hooks.md)).
4
+
5
+ **Keywords:** token, secret, reference number, snippet
6
+
7
+ **Import:** `import { CopyButton } from '@ahrowe/ui'`
8
+ **Types:** `import type { CopyButtonProps, CopyButtonLabels } from '@ahrowe/ui'`
9
+
10
+ ```tsx
11
+ import { CopyButton, Input } from '@ahrowe/ui';
12
+
13
+ <span>
14
+ {invoice.number} <CopyButton value={invoice.number} />
15
+ </span>
16
+
17
+ // inside a field
18
+ <Input label='API key' value={apiKey} readOnly suffix={<CopyButton value={apiKey} />} />
19
+
20
+ // a toast on success, where the button is not where the user is looking
21
+ <CopyButton value={url} onCopy={() => showToast('Link copied', { type: 'success' })} />
22
+ ```
23
+
24
+ After a click the icon rotates into a check (through `AnimatedIcon`, held still under `prefers-reduced-motion`) for `timeout` ms, the accessible name and tooltip read "Copied", and a polite live region announces it, because a swapped icon or a changed `aria-label` alone is not read out. A refused write (an insecure context, a blocked iframe) announces "Could not copy" and calls `onError`; the icon stays as it was. `CopyButton` shows no toast of its own: pass one through `onCopy` / `onError` if you want it.
25
+
26
+ The live region is rendered as a sibling of the button, so `CopyButton` returns two elements. It is visually hidden and takes no space.
27
+
28
+ **Key props:**
29
+
30
+ | Prop | Type | Description |
31
+ |------|------|-------------|
32
+ | `value` | `string` | Text written to the clipboard on click (required) |
33
+ | `timeout` | `number` | How long the copied state shows, in ms (default `2000`) |
34
+ | `icon` | `IconDefinition \| ReactElement` | Icon before copying (default `faCopy`). The swap animates only when both icons are FontAwesome definitions |
35
+ | `copiedIcon` | `IconDefinition \| ReactElement` | Icon while copied (default `faCheck`) |
36
+ | `onCopy` | `(value: string) => void` | Called after a successful copy |
37
+ | `onError` | `() => void` | Called when the clipboard refuses the write. `useClipboard` returns the reason |
38
+ | `onClick` | `(event: MouseEvent) => void` | Called on every click, before the copy |
39
+ | `ref` | `Ref<HTMLDivElement>` | Ref to the button |
40
+
41
+ **Global defaults:** adopts `ConfigProvider`, e.g. `defaultProps={{ CopyButton: { timeout: 1500 } }}`. See [ConfigProvider.md](ConfigProvider.md).
42
+
43
+ **Slots:** `root` `icon`
44
+
45
+ **Labels:** `copy` `copied` `failed`: the text this component renders of its own. Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `CopyButtonLabels`.
@@ -65,7 +65,7 @@ import { ErrorBoundary } from '@ahrowe/ui';
65
65
  </ErrorBoundary>
66
66
  ```
67
67
 
68
- The default fallback is a centered card with a tinted icon badge, an `Accordion` for the collapsible stack trace (with a labeled copy `Button` that fires a `Toast` confirmation if `ToastProvider` is mounted), and a `Button` for "Try again".
68
+ The default fallback is a centered card with a tinted icon badge, an `Accordion` for the collapsible stack trace (with a `CopyButton` that also fires a `Toast` confirmation if `ToastProvider` is mounted), and a `Button` for "Try again".
69
69
 
70
70
  **Key props:**
71
71
 
@@ -74,7 +74,7 @@ The default fallback is a centered card with a tinted icon badge, an `Accordion`
74
74
  | `children` | `ReactNode` | The subtree to protect |
75
75
  | `title` | `ReactNode` | Heading in the default fallback (default `"Something went wrong"`) |
76
76
  | `description` | `ReactNode` | Overrides the message shown under the title. Defaults to `error.message` |
77
- | `labels` | `ErrorBoundaryLabels` | Overrides for every other built-in string (stack trace label, copy/reset button text, copy toasts) — pass a translated set for non-English apps. See below |
77
+ | `labels` | `ErrorBoundaryLabels` | Overrides for every other built-in string (stack trace label, copy button tooltip, reset button text, copy toasts) — pass a translated set for non-English apps. See below |
78
78
  | `fallback` | `ReactNode \| (props: ErrorBoundaryFallbackProps) => ReactNode` | Replaces the built-in fallback entirely. The render-function form receives `{ error, errorInfo, resetErrorBoundary }` |
79
79
  | `onError` | `(error: Error, errorInfo: ErrorInfo) => void` | Called once per catch — the place to report to Sentry/Datadog/etc. |
80
80
  | `onReset` | `() => void` | Called after the boundary is reset, whether via the built-in "Try again" button, a custom fallback calling `resetErrorBoundary`, or a `resetKeys` change. Use it to re-arm whatever state caused the crash |
@@ -86,8 +86,8 @@ The default fallback is a centered card with a tinted icon badge, an `Accordion`
86
86
  | Field | Default | Description |
87
87
  |-------|---------|--------------|
88
88
  | `stackTrace` | `"Stack trace"` | Accordion header for the stack trace section |
89
- | `copy` | `"Copy"` | Copy button label before copying |
90
- | `copied` | `"Copied"` | Copy button label right after a successful copy |
89
+ | `copy` | `"Copy"` | Copy button tooltip and accessible name before copying |
90
+ | `copied` | `"Copied"` | Copy button tooltip and accessible name right after a successful copy |
91
91
  | `reset` | `"Try again"` | Reset button label |
92
92
  | `copySuccessToast` | `"Stack trace copied to clipboard"` | Toast shown after a successful copy (only visible if `ToastProvider` is mounted) |
93
93
  | `copyErrorToast` | `"Could not copy stack trace"` | Toast shown if the copy fails |
package/docs/Fab.md CHANGED
@@ -22,7 +22,6 @@ import { faPlus, faPen, faCamera } from '@fortawesome/free-solid-svg-icons';
22
22
  { id: 'photo', icon: faCamera, label: 'Photo' },
23
23
  ]}
24
24
  onMenuEntryClicked={(id) => handleAction(id)}
25
- closeMenuOnEntryClicked
26
25
  />
27
26
 
28
27
  // Mini size
@@ -38,7 +37,7 @@ import { faPlus, faPen, faCamera } from '@fortawesome/free-solid-svg-icons';
38
37
  | `size` | `'default' \| 'mini'` | Button size |
39
38
  | `menuEntries` | `FabMenuEntry[]` | `{ id, icon, label? }` — enables speed dial |
40
39
  | `onMenuEntryClicked` | `(id: string) => void` | Sub-menu entry handler |
41
- | `closeMenuOnEntryClicked` | `boolean` | Auto-close menu after selection |
40
+ | `closeMenuOnEntryClicked` | `boolean` | Close the menu after an entry is picked (default `true`) |
42
41
  | `onClick` | `() => void` | Simple click handler (no speed dial) |
43
42
  | `children` | `ReactNode` | FAB icon content |
44
43