@ahrowe/ui 0.13.0 → 0.14.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.
- package/dist/esm/common/animatedIcon/animatedIcon.mjs +2 -0
- package/dist/esm/common/animatedIcon/animatedIcon.mjs.map +1 -0
- package/dist/esm/common/animatedIcon/animatedIcon.module.mjs +2 -0
- package/dist/esm/common/animatedIcon/animatedIcon.module.mjs.map +1 -0
- package/dist/esm/common/breadcrumb/breadcrumb.mjs +1 -1
- package/dist/esm/common/breadcrumb/breadcrumb.mjs.map +1 -1
- package/dist/esm/common/buttonGroup/buttonGroup.mjs +2 -0
- package/dist/esm/common/buttonGroup/buttonGroup.mjs.map +1 -0
- package/dist/esm/common/buttonGroup/buttonGroup.module.mjs +2 -0
- package/dist/esm/common/buttonGroup/buttonGroup.module.mjs.map +1 -0
- package/dist/esm/common/buttonGroup/buttonGroup.types.mjs +2 -0
- package/dist/esm/common/buttonGroup/buttonGroup.types.mjs.map +1 -0
- package/dist/esm/common/divider/divider.mjs +1 -1
- package/dist/esm/common/divider/divider.mjs.map +1 -1
- package/dist/esm/common/divider/divider.module.mjs +1 -1
- package/dist/esm/common/divider/divider.module.mjs.map +1 -1
- package/dist/esm/common/divider/divider.types.mjs +1 -1
- package/dist/esm/common/divider/divider.types.mjs.map +1 -1
- package/dist/esm/common/dropdown/dropdown.module.mjs +1 -1
- package/dist/esm/common/dropdown/dropdown.module.mjs.map +1 -1
- package/dist/esm/common/fab/fab.mjs +1 -1
- package/dist/esm/common/fab/fab.mjs.map +1 -1
- package/dist/esm/common/floatingMenu/floatingMenu.mjs +1 -1
- package/dist/esm/common/floatingMenu/floatingMenu.mjs.map +1 -1
- package/dist/esm/common/floatingMenu/floatingMenu.module.mjs.map +1 -1
- package/dist/esm/common/hooks/useFocusBoundary.mjs +2 -0
- package/dist/esm/common/hooks/useFocusBoundary.mjs.map +1 -0
- package/dist/esm/common/input/input.mjs +1 -1
- package/dist/esm/common/input/input.mjs.map +1 -1
- package/dist/esm/common/input/input.module.mjs +1 -1
- package/dist/esm/common/input/input.module.mjs.map +1 -1
- package/dist/esm/common/input/input.types.mjs +1 -1
- package/dist/esm/common/input/input.types.mjs.map +1 -1
- package/dist/esm/common/interactableDiv/interactableDiv.mjs +1 -1
- package/dist/esm/common/interactableDiv/interactableDiv.mjs.map +1 -1
- package/dist/esm/common/kanbanBoard/KanbanBoard.mjs +6 -1
- package/dist/esm/common/kanbanBoard/KanbanBoard.mjs.map +1 -1
- package/dist/esm/common/kanbanBoard/KanbanColumn.mjs +1 -1
- package/dist/esm/common/kanbanBoard/KanbanColumn.mjs.map +1 -1
- package/dist/esm/common/kanbanBoard/KanbanItem.mjs +1 -1
- package/dist/esm/common/kanbanBoard/KanbanItem.mjs.map +1 -1
- package/dist/esm/common/kanbanBoard/kanbanBoard.module.mjs +1 -1
- package/dist/esm/common/kanbanBoard/kanbanBoard.module.mjs.map +1 -1
- package/dist/esm/common/kanbanBoard/kanbanBoard.utils.mjs +2 -0
- package/dist/esm/common/kanbanBoard/kanbanBoard.utils.mjs.map +1 -0
- package/dist/esm/common/klipyPicker/klipyPicker.mjs +1 -1
- package/dist/esm/common/klipyPicker/klipyPicker.mjs.map +1 -1
- package/dist/esm/common/numberInput/numberInput.mjs +1 -1
- package/dist/esm/common/numberInput/numberInput.mjs.map +1 -1
- package/dist/esm/common/popover/popover.mjs +1 -1
- package/dist/esm/common/popover/popover.mjs.map +1 -1
- package/dist/esm/common/splitButton/splitButton.mjs +2 -0
- package/dist/esm/common/splitButton/splitButton.mjs.map +1 -0
- package/dist/esm/common/splitButton/splitButton.module.mjs +2 -0
- package/dist/esm/common/splitButton/splitButton.module.mjs.map +1 -0
- package/dist/esm/common/virtualList/useRowDrag.mjs +1 -1
- package/dist/esm/common/virtualList/useRowDrag.mjs.map +1 -1
- package/dist/esm/common/virtualList/useVirtualWindow.mjs +1 -1
- package/dist/esm/common/virtualList/useVirtualWindow.mjs.map +1 -1
- package/dist/esm/common/virtualList/virtualList.mjs +1 -1
- package/dist/esm/common/virtualList/virtualList.mjs.map +1 -1
- package/dist/esm/common/virtualList/virtualRow.mjs +1 -1
- package/dist/esm/common/virtualList/virtualRow.mjs.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/index.cjs +9 -4
- package/dist/index.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/package/common/animatedIcon/animatedIcon.d.ts +3 -0
- package/dist/types/package/common/animatedIcon/animatedIcon.types.d.ts +25 -0
- package/dist/types/package/common/animatedIcon/index.d.ts +2 -0
- package/dist/types/package/common/buttonGroup/buttonGroup.d.ts +4 -0
- package/dist/types/package/common/buttonGroup/buttonGroup.types.d.ts +12 -0
- package/dist/types/package/common/buttonGroup/index.d.ts +2 -0
- package/dist/types/package/common/configProvider/configProvider.types.d.ts +2 -0
- package/dist/types/package/common/divider/divider.types.d.ts +5 -0
- package/dist/types/package/common/hooks/useFocusBoundary.d.ts +20 -0
- package/dist/types/package/common/input/input.types.d.ts +5 -0
- package/dist/types/package/common/kanbanBoard/KanbanBoard.d.ts +1 -1
- package/dist/types/package/common/kanbanBoard/KanbanColumn.d.ts +29 -1
- package/dist/types/package/common/kanbanBoard/KanbanItem.d.ts +2 -6
- package/dist/types/package/common/kanbanBoard/kanbanBoard.types.d.ts +71 -5
- package/dist/types/package/common/kanbanBoard/kanbanBoard.utils.d.ts +60 -0
- package/dist/types/package/common/splitButton/index.d.ts +2 -0
- package/dist/types/package/common/splitButton/splitButton.d.ts +3 -0
- package/dist/types/package/common/splitButton/splitButton.types.d.ts +28 -0
- package/dist/types/package/common/themeProvider/theme.types.d.ts +3 -0
- package/dist/types/package/common/virtualList/useRowDrag.d.ts +4 -0
- package/dist/types/package/common/virtualList/useVirtualWindow.d.ts +3 -1
- package/dist/types/package/common/virtualList/virtualList.types.d.ts +5 -1
- package/dist/types/package/common/virtualList/virtualRow.d.ts +8 -1
- package/dist/types/package/index.d.ts +4 -0
- package/docs/ButtonGroup.md +90 -0
- package/docs/CLAUDE.md +2 -0
- package/docs/Divider.md +15 -3
- package/docs/FloatingMenu.md +7 -3
- package/docs/Input.md +30 -6
- package/docs/KanbanBoard.md +129 -4
- package/docs/NumberInput.md +2 -0
- package/docs/SearchInput.md +6 -0
- package/docs/SplitButton.md +79 -0
- package/docs/VirtualList.md +86 -3
- package/package.json +2 -4
|
@@ -4,6 +4,34 @@ interface KanbanColumnInternalProps<T> extends KanbanColumnProps {
|
|
|
4
4
|
itemIds: string[];
|
|
5
5
|
items: Record<string, T>;
|
|
6
6
|
renderItem: (item: T, id: string) => ReactNode;
|
|
7
|
+
dragHandle?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Keep the column at its normal flex width even when `itemIds` is empty. Used while a drag
|
|
10
|
+
* is in progress: the empty-column collapse is driven by transient shadow state during a
|
|
11
|
+
* drag, and letting the column shrink mid-drag shifts every column's layout underneath the
|
|
12
|
+
* pointer, which can flip the collision target back and forth forever.
|
|
13
|
+
*/
|
|
14
|
+
preserveSizeWhileEmpty?: boolean;
|
|
15
|
+
/** Shrink to a narrow strip when empty — see `KanbanBoardProps.collapseEmptyColumns`. Defaults to `true`. */
|
|
16
|
+
collapseEmptyColumns?: boolean;
|
|
17
|
+
/** Work-in-progress limit — see `KanbanColumnDef.maxItems`. Display-only. */
|
|
18
|
+
maxItems?: number;
|
|
19
|
+
/** Freezes the column — see `KanbanColumnDef.disabled`. */
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
/** Returns whether a specific card can't be picked up — see `KanbanBoardProps.getItemDisabled`. */
|
|
22
|
+
getItemDisabled?: (item: T, id: string) => boolean;
|
|
23
|
+
/** Custom content shown when the column has no cards — see `KanbanBoardProps.renderEmptyColumn`. */
|
|
24
|
+
emptyPlaceholder?: ReactNode;
|
|
25
|
+
/** Custom header actions — see `KanbanBoardProps.renderColumnActions`. */
|
|
26
|
+
actions?: ReactNode;
|
|
27
|
+
/** Allow the whole column to be dragged (by its header) to reorder it — see `KanbanBoardProps.reorderableColumns`. */
|
|
28
|
+
reorderable?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the card currently being dragged (if any) is actually a column. Suppresses the
|
|
31
|
+
* cards drop-target highlight, which otherwise lights up on every column a dragged column
|
|
32
|
+
* passes over — that highlight means "drop a card here", which doesn't apply mid-column-drag.
|
|
33
|
+
*/
|
|
34
|
+
activeIsColumn?: boolean;
|
|
7
35
|
}
|
|
8
|
-
declare function KanbanColumn<T>({ id, title, itemIds, items, renderItem, className, classNames, styles: slotStyles, }: KanbanColumnInternalProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
36
|
+
declare function KanbanColumn<T>({ id, title, itemIds, items, renderItem, dragHandle, preserveSizeWhileEmpty, collapseEmptyColumns, maxItems, disabled, getItemDisabled, emptyPlaceholder, actions, reorderable, activeIsColumn, className, style, classNames, styles: slotStyles, ...rest }: KanbanColumnInternalProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
9
37
|
export default KanbanColumn;
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
id: string;
|
|
4
|
-
children: ReactNode;
|
|
5
|
-
}
|
|
6
|
-
declare function KanbanItem({ id, children }: KanbanItemProps): import("react/jsx-runtime").JSX.Element;
|
|
1
|
+
import { KanbanItemProps } from './kanbanBoard.types';
|
|
2
|
+
declare function KanbanItem({ id, dragHandle, disabled, preventAsDropTarget, children }: KanbanItemProps): import("react/jsx-runtime").JSX.Element;
|
|
7
3
|
export default KanbanItem;
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { CSSProperties, ReactNode } from 'react';
|
|
2
|
-
import { SlotClassNames, SlotStyles } from '../types/slots.types';
|
|
2
|
+
import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
|
|
3
3
|
export interface KanbanColumnDef {
|
|
4
4
|
id: string;
|
|
5
5
|
title: string;
|
|
6
6
|
itemIds: string[];
|
|
7
|
+
/**
|
|
8
|
+
* Optional work-in-progress limit. When set, the column header shows a `count/maxItems`
|
|
9
|
+
* badge, and the column gets a warning style once `itemIds.length` exceeds it. This is
|
|
10
|
+
* display-only — `KanbanBoard` doesn't block drops on its own. To enforce a hard limit,
|
|
11
|
+
* check it in `onChange` and skip updating your state when a drop would push a column over.
|
|
12
|
+
*/
|
|
13
|
+
maxItems?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Freezes the column: cards inside it can't be picked up, and nothing can be dropped into it.
|
|
16
|
+
* Use for a workflow rule like "cards in Done can't be moved back out."
|
|
17
|
+
*/
|
|
18
|
+
disabled?: boolean;
|
|
7
19
|
}
|
|
8
20
|
export type KanbanBoardSlots = 'dragOverlay';
|
|
9
|
-
export interface KanbanBoardProps<T = unknown> {
|
|
21
|
+
export interface KanbanBoardProps<T = unknown> extends HtmlProps {
|
|
10
22
|
/** Ordered list of column definitions. Each column owns its itemIds. */
|
|
11
23
|
columns: KanbanColumnDef[];
|
|
12
24
|
/** Flat map of all items keyed by their id. */
|
|
@@ -18,18 +30,72 @@ export interface KanbanBoardProps<T = unknown> {
|
|
|
18
30
|
* Receives the full updated columns array — replace your state with this value.
|
|
19
31
|
*/
|
|
20
32
|
onChange: (columns: KanbanColumnDef[]) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Restrict drag initiation to elements marked `data-drag-handle` within a card,
|
|
35
|
+
* instead of the whole card. Use when `renderItem` includes interactive content
|
|
36
|
+
* (buttons, inputs) that would otherwise have its clicks swallowed by the drag listener.
|
|
37
|
+
*/
|
|
38
|
+
dragHandle?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Produce an accessible label for a card, announced to screen-reader users while dragging
|
|
41
|
+
* (e.g. "Picked up <label>."). Defaults to the card's id when omitted.
|
|
42
|
+
*/
|
|
43
|
+
getItemLabel?: (item: T, id: string) => string;
|
|
44
|
+
/**
|
|
45
|
+
* Returns whether a specific card can't be picked up. Defaults to every card being draggable.
|
|
46
|
+
* Independent of a column's own `disabled` — a card can be pinned even in an otherwise
|
|
47
|
+
* unrestricted column.
|
|
48
|
+
*/
|
|
49
|
+
getItemDisabled?: (item: T, id: string) => boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Custom content shown inside a column when it has no cards, instead of leaving it blank.
|
|
52
|
+
* Receives the column definition.
|
|
53
|
+
*/
|
|
54
|
+
renderEmptyColumn?: (column: KanbanColumnDef) => ReactNode;
|
|
55
|
+
/**
|
|
56
|
+
* Custom actions rendered in a column's header, next to its title (e.g. an "add card"
|
|
57
|
+
* button, a column menu). Receives the column definition.
|
|
58
|
+
*/
|
|
59
|
+
renderColumnActions?: (column: KanbanColumnDef) => ReactNode;
|
|
60
|
+
/**
|
|
61
|
+
* Allow whole columns to be reordered by dragging their header, alongside the existing
|
|
62
|
+
* card dragging. Defaults to `false`.
|
|
63
|
+
*/
|
|
64
|
+
reorderableColumns?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Shrink a column to a narrow strip once it has no cards, instead of keeping it at the same
|
|
67
|
+
* width as every other column. Defaults to `true`. Set `false` to keep every column the same
|
|
68
|
+
* width regardless of card count. The collapsed width itself is set via the
|
|
69
|
+
* `--kanban-empty-column-width` CSS variable (falls back to `140px`) — override it theme-wide
|
|
70
|
+
* via `ThemeProvider`'s `variables`, or per board via `style`.
|
|
71
|
+
*/
|
|
72
|
+
collapseEmptyColumns?: boolean;
|
|
21
73
|
className?: string;
|
|
22
74
|
style?: CSSProperties;
|
|
23
75
|
classNames?: SlotClassNames<KanbanBoardSlots>;
|
|
24
76
|
styles?: SlotStyles<KanbanBoardSlots>;
|
|
25
77
|
}
|
|
26
|
-
export type KanbanColumnSlots = 'header' | 'items';
|
|
27
|
-
export interface KanbanColumnProps {
|
|
78
|
+
export type KanbanColumnSlots = 'header' | 'items' | 'count' | 'placeholder' | 'actions';
|
|
79
|
+
export interface KanbanColumnProps extends HtmlProps {
|
|
28
80
|
id: string;
|
|
29
81
|
title: string;
|
|
30
|
-
isOver?: boolean;
|
|
31
82
|
children?: ReactNode;
|
|
32
83
|
className?: string;
|
|
84
|
+
style?: CSSProperties;
|
|
33
85
|
classNames?: SlotClassNames<KanbanColumnSlots>;
|
|
34
86
|
styles?: SlotStyles<KanbanColumnSlots>;
|
|
35
87
|
}
|
|
88
|
+
export interface KanbanItemProps {
|
|
89
|
+
id: string;
|
|
90
|
+
dragHandle?: boolean;
|
|
91
|
+
/** Whether this card can't be picked up (column frozen, or the card itself is pinned). */
|
|
92
|
+
disabled?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Whether this card also can't be a drop target for other cards — i.e. other cards can't be
|
|
95
|
+
* reordered relative to it either. Only true when the *column* is frozen (nothing should be
|
|
96
|
+
* dropped into it at all); a card pinned individually via `getItemDisabled` stays droppable, so
|
|
97
|
+
* other cards can still land before/after it normally.
|
|
98
|
+
*/
|
|
99
|
+
preventAsDropTarget?: boolean;
|
|
100
|
+
children?: ReactNode;
|
|
101
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Announcements, ClientRect, CollisionDetection, KeyboardCoordinateGetter } from '@dnd-kit/core';
|
|
2
|
+
import { KanbanColumnDef } from './kanbanBoard.types';
|
|
3
|
+
export declare function columnsEqual(a: KanbanColumnDef[], b: KanbanColumnDef[]): boolean;
|
|
4
|
+
export declare function findColumnForItem(columns: KanbanColumnDef[], itemId: string): number;
|
|
5
|
+
/** Whether a drag's `active`/`over` participant is a whole column (vs. a card) — see `reorderableColumns`. */
|
|
6
|
+
export declare function isColumnDragData(data: unknown): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Reorders whole columns: moves `activeColumnId` to `overColumnId`'s position. Returns the same
|
|
9
|
+
* `columns` reference, unchanged, when either id doesn't resolve to a column or they're the same
|
|
10
|
+
* — callers can use that to skip a state update.
|
|
11
|
+
*/
|
|
12
|
+
export declare function moveColumn(columns: KanbanColumnDef[], activeColumnId: string, overColumnId: string): KanbanColumnDef[];
|
|
13
|
+
/**
|
|
14
|
+
* Collision detection for the board's shared `DndContext`, which has to pick between two very
|
|
15
|
+
* differently-scaled sets of droppables at once: individual cards / a column's own card list
|
|
16
|
+
* (small, numerous) and whole columns (large, few). Plain `closestCorners` weighs every
|
|
17
|
+
* registered droppable together regardless of what's actually being dragged, so while dragging a
|
|
18
|
+
* column it competes against every card and card-list droppable it happens to fly over too — the
|
|
19
|
+
* column-level target only wins when the pointer lands somewhere that geometrically favors it,
|
|
20
|
+
* which reads as needing to "wiggle it around" before the right drop target lights up. This
|
|
21
|
+
* restricts the candidates to only the same kind as whatever is being dragged: a column drag only
|
|
22
|
+
* considers other columns, a card drag only considers cards and card lists.
|
|
23
|
+
*/
|
|
24
|
+
export declare const kanbanCollisionDetection: CollisionDetection;
|
|
25
|
+
/** Extracts the column id from a column droppable's `column-<id>` identifier, or `null` for a card id. */
|
|
26
|
+
export declare function resolveColumnDropId(overId: string): string | null;
|
|
27
|
+
export interface DragGeometry {
|
|
28
|
+
/** The dragged card's current rect (translated by the drag delta), if measured. */
|
|
29
|
+
activeRect: ClientRect | null;
|
|
30
|
+
/** The rect of whatever `overId` refers to — a card, or a column's own droppable. */
|
|
31
|
+
overRect: ClientRect;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Computes the columns that result from dragging `activeItemId` over `overId` (a card id, or a
|
|
35
|
+
* `column-<id>` droppable id for empty column space). Returns the same `columns` reference,
|
|
36
|
+
* unchanged, when the drag doesn't resolve to a valid source/destination — callers can use that
|
|
37
|
+
* to skip a state update.
|
|
38
|
+
*
|
|
39
|
+
* `geometry` resolves cross-column drops that land on a card or gap with more than one possible
|
|
40
|
+
* position (see the two branches below for what each one decides).
|
|
41
|
+
*/
|
|
42
|
+
export declare function moveItem(columns: KanbanColumnDef[], activeItemId: string, overId: string, geometry: DragGeometry): KanbanColumnDef[];
|
|
43
|
+
/**
|
|
44
|
+
* Builds screen-reader drag announcements against the columns layout as currently understood
|
|
45
|
+
* (the live shadow layout while a drag is in progress, or the settled `columns` prop otherwise).
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildAnnouncements<T>(columns: KanbanColumnDef[], items: Record<string, T>, getItemLabel: (item: T, id: string) => string): Announcements;
|
|
48
|
+
/**
|
|
49
|
+
* Custom keyboard coordinate getter for the board's multi-column, multi-`SortableContext`
|
|
50
|
+
* layout. dnd-kit's default `sortableKeyboardCoordinates` picks the geometrically closest
|
|
51
|
+
* droppable across the *entire* board — cards and column containers alike — which is unreliable
|
|
52
|
+
* once cards live inside independent per-column `SortableContext`s: up/down and left/right end up
|
|
53
|
+
* interchangeable depending on incidental rect positions. This instead restricts up/down to
|
|
54
|
+
* reordering within the active card's own column, and left/right to moving into the adjacent
|
|
55
|
+
* column (landing on the closest card there by vertical position, or the column itself when
|
|
56
|
+
* empty). Requires each `KanbanColumn`'s `SortableContext` to be given an explicit `id` matching
|
|
57
|
+
* its column id, so a card's `sortable.containerId` can be correlated with its `column-<id>`
|
|
58
|
+
* droppable.
|
|
59
|
+
*/
|
|
60
|
+
export declare const kanbanKeyboardCoordinateGetter: KeyboardCoordinateGetter;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
2
|
+
import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
|
|
3
|
+
import { ButtonStyleType, ButtonSize } from '../button/button.types';
|
|
4
|
+
import { Align } from '../floatingMenu/floatingMenu.types';
|
|
5
|
+
import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';
|
|
6
|
+
export interface SplitButtonAction {
|
|
7
|
+
id: string;
|
|
8
|
+
label: ReactNode;
|
|
9
|
+
onClick: () => void;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
icon?: IconDefinition;
|
|
12
|
+
}
|
|
13
|
+
export type SplitButtonSlots = 'root' | 'primaryButton' | 'triggerButton' | 'menu' | 'menuItem';
|
|
14
|
+
export interface SplitButtonProps extends HtmlProps {
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => Promise<void> | void;
|
|
17
|
+
actions: SplitButtonAction[];
|
|
18
|
+
styleType?: ButtonStyleType;
|
|
19
|
+
size?: ButtonSize;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
isLoading?: boolean;
|
|
22
|
+
/** Alignment of the actions menu relative to the trigger button (default `Align.Right`) */
|
|
23
|
+
align?: Align;
|
|
24
|
+
className?: string;
|
|
25
|
+
style?: CSSProperties;
|
|
26
|
+
classNames?: SlotClassNames<SplitButtonSlots>;
|
|
27
|
+
styles?: SlotStyles<SplitButtonSlots>;
|
|
28
|
+
}
|
|
@@ -56,6 +56,7 @@ export interface ThemeVariables {
|
|
|
56
56
|
'--error-border-color'?: string;
|
|
57
57
|
'--input-label-resting-color'?: string;
|
|
58
58
|
'--input-label-floating-color'?: string;
|
|
59
|
+
'--search-input-color'?: string;
|
|
59
60
|
'--default-border-radius'?: string;
|
|
60
61
|
'--padding-sides'?: string;
|
|
61
62
|
'--margin-top'?: string;
|
|
@@ -106,6 +107,8 @@ export interface ThemeVariables {
|
|
|
106
107
|
'--stepper-gap'?: string;
|
|
107
108
|
/** Text/check colour on a filled marker. Falls back to --text-on-primary. */
|
|
108
109
|
'--stepper-text-on-marker'?: string;
|
|
110
|
+
/** Width a column collapses to once it has no cards (see `collapseEmptyColumns`). Falls back to 140px. */
|
|
111
|
+
'--kanban-empty-column-width'?: string;
|
|
109
112
|
[key: string]: string | undefined;
|
|
110
113
|
}
|
|
111
114
|
export interface Theme {
|
|
@@ -19,9 +19,13 @@ interface UseRowDragArgs {
|
|
|
19
19
|
/** The drag-related props the parent spreads onto a given row. */
|
|
20
20
|
export interface RowDragProps {
|
|
21
21
|
reorderable: boolean;
|
|
22
|
+
/** Native mouse DnD is active for the list (independent of which row is armed). */
|
|
23
|
+
mouseDragEnabled: boolean;
|
|
22
24
|
draggable: boolean | undefined;
|
|
23
25
|
isDragging: boolean;
|
|
24
26
|
onMouseDown: ((e: React.MouseEvent<HTMLDivElement>) => void) | undefined;
|
|
27
|
+
onMouseOver: ((e: React.MouseEvent<HTMLDivElement>) => void) | undefined;
|
|
28
|
+
onMouseLeave: ((e: React.MouseEvent<HTMLDivElement>) => void) | undefined;
|
|
25
29
|
onDragStart: ((e: React.DragEvent<HTMLDivElement>) => void) | undefined;
|
|
26
30
|
onDragOver: ((e: React.DragEvent<HTMLDivElement>) => void) | undefined;
|
|
27
31
|
onDragEnd: (() => void) | undefined;
|
|
@@ -10,6 +10,8 @@ interface UseVirtualWindowArgs<T> {
|
|
|
10
10
|
overscan: number;
|
|
11
11
|
onLoadMore?: () => Promise<void>;
|
|
12
12
|
loadMoreThreshold: number;
|
|
13
|
+
/** Initial/filter load in progress — the body is replaced by a spinner. */
|
|
14
|
+
isLoading: boolean;
|
|
13
15
|
}
|
|
14
16
|
interface UseVirtualWindowResult {
|
|
15
17
|
rootRef: React.RefObject<HTMLDivElement | null>;
|
|
@@ -37,5 +39,5 @@ interface UseVirtualWindowResult {
|
|
|
37
39
|
* scroll handle. Mode-agnostic — knows nothing about columns, selection or
|
|
38
40
|
* dragging.
|
|
39
41
|
*/
|
|
40
|
-
export declare function useVirtualWindow<T>({ items, keys, getItemKey, estimatedRowHeight, rowGap, overscan, onLoadMore, loadMoreThreshold, }: UseVirtualWindowArgs<T>): UseVirtualWindowResult;
|
|
42
|
+
export declare function useVirtualWindow<T>({ items, keys, getItemKey, estimatedRowHeight, rowGap, overscan, onLoadMore, loadMoreThreshold, isLoading, }: UseVirtualWindowArgs<T>): UseVirtualWindowResult;
|
|
41
43
|
export {};
|
|
@@ -132,7 +132,11 @@ export interface VirtualListProps<T> extends HtmlProps {
|
|
|
132
132
|
/** Show the gear column-toggle button in the header. Default: true when columns present. */
|
|
133
133
|
showColumnToggle?: boolean;
|
|
134
134
|
/**
|
|
135
|
-
* Called when the user scrolls within `loadMoreThreshold` px of the bottom
|
|
135
|
+
* Called when the user scrolls within `loadMoreThreshold` px of the bottom, and
|
|
136
|
+
* repeatedly while the rows don't fill the viewport — a list too short to scroll
|
|
137
|
+
* would otherwise never ask for a second batch. Each row count is asked at most
|
|
138
|
+
* once, so the loop ends as soon as a call appends nothing; pass `undefined`
|
|
139
|
+
* once the source is exhausted to skip even that final probe.
|
|
136
140
|
* Must return a Promise that resolves once new items have been appended to `items`.
|
|
137
141
|
* A spinner is shown at the bottom while the Promise is pending.
|
|
138
142
|
*/
|
|
@@ -16,10 +16,17 @@ export interface VirtualRowProps {
|
|
|
16
16
|
ariaSetSize?: number;
|
|
17
17
|
rowRole: 'row' | 'listitem';
|
|
18
18
|
reorderable?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Native mouse DnD is active for the list. Distinct from `draggable`, which in
|
|
21
|
+
* `dragHandle` mode is only set on the row the pointer has armed.
|
|
22
|
+
*/
|
|
23
|
+
mouseDragEnabled?: boolean;
|
|
19
24
|
draggable?: boolean;
|
|
20
25
|
isDragging?: boolean;
|
|
21
26
|
reserveToggleGutter?: boolean;
|
|
22
27
|
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
|
28
|
+
onMouseOver?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
|
29
|
+
onMouseLeave?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
|
23
30
|
onDragStart?: (e: React.DragEvent<HTMLDivElement>) => void;
|
|
24
31
|
onDragOver?: (e: React.DragEvent<HTMLDivElement>) => void;
|
|
25
32
|
onDragEnd?: () => void;
|
|
@@ -38,5 +45,5 @@ export interface VirtualRowProps {
|
|
|
38
45
|
* grid (table mode), registering itself with the shared ResizeObserver and
|
|
39
46
|
* carrying all the drag / drop-indicator wiring the parent assigns it.
|
|
40
47
|
*/
|
|
41
|
-
declare function VirtualRow({ index, top, padding, columnTemplate, isSelected, showDivider, hover, onClick, observer, ariaRowIndex, ariaPosInSet, ariaSetSize, rowRole, reorderable, draggable, isDragging, reserveToggleGutter, onMouseDown, onDragStart, onDragOver, onDragEnd, onDrop, onTouchStart, dropPosition, dropIndentPx, dropIndicatorClassName, dropIndicatorStyle, rowClassName, rowStyle, children, }: VirtualRowProps): import("react/jsx-runtime").JSX.Element;
|
|
48
|
+
declare function VirtualRow({ index, top, padding, columnTemplate, isSelected, showDivider, hover, onClick, observer, ariaRowIndex, ariaPosInSet, ariaSetSize, rowRole, reorderable, mouseDragEnabled, draggable, isDragging, reserveToggleGutter, onMouseDown, onMouseOver, onMouseLeave, onDragStart, onDragOver, onDragEnd, onDrop, onTouchStart, dropPosition, dropIndentPx, dropIndicatorClassName, dropIndicatorStyle, rowClassName, rowStyle, children, }: VirtualRowProps): import("react/jsx-runtime").JSX.Element;
|
|
42
49
|
export default VirtualRow;
|
|
@@ -19,6 +19,8 @@ export { default as Breadcrumb } from './common/breadcrumb';
|
|
|
19
19
|
export * from './common/breadcrumb';
|
|
20
20
|
export { default as Button } from './common/button';
|
|
21
21
|
export * from './common/button';
|
|
22
|
+
export { default as ButtonGroup } from './common/buttonGroup';
|
|
23
|
+
export * from './common/buttonGroup';
|
|
22
24
|
export { default as Card } from './common/card';
|
|
23
25
|
export * from './common/card';
|
|
24
26
|
export { default as Carousel } from './common/carousel';
|
|
@@ -98,6 +100,8 @@ export { default as Skeleton } from './common/skeleton';
|
|
|
98
100
|
export * from './common/skeleton';
|
|
99
101
|
export { default as Slider } from './common/slider';
|
|
100
102
|
export * from './common/slider';
|
|
103
|
+
export { default as SplitButton } from './common/splitButton';
|
|
104
|
+
export * from './common/splitButton';
|
|
101
105
|
export { default as Stepper } from './common/stepper';
|
|
102
106
|
export * from './common/stepper';
|
|
103
107
|
export { default as Sticky } from './common/sticky';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# ButtonGroup
|
|
2
|
+
|
|
3
|
+
**When to use:** Joins a row (or column) of buttons into a single seamless control, merging their borders and rounding only the outer corners. Use for a segmented toggle ("Day / Week / Month"), an icon toolbar (bold/italic/underline), or any set of related actions that should read as one control rather than separate buttons. Purely a layout wrapper, no interaction logic of its own, so any button-like children work, not just `Button`.
|
|
4
|
+
|
|
5
|
+
**Import:** `import { ButtonGroup, ButtonGroupOrientation } from '@ahrowe/ui'`
|
|
6
|
+
|
|
7
|
+
**Orientation:** `ButtonGroupOrientation.Horizontal` (default) | `ButtonGroupOrientation.Vertical`
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { ButtonGroup, ButtonGroupOrientation, Button, ButtonStyleType } from '@ahrowe/ui';
|
|
11
|
+
|
|
12
|
+
// Segmented toggle
|
|
13
|
+
<ButtonGroup>
|
|
14
|
+
<Button styleType={ButtonStyleType.Default}>Day</Button>
|
|
15
|
+
<Button styleType={ButtonStyleType.Default}>Week</Button>
|
|
16
|
+
<Button styleType={ButtonStyleType.Default}>Month</Button>
|
|
17
|
+
</ButtonGroup>
|
|
18
|
+
|
|
19
|
+
// Icon toolbar
|
|
20
|
+
<ButtonGroup>
|
|
21
|
+
<Button styleType={ButtonStyleType.Default} icon={faBold} aria-label="Bold" />
|
|
22
|
+
<Button styleType={ButtonStyleType.Default} icon={faItalic} aria-label="Italic" />
|
|
23
|
+
<Button styleType={ButtonStyleType.Default} icon={faUnderline} aria-label="Underline" />
|
|
24
|
+
</ButtonGroup>
|
|
25
|
+
|
|
26
|
+
// Vertical
|
|
27
|
+
<ButtonGroup orientation={ButtonGroupOrientation.Vertical}>
|
|
28
|
+
<Button styleType={ButtonStyleType.Default}>Bold</Button>
|
|
29
|
+
<Button styleType={ButtonStyleType.Default}>Italic</Button>
|
|
30
|
+
<Button styleType={ButtonStyleType.Default}>Underline</Button>
|
|
31
|
+
</ButtonGroup>
|
|
32
|
+
|
|
33
|
+
// With a separator — filled styles (Primary/Delete) have no border of their own to merge,
|
|
34
|
+
// so a vertical Divider gives adjacent buttons a visible seam instead of blending together
|
|
35
|
+
import { Divider, DividerOrientation } from '@ahrowe/ui';
|
|
36
|
+
|
|
37
|
+
<ButtonGroup>
|
|
38
|
+
<Button styleType={ButtonStyleType.Primary}>Save</Button>
|
|
39
|
+
<Divider orientation={DividerOrientation.Vertical} />
|
|
40
|
+
<Button styleType={ButtonStyleType.Primary}>Save & new</Button>
|
|
41
|
+
</ButtonGroup>
|
|
42
|
+
|
|
43
|
+
// Nested groups — a toolbar made of two self-contained sub-groups, spaced apart rather
|
|
44
|
+
// than merged into one continuous strip
|
|
45
|
+
<ButtonGroup>
|
|
46
|
+
<ButtonGroup>
|
|
47
|
+
<Button styleType={ButtonStyleType.Default} icon={faBold} aria-label="Bold" />
|
|
48
|
+
<Button styleType={ButtonStyleType.Default} icon={faItalic} aria-label="Italic" />
|
|
49
|
+
<Button styleType={ButtonStyleType.Default} icon={faUnderline} aria-label="Underline" />
|
|
50
|
+
</ButtonGroup>
|
|
51
|
+
<ButtonGroup>
|
|
52
|
+
<Button styleType={ButtonStyleType.Default} icon={faAlignLeft} aria-label="Align left" />
|
|
53
|
+
<Button styleType={ButtonStyleType.Default} icon={faAlignCenter} aria-label="Align center" />
|
|
54
|
+
<Button styleType={ButtonStyleType.Default} icon={faAlignRight} aria-label="Align right" />
|
|
55
|
+
</ButtonGroup>
|
|
56
|
+
</ButtonGroup>
|
|
57
|
+
|
|
58
|
+
// With an Input — a search bar. Input's visible border lives on its own container/fieldset
|
|
59
|
+
// elements, not its root, so (like SplitButton) the side touching the button needs a manual
|
|
60
|
+
// corner override via Input's own slots rather than ButtonGroup's automatic ones
|
|
61
|
+
import { Input } from '@ahrowe/ui';
|
|
62
|
+
|
|
63
|
+
const groupedInputStyle = { borderTopRightRadius: 0, borderBottomRightRadius: 0 };
|
|
64
|
+
|
|
65
|
+
<ButtonGroup>
|
|
66
|
+
<Input
|
|
67
|
+
placeholder="Search…"
|
|
68
|
+
useMatLabelStyle={false}
|
|
69
|
+
value={search}
|
|
70
|
+
onChange={setSearch}
|
|
71
|
+
styles={{ container: groupedInputStyle, fieldset: groupedInputStyle }}
|
|
72
|
+
/>
|
|
73
|
+
<Button styleType={ButtonStyleType.Primary} icon={faMagnifyingGlass} aria-label="Search" />
|
|
74
|
+
</ButtonGroup>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Key props:**
|
|
78
|
+
|
|
79
|
+
| Prop | Type | Description |
|
|
80
|
+
|------|------|-------------|
|
|
81
|
+
| `children` | `ReactNode` | The buttons (or button-like elements) to join, in order |
|
|
82
|
+
| `orientation` | `ButtonGroupOrientation` | Layout direction (default `Horizontal`) |
|
|
83
|
+
|
|
84
|
+
**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.
|
|
85
|
+
|
|
86
|
+
**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.
|
|
87
|
+
|
|
88
|
+
**Note:** the corner-flattening rules key off DOM position (`:first-child`/`:last-child`), so they work for direct `Button` children out of the box. A child that renders as a wrapper around its own button (e.g. a component that portals its real content elsewhere) won't get the flattening automatically. `SplitButton` is exactly that case (its second child is a `FloatingMenu`-wrapped button), which is why it sets its own corner overrides directly rather than relying on `ButtonGroup`'s automatic ones. See [SplitButton.md](SplitButton.md).
|
|
89
|
+
|
|
90
|
+
**Slots:** none — `ButtonGroup` has no named inner elements, only `className`/`style` on the root.
|
package/docs/CLAUDE.md
CHANGED
|
@@ -103,6 +103,7 @@ Slot keys per component are documented in each component's doc file below.
|
|
|
103
103
|
@BodyEnd.md
|
|
104
104
|
@Breadcrumb.md
|
|
105
105
|
@Button.md
|
|
106
|
+
@ButtonGroup.md
|
|
106
107
|
@Card.md
|
|
107
108
|
@Carousel.md
|
|
108
109
|
@Checkbox.md
|
|
@@ -145,6 +146,7 @@ Slot keys per component are documented in each component's doc file below.
|
|
|
145
146
|
@SectionHeader.md
|
|
146
147
|
@Skeleton.md
|
|
147
148
|
@Slider.md
|
|
149
|
+
@SplitButton.md
|
|
148
150
|
@Stepper.md
|
|
149
151
|
@Sticky.md
|
|
150
152
|
@Switch.md
|
package/docs/Divider.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# Divider
|
|
2
2
|
|
|
3
|
-
**When to use:** A
|
|
3
|
+
**When to use:** A rule that separates sections of content — horizontal by default, filling the width of its parent. Optionally label it with text (e.g. "OR", a date, a section name); the line splits around the text, leaving a gap for it. Set `orientation` to `Vertical` for a vertical line filling the height of its parent instead, e.g. as a separator between two buttons in a `ButtonGroup`.
|
|
4
4
|
|
|
5
|
-
**Import:** `import { Divider, DividerTextPosition } from '@ahrowe/ui'`
|
|
5
|
+
**Import:** `import { Divider, DividerTextPosition, DividerOrientation } from '@ahrowe/ui'`
|
|
6
6
|
|
|
7
7
|
**Text position:** `DividerTextPosition.Left` | `DividerTextPosition.Center` (default) | `DividerTextPosition.Right`
|
|
8
8
|
|
|
9
|
+
**Orientation:** `DividerOrientation.Horizontal` (default) | `DividerOrientation.Vertical`
|
|
10
|
+
|
|
9
11
|
```tsx
|
|
10
|
-
import { Divider, DividerTextPosition } from '@ahrowe/ui';
|
|
12
|
+
import { Divider, DividerTextPosition, DividerOrientation } from '@ahrowe/ui';
|
|
11
13
|
|
|
12
14
|
// Plain line
|
|
13
15
|
<Divider />
|
|
@@ -20,6 +22,13 @@ import { Divider, DividerTextPosition } from '@ahrowe/ui';
|
|
|
20
22
|
|
|
21
23
|
// Text near the right edge
|
|
22
24
|
<Divider text="March 2026" textPosition={DividerTextPosition.Right} />
|
|
25
|
+
|
|
26
|
+
// Vertical — fills the height of its (flex/grid) parent, e.g. between two buttons
|
|
27
|
+
<ButtonGroup>
|
|
28
|
+
<Button>Save</Button>
|
|
29
|
+
<Divider orientation={DividerOrientation.Vertical} />
|
|
30
|
+
<Button>Save & new</Button>
|
|
31
|
+
</ButtonGroup>
|
|
23
32
|
```
|
|
24
33
|
|
|
25
34
|
**Key props:**
|
|
@@ -28,6 +37,9 @@ import { Divider, DividerTextPosition } from '@ahrowe/ui';
|
|
|
28
37
|
|------|------|-------------|
|
|
29
38
|
| `text` | `ReactNode` | Text (or any node) rendered on the line. Omit for a plain full-width line |
|
|
30
39
|
| `textPosition` | `DividerTextPosition` | Where `text` sits along the line (default `Center`) |
|
|
40
|
+
| `orientation` | `DividerOrientation` | `Horizontal` (default, fills width) or `Vertical` (fills height) |
|
|
31
41
|
| `className` / `style` | | Root element |
|
|
32
42
|
|
|
43
|
+
**Vertical orientation:** stretches to fill the height of a flex or grid parent (`align-self: stretch`), the same way `Button`s in a `ButtonGroup` do. Outside a flex/grid context, give it an explicit height via `style`, since a vertical line has no natural height of its own the way a horizontal one has a natural width (`100%` of its block-level parent).
|
|
44
|
+
|
|
33
45
|
**Slots:** `root` `line` (the line segment(s) either side of `text`) `content` (the `text` wrapper)
|
package/docs/FloatingMenu.md
CHANGED
|
@@ -29,7 +29,9 @@ import { FloatingMenu, Align } from '@ahrowe/ui';
|
|
|
29
29
|
<ActionIcon icon={faEllipsis} />
|
|
30
30
|
</FloatingMenu>
|
|
31
31
|
|
|
32
|
-
// Keep open
|
|
32
|
+
// Keep open through several interactions with content — a column-visibility toggle where
|
|
33
|
+
// checking one box shouldn't close the whole menu — or when re-clicking the trigger itself
|
|
34
|
+
// (e.g. DatePicker re-clicking its own input) shouldn't close it either
|
|
33
35
|
<FloatingMenu dontCloseOnChildClick content={<Form />}>
|
|
34
36
|
<button>Open form</button>
|
|
35
37
|
</FloatingMenu>
|
|
@@ -46,8 +48,10 @@ import { FloatingMenu, Align } from '@ahrowe/ui';
|
|
|
46
48
|
| `align` | `Align` | Popover alignment relative to trigger |
|
|
47
49
|
| `isOpen` | `boolean` | Controlled open state |
|
|
48
50
|
| `onOpenChange` | `(isOpen: boolean) => void` | Open state change callback |
|
|
49
|
-
| `dontCloseOnChildClick` | `boolean` |
|
|
51
|
+
| `dontCloseOnChildClick` | `boolean` | Keep the menu open when its own content is clicked, and when the trigger itself is re-clicked while already open (default `false`) |
|
|
50
52
|
|
|
51
53
|
**Slots:** `root` `trigger` `menu` `menuContainer`
|
|
52
54
|
|
|
53
|
-
**
|
|
55
|
+
**Closing behaviour:** by default, clicking anywhere in `content` closes the menu — including inside a nested overlay that renders through its own portal (e.g. a `Dropdown` or another `FloatingMenu` used inside `content`), even though that overlay's DOM lives outside `content`'s own subtree. Re-clicking the trigger while open is a clean toggle: it closes the menu (unless `dontCloseOnChildClick` is set, in which case it's a no-op — the trigger owns its own open/close entirely, so it never fights with an outside-click check). Set `dontCloseOnChildClick` when `content` needs several interactions before the user is done (a multi-checkbox toggle, a color picker's slider, a calendar) — the consumer is then responsible for closing explicitly, e.g. calling `onOpenChange(false)` from the handler that reacts to a final selection.
|
|
56
|
+
|
|
57
|
+
**Keyboard:** the menu is portaled to the end of the DOM, so Tab can't reach it in visual order on its own. When `content` has real focusable elements, Escape closes the menu and returns focus to the trigger; Tab past the last focusable element (or Shift+Tab past the first) closes the menu and continues focus as if it sat right after the trigger. This doesn't include auto-focusing the first element on open — content ranges from menus to live controls (e.g. `ColorPicker`'s hue slider), where grabbing focus on open would let a stray arrow-key press change a value the user never touched. A consumer that wants that (like `SplitButton` focusing its first enabled action) implements it itself.
|
package/docs/Input.md
CHANGED
|
@@ -17,14 +17,13 @@ const emailValidator = new FormValidator('', [Validators.required(), Validators.
|
|
|
17
17
|
// Password
|
|
18
18
|
<Input label="Password" type={InputType.Password} value={pw} onChange={setPw} />
|
|
19
19
|
|
|
20
|
-
// Search
|
|
20
|
+
// Search — clear button appears automatically once there's a value, and clears itself;
|
|
21
|
+
// onClearClicked is optional, only needed to react to the clear beyond the value itself
|
|
21
22
|
<Input
|
|
22
23
|
type={InputType.Search}
|
|
23
24
|
label="Search"
|
|
24
25
|
value={query}
|
|
25
26
|
onChange={setQuery}
|
|
26
|
-
showCancel
|
|
27
|
-
onClearClicked={() => setQuery('')}
|
|
28
27
|
/>
|
|
29
28
|
|
|
30
29
|
// Multiline (textarea)
|
|
@@ -38,10 +37,34 @@ const emailValidator = new FormValidator('', [Validators.required(), Validators.
|
|
|
38
37
|
|
|
39
38
|
// Row layout
|
|
40
39
|
<Input label="Name" isRow value={name} onChange={setName} />
|
|
40
|
+
|
|
41
|
+
// Custom icon on the left instead of the default right — the field's padding, the material
|
|
42
|
+
// label (resting and floated), and the native placeholder all shift out of its way automatically
|
|
43
|
+
<Input
|
|
44
|
+
label="Search"
|
|
45
|
+
icon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
|
46
|
+
iconPosition={InputIconPosition.Left}
|
|
47
|
+
value={query}
|
|
48
|
+
onChange={setQuery}
|
|
49
|
+
/>
|
|
50
|
+
|
|
51
|
+
// A decorative type icon (Email/Phone/Edit) on the left, no custom icon needed
|
|
52
|
+
<Input label="Email" type={InputType.Email} iconPosition={InputIconPosition.Left} />
|
|
53
|
+
|
|
54
|
+
// A custom icon replaces Email's built-in envelope outright — there's nothing functional to lose
|
|
55
|
+
<Input label="Email" type={InputType.Email} icon={<FontAwesomeIcon icon={faAt} />} />
|
|
41
56
|
```
|
|
42
57
|
|
|
43
58
|
**InputType enum:** `Edit` | `Phone` | `Email` | `Iban` | `Date` | `HasNoIcon` | `Search` | `Password`
|
|
44
59
|
|
|
60
|
+
**InputIconPosition enum:** `Left` | `Right` — which side the input's icon renders on, whichever icon is currently showing (built-in type icon or custom `icon`). Defaults to `Left` for `Email`/`Phone`/`Search` once `useMatLabelStyle` is off (their historical placement), `Right` otherwise; pass it explicitly to move any type's icon to either side regardless of `useMatLabelStyle`.
|
|
61
|
+
|
|
62
|
+
**Custom icon vs. built-in type icon:** passing `icon` alongside a decorative type (`Edit`, `Phone`, `Email`) replaces that type's built-in icon outright, since there's nothing functional attached to it to lose. `Search` and `Password` ignore `icon` — their icon is functional (clear button, show/hide toggle), not decorative, so it always renders regardless of whether `icon` is set.
|
|
63
|
+
|
|
64
|
+
**Search clear icon:** `InputType.Search` shows a magnifying glass that animates into a clear (×) button once the field has a value, rather than the browser's own native search-cancel decoration, which is suppressed so it doesn't clash with the theme.
|
|
65
|
+
|
|
66
|
+
**Mobile keyboard layout:** `Email`, `Phone`, and `Search` each set the matching `inputMode` (`email` / `tel` / `search`), so mobile browsers show the right on-screen keyboard — an `@`-and-`.com` layout for email, a numeric dial pad for phone, a "Go"/search action key for search. `Email`'s native `type` is deliberately left as `text` rather than `email`, to avoid the browser's own "please match the requested format" validation UI fighting the `formValidator`-driven error tooltip this component already shows — `inputMode="email"` alone is enough to get the correct keyboard. Pass `inputMode` directly to override the computed default for any other case.
|
|
67
|
+
|
|
45
68
|
**Key props:**
|
|
46
69
|
|
|
47
70
|
| Prop | Type | Description |
|
|
@@ -58,12 +81,13 @@ const emailValidator = new FormValidator('', [Validators.required(), Validators.
|
|
|
58
81
|
| `rows` | `number` | Textarea row count |
|
|
59
82
|
| `autoResize` | `boolean` | Textarea grows with content |
|
|
60
83
|
| `suffix` | `string \| ReactNode` | Right-side suffix |
|
|
61
|
-
| `icon` | `ReactElement` | Custom icon
|
|
84
|
+
| `icon` | `ReactElement` | Custom icon, positioned absolute on the side set by `iconPosition`; clickable via `onIconClick`. Replaces the built-in icon for `Edit`/`Phone`/`Email`; ignored for `Search`/`Password` (see below) |
|
|
85
|
+
| `iconPosition` | `InputIconPosition` | Side the input's icon renders on — built-in or custom, whichever is showing (see enum above for the default). On the left, the material label (resting and floated) and the fieldset notch shift right to clear it too |
|
|
62
86
|
| `onIconClick` | `() => void` | Click handler for the custom `icon` |
|
|
63
87
|
| `upperRightLabel` | `string \| ReactNode` | Small label above right |
|
|
64
|
-
| `showCancel` | `boolean` | Show clear icon when focused |
|
|
88
|
+
| `showCancel` | `boolean` | Show clear icon when focused (ignored for `InputType.Search`, which always shows it once there's a value — see below) |
|
|
65
89
|
| `alwaysShowCancel` | `boolean` | Always show clear icon |
|
|
66
|
-
| `onClearClicked` | `() => void` |
|
|
90
|
+
| `onClearClicked` | `() => void` | Called after the clear button clears the value. Optional — clicking clear always empties the field itself, whether or not this is set |
|
|
67
91
|
| `isRow` | `boolean` | Horizontal layout |
|
|
68
92
|
| `labelInBorder` | `boolean` | Label inside fieldset border |
|
|
69
93
|
| `alwaysFloatLabel` | `boolean` | Keep the material label floated in the border notch even when empty and unfocused, so an empty field reads as blank instead of showing the label as if it were a value. Only applies with `useMatLabelStyle` (default) |
|