@ahrowe/ui 0.48.0 → 0.50.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.
- package/CHANGELOG.md +25 -0
- package/README.md +3 -0
- package/dist/cjs-types/common/actionIcon/actionIcon.types.d.ts +1 -1
- package/dist/cjs-types/common/button/button.types.d.ts +6 -1
- package/dist/cjs-types/common/chatComposer/chatComposer.d.ts +4 -0
- package/dist/cjs-types/common/chatComposer/chatComposer.types.d.ts +40 -0
- package/dist/cjs-types/common/chatComposer/index.d.ts +2 -0
- package/dist/cjs-types/common/chatMessage/chatMessage.d.ts +4 -0
- package/dist/cjs-types/common/chatMessage/chatMessage.types.d.ts +60 -0
- package/dist/cjs-types/common/chatMessage/index.d.ts +2 -0
- package/dist/cjs-types/common/chatMessageList/chatMessageList.d.ts +4 -0
- package/dist/cjs-types/common/chatMessageList/chatMessageList.types.d.ts +27 -0
- package/dist/cjs-types/common/chatMessageList/index.d.ts +2 -0
- package/dist/cjs-types/common/configProvider/configProvider.types.d.ts +6 -0
- package/dist/cjs-types/common/hooks/useAutoGrow.d.ts +12 -0
- package/dist/cjs-types/common/hooks/useHotkeys.d.ts +11 -0
- package/dist/cjs-types/common/tabHeader/tabHeader.types.d.ts +5 -3
- package/dist/cjs-types/index.d.ts +6 -0
- package/dist/esm/common/button/button.mjs +1 -1
- package/dist/esm/common/button/button.mjs.map +1 -1
- package/dist/esm/common/button/button.module.mjs +1 -1
- package/dist/esm/common/button/button.module.mjs.map +1 -1
- package/dist/esm/common/button/button.types.mjs.map +1 -1
- package/dist/esm/common/chatComposer/chatComposer.mjs +2 -0
- package/dist/esm/common/chatComposer/chatComposer.mjs.map +1 -0
- package/dist/esm/common/chatComposer/chatComposer.module.mjs +2 -0
- package/dist/esm/common/chatComposer/chatComposer.module.mjs.map +1 -0
- package/dist/esm/common/chatMessage/chatMessage.mjs +2 -0
- package/dist/esm/common/chatMessage/chatMessage.mjs.map +1 -0
- package/dist/esm/common/chatMessage/chatMessage.module.mjs +2 -0
- package/dist/esm/common/chatMessage/chatMessage.module.mjs.map +1 -0
- package/dist/esm/common/chatMessage/chatMessage.types.mjs +2 -0
- package/dist/esm/common/chatMessage/chatMessage.types.mjs.map +1 -0
- package/dist/esm/common/chatMessageList/chatMessageList.mjs +2 -0
- package/dist/esm/common/chatMessageList/chatMessageList.mjs.map +1 -0
- package/dist/esm/common/chatMessageList/chatMessageList.module.mjs +2 -0
- package/dist/esm/common/chatMessageList/chatMessageList.module.mjs.map +1 -0
- package/dist/esm/common/commandPalette/commandPalette.mjs +1 -1
- package/dist/esm/common/commandPalette/commandPalette.mjs.map +1 -1
- package/dist/esm/common/dropdown/dropdown.mjs +1 -1
- package/dist/esm/common/hooks/useAutoGrow.mjs +2 -0
- package/dist/esm/common/hooks/useAutoGrow.mjs.map +1 -0
- package/dist/esm/common/hooks/useHotkeys.mjs +1 -1
- package/dist/esm/common/hooks/useHotkeys.mjs.map +1 -1
- package/dist/esm/common/infiniteBlock/useInfiniteScroll.mjs +1 -1
- package/dist/esm/common/infiniteBlock/useInfiniteScroll.mjs.map +1 -1
- package/dist/esm/common/input/input.mjs +1 -1
- package/dist/esm/common/input/input.mjs.map +1 -1
- package/dist/esm/common/menu/menu.mjs +1 -1
- package/dist/esm/common/menu/menu.mjs.map +1 -1
- package/dist/esm/common/tabHeader/tabHeader.mjs +1 -1
- package/dist/esm/common/tabHeader/tabHeader.mjs.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/common/actionIcon/actionIcon.types.d.ts +1 -1
- package/dist/types/common/button/button.types.d.ts +6 -1
- package/dist/types/common/chatComposer/chatComposer.d.ts +4 -0
- package/dist/types/common/chatComposer/chatComposer.types.d.ts +40 -0
- package/dist/types/common/chatComposer/index.d.ts +2 -0
- package/dist/types/common/chatMessage/chatMessage.d.ts +4 -0
- package/dist/types/common/chatMessage/chatMessage.types.d.ts +60 -0
- package/dist/types/common/chatMessage/index.d.ts +2 -0
- package/dist/types/common/chatMessageList/chatMessageList.d.ts +4 -0
- package/dist/types/common/chatMessageList/chatMessageList.types.d.ts +27 -0
- package/dist/types/common/chatMessageList/index.d.ts +2 -0
- package/dist/types/common/configProvider/configProvider.types.d.ts +6 -0
- package/dist/types/common/hooks/useAutoGrow.d.ts +12 -0
- package/dist/types/common/hooks/useHotkeys.d.ts +11 -0
- package/dist/types/common/tabHeader/tabHeader.types.d.ts +5 -3
- package/dist/types/index.d.ts +6 -0
- package/docs/ActionIcon.md +2 -2
- package/docs/Button.md +18 -2
- package/docs/CLAUDE.md +6 -3
- package/docs/ChatComposer.md +100 -0
- package/docs/ChatMessage.md +88 -0
- package/docs/ChatMessageList.md +113 -0
- package/docs/ConfigProvider.md +1 -0
- package/docs/Hooks.md +23 -4
- package/docs/Menu.md +1 -1
- package/docs/TabHeader.md +19 -8
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ export type ActionIconSlots = 'root' | 'icon';
|
|
|
6
6
|
* A Button with only an icon, sized by that icon, so every Button prop applies except the ones
|
|
7
7
|
* that describe a label or a size.
|
|
8
8
|
*/
|
|
9
|
-
export interface ActionIconProps extends Omit<ButtonProps, 'icon' | 'size' | 'children' | 'isLoading' | 'classNames' | 'styles'> {
|
|
9
|
+
export interface ActionIconProps extends Omit<ButtonProps, 'icon' | 'size' | 'children' | 'isLoading' | 'shortcut' | 'classNames' | 'styles'> {
|
|
10
10
|
icon: IconContent;
|
|
11
11
|
classNames?: SlotClassNames<ActionIconSlots>;
|
|
12
12
|
styles?: SlotStyles<ActionIconSlots>;
|
|
@@ -26,7 +26,7 @@ export declare enum ButtonType {
|
|
|
26
26
|
Reset = "reset",
|
|
27
27
|
Submit = "submit"
|
|
28
28
|
}
|
|
29
|
-
export type ButtonSlots = 'background' | 'content' | 'loadingContainer' | 'label';
|
|
29
|
+
export type ButtonSlots = 'background' | 'content' | 'loadingContainer' | 'label' | 'shortcut';
|
|
30
30
|
/**
|
|
31
31
|
* `DataProps` supplies the `data-*` index signature, which `ButtonHTMLAttributes` doesn't cover.
|
|
32
32
|
* `type`, `color` and `onClick` below deliberately narrow their inherited counterparts.
|
|
@@ -43,6 +43,11 @@ export interface ButtonProps extends DataProps, React.ButtonHTMLAttributes<HTMLB
|
|
|
43
43
|
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => Promise<void> | void;
|
|
44
44
|
children?: React.ReactNode;
|
|
45
45
|
icon?: IconContent;
|
|
46
|
+
/**
|
|
47
|
+
* A shortcut drawn after the label, in `useHotkeys` spelling (`'mod+s'`): `⌘S` on Apple
|
|
48
|
+
* platforms, `Ctrl+S` elsewhere. Drawn only; bind it with `useHotkeys`.
|
|
49
|
+
*/
|
|
50
|
+
shortcut?: string;
|
|
46
51
|
isLoading?: boolean;
|
|
47
52
|
disabled?: boolean;
|
|
48
53
|
title?: string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CSSProperties, ReactNode, Ref, TextareaHTMLAttributes } from 'react';
|
|
2
|
+
import { SlotClassNames, SlotStyles, DataProps } from '../types/slots.types.js';
|
|
3
|
+
export type ChatComposerSlots = 'input' | 'button';
|
|
4
|
+
/** Text ChatComposer renders of its own, for translating it. */
|
|
5
|
+
export interface ChatComposerLabels {
|
|
6
|
+
/** Shown in the empty field, and its accessible name. */
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
/** Send button, read out by screen readers. */
|
|
9
|
+
send?: string;
|
|
10
|
+
/** Stop button shown while `onStop` is set, read out by screen readers. */
|
|
11
|
+
stop?: string;
|
|
12
|
+
}
|
|
13
|
+
/** Native attributes go to the `<textarea>`; `className` and `style` to the root. */
|
|
14
|
+
export interface ChatComposerProps extends DataProps, Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'onChange' | 'value' | 'defaultValue' | 'placeholder'> {
|
|
15
|
+
/** Called with the trimmed text. Not called while the field is empty or only whitespace. */
|
|
16
|
+
onSend: (text: string) => void;
|
|
17
|
+
/** Controlled value. Sending clears it through `onChange('')`. */
|
|
18
|
+
value?: string;
|
|
19
|
+
defaultValue?: string;
|
|
20
|
+
onChange?: (value: string) => void;
|
|
21
|
+
/** Runs before the composer's own handling; `preventDefault()` on an Enter keeps it from sending. */
|
|
22
|
+
onKeyDown?: TextareaHTMLAttributes<HTMLTextAreaElement>['onKeyDown'];
|
|
23
|
+
/** Swaps the send button for a stop button while set, e.g. while an AI answer streams. Enter sends nothing meanwhile. */
|
|
24
|
+
onStop?: () => void;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
/** The height of the empty field, in lines. It grows from there to seven. Default 1. */
|
|
27
|
+
rows?: number;
|
|
28
|
+
/** Before the field: an attach or emoji button. Moves to a row below it once the text wraps. */
|
|
29
|
+
startContent?: ReactNode;
|
|
30
|
+
/** Between the field and the send button. Moves to a row below it, with the send button, once the text wraps. */
|
|
31
|
+
endContent?: ReactNode;
|
|
32
|
+
/** Ref to the inner `<textarea>`. */
|
|
33
|
+
ref?: Ref<HTMLTextAreaElement>;
|
|
34
|
+
/** Overrides the text ChatComposer renders of its own. Merged per key. */
|
|
35
|
+
labels?: ChatComposerLabels;
|
|
36
|
+
className?: string;
|
|
37
|
+
style?: CSSProperties;
|
|
38
|
+
classNames?: SlotClassNames<ChatComposerSlots>;
|
|
39
|
+
styles?: SlotStyles<ChatComposerSlots>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
2
|
+
import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types.js';
|
|
3
|
+
import { ColorValue } from '../themeProvider/color.types.js';
|
|
4
|
+
/** How much the message stands out. Independent of its colour. */
|
|
5
|
+
export declare enum ChatMessageStyleType {
|
|
6
|
+
/** Solid bubble, text in a contrasting colour. The default for `isOwn`. */
|
|
7
|
+
Filled = "filled",
|
|
8
|
+
/** A light tint of the colour. The default for everyone else. */
|
|
9
|
+
Light = "light",
|
|
10
|
+
/** No bubble, full width: an AI assistant's answer. */
|
|
11
|
+
Plain = "plain"
|
|
12
|
+
}
|
|
13
|
+
/** Delivery state of an own message: an icon next to `time`, and `Failed` a notice under the bubble. */
|
|
14
|
+
export declare enum ChatMessageStatus {
|
|
15
|
+
Sending = "sending",
|
|
16
|
+
Sent = "sent",
|
|
17
|
+
Delivered = "delivered",
|
|
18
|
+
Read = "read",
|
|
19
|
+
Failed = "failed"
|
|
20
|
+
}
|
|
21
|
+
export type ChatMessageSlots = 'avatar' | 'body' | 'author' | 'bubble' | 'meta' | 'actions';
|
|
22
|
+
/** Text ChatMessage renders of its own, for translating it. */
|
|
23
|
+
export interface ChatMessageLabels {
|
|
24
|
+
/** Read out for the status icon. */
|
|
25
|
+
sending?: string;
|
|
26
|
+
sent?: string;
|
|
27
|
+
delivered?: string;
|
|
28
|
+
read?: string;
|
|
29
|
+
/** Shown under the bubble. */
|
|
30
|
+
failed?: string;
|
|
31
|
+
/** Names an own message without `author` for screen readers. */
|
|
32
|
+
you?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface ChatMessageProps extends HtmlProps<HTMLDivElement> {
|
|
35
|
+
/** The message. A string keeps its line breaks; anything else (rendered Markdown, an image) renders as given. */
|
|
36
|
+
children?: ReactNode;
|
|
37
|
+
/** Sent by the current user: aligned to the end, `Filled` in `Primary` by default. */
|
|
38
|
+
isOwn?: boolean;
|
|
39
|
+
/** Follows a message from the same author: hides `author` from view, keeps the space of `avatar` and tightens the gap above. */
|
|
40
|
+
isContinued?: boolean;
|
|
41
|
+
/** Shown beside the bubble, typically an `Avatar`. */
|
|
42
|
+
avatar?: ReactNode;
|
|
43
|
+
/** Name above the bubble. Screen readers get it on every message of a run, not only the first. */
|
|
44
|
+
author?: ReactNode;
|
|
45
|
+
/** Shown in the bubble's corner. Format it yourself, e.g. with `Intl.DateTimeFormat`. */
|
|
46
|
+
time?: ReactNode;
|
|
47
|
+
status?: ChatMessageStatus;
|
|
48
|
+
/** Shown below the bubble, after the `Failed` notice: copy, retry, rate. */
|
|
49
|
+
actions?: ReactNode;
|
|
50
|
+
/** Default `Filled` when `isOwn`, otherwise `Light`. */
|
|
51
|
+
styleType?: ChatMessageStyleType;
|
|
52
|
+
/** A `ThemeColor` or any CSS colour. Default `Primary` when `isOwn`, otherwise `Neutral`. */
|
|
53
|
+
color?: ColorValue;
|
|
54
|
+
/** Overrides the text ChatMessage renders of its own. Merged per key. */
|
|
55
|
+
labels?: ChatMessageLabels;
|
|
56
|
+
className?: string;
|
|
57
|
+
style?: CSSProperties;
|
|
58
|
+
classNames?: SlotClassNames<ChatMessageSlots>;
|
|
59
|
+
styles?: SlotStyles<ChatMessageSlots>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CSSProperties, ReactNode, Ref } from 'react';
|
|
2
|
+
import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types.js';
|
|
3
|
+
export type ChatMessageListSlots = 'viewport' | 'content' | 'scrollButton';
|
|
4
|
+
/** Text ChatMessageList renders of its own, for translating it. */
|
|
5
|
+
export interface ChatMessageListLabels {
|
|
6
|
+
/** Accessible name of the message log. */
|
|
7
|
+
messages?: string;
|
|
8
|
+
/** Button that jumps back to the newest message, read out by screen readers. */
|
|
9
|
+
scrollToBottom?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ChatMessageListHandle {
|
|
12
|
+
/** Scrolls to the newest message and follows new ones again. Call it after the user sends one. */
|
|
13
|
+
scrollToBottom: () => void;
|
|
14
|
+
}
|
|
15
|
+
export interface ChatMessageListProps extends HtmlProps<HTMLDivElement> {
|
|
16
|
+
/** `ChatMessage`s, and anything between them: a `Divider` with a date, a typing indicator. Oldest first. */
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
/** Loads older messages when the user scrolls to the top. Prepend them; the view stays where it was. Leave it out once there are no more. */
|
|
19
|
+
onLoadMore?: () => Promise<void>;
|
|
20
|
+
ref?: Ref<ChatMessageListHandle>;
|
|
21
|
+
/** Overrides the text ChatMessageList renders of its own. Merged per key. */
|
|
22
|
+
labels?: ChatMessageListLabels;
|
|
23
|
+
className?: string;
|
|
24
|
+
style?: CSSProperties;
|
|
25
|
+
classNames?: SlotClassNames<ChatMessageListSlots>;
|
|
26
|
+
styles?: SlotStyles<ChatMessageListSlots>;
|
|
27
|
+
}
|
|
@@ -49,6 +49,9 @@ import { RoomViewerProps } from '../roomViewer/roomViewer.types.js';
|
|
|
49
49
|
import { PopoverProps } from '../popover/popover.types.js';
|
|
50
50
|
import { ModalProps } from '../modal/modal.types.js';
|
|
51
51
|
import { StepperProps } from '../stepper/stepper.types.js';
|
|
52
|
+
import { ChatMessageProps } from '../chatMessage/chatMessage.types.js';
|
|
53
|
+
import { ChatMessageListProps } from '../chatMessageList/chatMessageList.types.js';
|
|
54
|
+
import { ChatComposerProps } from '../chatComposer/chatComposer.types.js';
|
|
52
55
|
import { Presentation } from '../configProvider/presentation.types.js';
|
|
53
56
|
/**
|
|
54
57
|
* Global default props keyed by component name.
|
|
@@ -133,6 +136,9 @@ export interface ComponentDefaults {
|
|
|
133
136
|
CommandPalette?: Partial<CommandPaletteProps>;
|
|
134
137
|
WeatherIcon?: Partial<WeatherIconProps>;
|
|
135
138
|
Stepper?: Partial<StepperProps>;
|
|
139
|
+
ChatMessage?: Partial<ChatMessageProps>;
|
|
140
|
+
ChatMessageList?: Partial<ChatMessageListProps>;
|
|
141
|
+
ChatComposer?: Partial<ChatComposerProps>;
|
|
136
142
|
}
|
|
137
143
|
export interface ConfigProviderProps {
|
|
138
144
|
children: React.ReactNode;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
interface AutoGrowOptions {
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
/** Whether the text needs more than the field's `rows`, after each change of `value` or width. */
|
|
5
|
+
onGrow?: (isGrown: boolean) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Keeps a textarea as tall as its text, up to the `max-height` its stylesheet sets. It measures
|
|
9
|
+
* again when `value` changes, so the field must be controlled, and when its width changes.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useAutoGrow(ref: RefObject<HTMLElement | null>, value: unknown, { enabled, onGrow }?: AutoGrowOptions): void;
|
|
12
|
+
export {};
|
|
@@ -18,6 +18,11 @@ export interface UseHotkeysOptions {
|
|
|
18
18
|
* Default `true`.
|
|
19
19
|
*/
|
|
20
20
|
preventDefault?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Fire again for each repeat while the combo is held. Default `false`, so holding `mod+s` saves
|
|
23
|
+
* once. Turn it on for keys that step through something, such as arrows or zoom.
|
|
24
|
+
*/
|
|
25
|
+
repeat?: boolean;
|
|
21
26
|
}
|
|
22
27
|
/**
|
|
23
28
|
* A combo written for the reader: `'mod+shift+k'` reads `⇧⌘K` on an Apple platform and
|
|
@@ -30,6 +35,12 @@ export interface UseHotkeysOptions {
|
|
|
30
35
|
* Resolved from the platform at call time, so a server-rendered page renders the non-Apple form.
|
|
31
36
|
*/
|
|
32
37
|
export declare function formatHotkey(combo: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* A combo as `aria-keyshortcuts` spells it: `'mod+shift+k'` is `Control+Shift+K`, or
|
|
40
|
+
* `Meta+Shift+K` on an Apple platform. Resolved from the platform like `formatHotkey`, so the two
|
|
41
|
+
* always name the same keys.
|
|
42
|
+
*/
|
|
43
|
+
export declare function ariaKeyshortcuts(combo: string | undefined): string | undefined;
|
|
33
44
|
/**
|
|
34
45
|
* Keyboard shortcuts, for a whole app or for one element.
|
|
35
46
|
*
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
import { IconContent } from '../types/icon.types.js';
|
|
3
3
|
import { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types.js';
|
|
4
|
-
export type TabHeaderSlots = 'root' | 'tab' | 'icon' | 'labels' | 'title' | 'subtitle';
|
|
4
|
+
export type TabHeaderSlots = 'root' | 'tab' | 'icon' | 'labels' | 'title' | 'subtitle' | 'endContent';
|
|
5
5
|
export interface TabHeaderTab {
|
|
6
6
|
id: string;
|
|
7
7
|
icon: IconContent;
|
|
8
|
-
title?:
|
|
9
|
-
subtitle?:
|
|
8
|
+
title?: React.ReactNode;
|
|
9
|
+
subtitle?: React.ReactNode;
|
|
10
|
+
/** After the labels. Stays visible when a narrow screen collapses the tab, so keep it small: a `Badge`, a dot. */
|
|
11
|
+
endContent?: React.ReactNode;
|
|
10
12
|
disabled?: boolean;
|
|
11
13
|
}
|
|
12
14
|
export interface TabHeaderProps extends Omit<HtmlProps, 'onChange'> {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -29,6 +29,12 @@ export { default as Card } from './common/card/index.js';
|
|
|
29
29
|
export * from './common/card/index.js';
|
|
30
30
|
export { default as Carousel } from './common/carousel/index.js';
|
|
31
31
|
export * from './common/carousel/index.js';
|
|
32
|
+
export { default as ChatComposer } from './common/chatComposer/index.js';
|
|
33
|
+
export * from './common/chatComposer/index.js';
|
|
34
|
+
export { default as ChatMessage } from './common/chatMessage/index.js';
|
|
35
|
+
export * from './common/chatMessage/index.js';
|
|
36
|
+
export { default as ChatMessageList } from './common/chatMessageList/index.js';
|
|
37
|
+
export * from './common/chatMessageList/index.js';
|
|
32
38
|
export { default as Checkbox } from './common/checkbox/index.js';
|
|
33
39
|
export * from './common/checkbox/index.js';
|
|
34
40
|
export { default as Chip } from './common/chip/index.js';
|
package/docs/ActionIcon.md
CHANGED
|
@@ -38,8 +38,8 @@ import { faTrash, faXmark } from '@fortawesome/free-solid-svg-icons';
|
|
|
38
38
|
| `ref` | `Ref<HTMLButtonElement>` | Ref to the button |
|
|
39
39
|
|
|
40
40
|
Every other Button prop and native `<button>` attribute passes through, except `size`,
|
|
41
|
-
`children` and `
|
|
42
|
-
for a different one.
|
|
41
|
+
`children`, `isLoading` and `shortcut`. The box is the icon plus `4px` on each side; set `width`
|
|
42
|
+
and `height` for a different one.
|
|
43
43
|
|
|
44
44
|
**Slots:** `root` `icon`
|
|
45
45
|
|
package/docs/Button.md
CHANGED
|
@@ -11,7 +11,7 @@ works with any colour. The colours are the library-wide `ThemeColor`s or any CSS
|
|
|
11
11
|
[ThemeColor.md](ThemeColor.md).
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
|
-
import { Button, ButtonStyleType, ButtonSize, ButtonType, ThemeColor } from '@ahrowe/ui';
|
|
14
|
+
import { Button, ButtonStyleType, ButtonSize, ButtonType, ThemeColor, useHotkeys } from '@ahrowe/ui';
|
|
15
15
|
import { faPlus, faSave, faTrash, faXmark } from '@fortawesome/free-solid-svg-icons';
|
|
16
16
|
|
|
17
17
|
// The main action
|
|
@@ -52,6 +52,10 @@ import { faPlus, faSave, faTrash, faXmark } from '@fortawesome/free-solid-svg-ic
|
|
|
52
52
|
// Small
|
|
53
53
|
<Button size={ButtonSize.Small}>Cancel</Button>
|
|
54
54
|
|
|
55
|
+
// A keyboard shortcut: the button draws it, useHotkeys binds it
|
|
56
|
+
useHotkeys({ 'mod+s': handleSave });
|
|
57
|
+
<Button styleType={ButtonStyleType.Filled} shortcut='mod+s' onClick={handleSave}>Save</Button>
|
|
58
|
+
|
|
55
59
|
// Press-and-hold: act on press rather than release
|
|
56
60
|
<Button
|
|
57
61
|
icon={faPlus}
|
|
@@ -81,6 +85,7 @@ import { faPlus, faSave, faTrash, faXmark } from '@fortawesome/free-solid-svg-ic
|
|
|
81
85
|
| `size` | `ButtonSize` | Size variant. `Icon` strips all padding, inherits the surrounding text size, and centres a lone icon — set the width/height yourself |
|
|
82
86
|
| `type` | `ButtonType` | HTML button type |
|
|
83
87
|
| `icon` | `IconDefinition \| ReactElement` | FontAwesome icon or any React element, shown before label |
|
|
88
|
+
| `shortcut` | `string` | A combo in [useHotkeys](Hooks.md) spelling, e.g. `'mod+s'`. Shown after the label, written for the platform. Binding it is yours, see below |
|
|
84
89
|
| `isLoading` | `boolean` | Replaces content with a [Loader](Loader.md) in the button's text colour. Blocks clicks like `disabled` but keeps full colour |
|
|
85
90
|
| `disabled` | `boolean` | Fades the button to half opacity |
|
|
86
91
|
| `onClick` | `(e: MouseEvent) => void \| Promise<void>` | |
|
|
@@ -92,6 +97,17 @@ press-and-hold controls that act on press rather than release), `onContextMenu`,
|
|
|
92
97
|
takes the `ButtonType` enum, and `onClick` may return a promise. `color` is Button's own prop, not
|
|
93
98
|
the obsolete HTML attribute.
|
|
94
99
|
|
|
100
|
+
**Shortcuts are drawn, not bound.** `shortcut` writes the combo in a pill after the label,
|
|
101
|
+
`⌘S` on an Apple platform and `Ctrl+S` elsewhere. It also sets `aria-keyshortcuts`, so a screen
|
|
102
|
+
reader announces it apart from the button's name. It is hidden on a touch screen
|
|
103
|
+
(`pointer: coarse`), which usually has no keyboard to press it on.
|
|
104
|
+
|
|
105
|
+
Listening for it is yours, with `useHotkeys` and the same string, because only your app knows
|
|
106
|
+
when the action is available. A button cannot tell: it would still fire while hidden in an
|
|
107
|
+
inactive tab, behind an open Modal, or twice when the same action has a button in the toolbar and
|
|
108
|
+
another in the footer. Turn the binding off wherever the button is disabled:
|
|
109
|
+
`useHotkeys({ 'mod+s': handleSave }, { enabled: !isSaving })`.
|
|
110
|
+
|
|
95
111
|
**Theming:** each colour reads the theme variables listed in [ThemeColor.md](ThemeColor.md). The
|
|
96
112
|
`Filled` `Primary` button also paints `--primary-gradient` when the theme sets one. See
|
|
97
113
|
[ThemeProvider.md](ThemeProvider.md).
|
|
@@ -112,7 +128,7 @@ sits inside the edge, in `--focus-border-color`, or in the text colour on a `Fil
|
|
|
112
128
|
ripple is in the text colour too. Every button reserves a `1px` border, so switching style never
|
|
113
129
|
shifts layout or changes its height.
|
|
114
130
|
|
|
115
|
-
**Slots:** `background` `content` `loadingContainer` `label`
|
|
131
|
+
**Slots:** `background` `content` `loadingContainer` `label` `shortcut`
|
|
116
132
|
|
|
117
133
|
**Sizing in a flex row:** a Button never shrinks below its own content (`min-width: min-content`),
|
|
118
134
|
so an icon is never cut off by a flexible sibling. A multi-word label still wraps above that floor.
|
package/docs/CLAUDE.md
CHANGED
|
@@ -41,9 +41,9 @@ enums and slot names. Do not infer props from a component's name.
|
|
|
41
41
|
`Light` for status and selection, `Outlined` for secondary actions, `Plain` for tertiary ones.
|
|
42
42
|
- **`icon` props** take a FontAwesome `IconDefinition` or any React element.
|
|
43
43
|
- **`ref`** is a plain prop, there is no `forwardRef`. It lands on the element you would reach for:
|
|
44
|
-
Input, Textarea, NumberInput and SearchInput hand out their inner `<input
|
|
45
|
-
Ripple, VirtualList, RoomDrawer and RoomViewer
|
|
46
|
-
exported next to the props.
|
|
44
|
+
Input, Textarea, NumberInput and SearchInput hand out their inner `<input>`, ChatComposer its
|
|
45
|
+
`<textarea>`. Timer, Flip, Tilt, Ripple, VirtualList, ChatMessageList, RoomDrawer and RoomViewer
|
|
46
|
+
expose an imperative handle instead; its type is exported next to the props.
|
|
47
47
|
- Every module is `'use client'`; nothing renders on the server. The exception is
|
|
48
48
|
`@ahrowe/ui/floor-plan`, the plan model (FloorPlan.md) without React, for server code that reads
|
|
49
49
|
or validates a plan. No right-to-left support.
|
|
@@ -76,6 +76,9 @@ enums and slot names. Do not infer props from a component's name.
|
|
|
76
76
|
| Calendar.md | Calendar, CalendarView | Showing and editing your own events over time, in month, week, day and agenda views: bookings, shifts, appointments |
|
|
77
77
|
| Card.md | Card, CardStyleType, CardHeader, CardMedia, CardContent, CardActions, ThemeColor | Surface container for grouping related content — list items, profile cards, dashboard tiles, product listings |
|
|
78
78
|
| Carousel.md | Carousel | A slide track for stepping through a set of items one at a time, with arrows, dots, and drag/swipe support, plus optional autoplay |
|
|
79
|
+
| ChatComposer.md | ChatComposer | The field a chat message is written in, with its send button: under a ChatMessageList, which shows a whole chat put together |
|
|
80
|
+
| ChatMessage.md | ChatMessage, ChatMessageStyleType, ChatMessageStatus | One message in a conversation: a messenger bubble, a support agent's reply, an AI assistant's answer |
|
|
81
|
+
| ChatMessageList.md | ChatMessageList | The scrolling conversation of a chat: a messenger, a support widget, an AI assistant |
|
|
79
82
|
| Checkbox.md | Checkbox | Boolean toggle input — settings, multi-select lists, accept terms, feature flags |
|
|
80
83
|
| Chip.md | Chip, ChipContainer, ChipStyleType, ThemeColor | Compact tag, label, or filter token — selected items, applied filters, category badges, user avatars with remove |
|
|
81
84
|
| ColorPicker.md | ColorPicker | Hex colour selection — theme editors, annotation tools, customisation panels |
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# ChatComposer
|
|
2
|
+
|
|
3
|
+
**When to use:** The field a chat message is written in, with its send button: under a [ChatMessageList](ChatMessageList.md), which shows a whole chat put together. For multiline text in a form, with a label and validation, use [Textarea](Textarea.md).
|
|
4
|
+
|
|
5
|
+
**Keywords:** message input, chat input, prompt, compose, reply box, enter to send, submit, attachment
|
|
6
|
+
|
|
7
|
+
**Import:** `import { ChatComposer } from '@ahrowe/ui'`
|
|
8
|
+
**Types:** `import type { ChatComposerProps, ChatComposerLabels } from '@ahrowe/ui'`
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { ActionIcon, ChatComposer } from '@ahrowe/ui';
|
|
12
|
+
import { faFaceSmile, faPaperclip } from '@fortawesome/free-solid-svg-icons';
|
|
13
|
+
|
|
14
|
+
// Uncontrolled: it clears itself after sending
|
|
15
|
+
<ChatComposer onSend={(text) => send(text)} />
|
|
16
|
+
|
|
17
|
+
// AI chat: a stop button replaces send while the answer streams
|
|
18
|
+
<ChatComposer
|
|
19
|
+
onSend={ask}
|
|
20
|
+
onStop={isStreaming ? stop : undefined}
|
|
21
|
+
labels={{ placeholder: 'Ask anything' }}
|
|
22
|
+
/>
|
|
23
|
+
|
|
24
|
+
// Buttons of your own around the field
|
|
25
|
+
<ChatComposer
|
|
26
|
+
onSend={send}
|
|
27
|
+
startContent={<ActionIcon icon={faPaperclip} aria-label='Attach a file' onClick={openFilePicker} />}
|
|
28
|
+
endContent={<ActionIcon icon={faFaceSmile} aria-label='Emoji' onClick={openEmojiPicker} />}
|
|
29
|
+
/>
|
|
30
|
+
|
|
31
|
+
// Controlled, e.g. to keep a draft per conversation: sending clears it through onChange('')
|
|
32
|
+
<ChatComposer value={draft} onChange={setDraft} onSend={send} />
|
|
33
|
+
|
|
34
|
+
// Native attributes go to the textarea; an Enter your onKeyDown prevents is not sent
|
|
35
|
+
<ChatComposer
|
|
36
|
+
onSend={send}
|
|
37
|
+
maxLength={2000}
|
|
38
|
+
autoFocus
|
|
39
|
+
onKeyDown={(event) => {
|
|
40
|
+
if (event.key === 'Enter' && isMentionListOpen) {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
pickMention();
|
|
43
|
+
}
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Keys:**
|
|
49
|
+
|
|
50
|
+
| Key | Does |
|
|
51
|
+
|-----|------|
|
|
52
|
+
| `Enter` | Sends. On a touch screen it starts a new line instead, since that keyboard has no other way to, and only the button sends |
|
|
53
|
+
| `Shift+Enter` | A new line |
|
|
54
|
+
| `Ctrl+Enter` / `⌘+Enter` | Sends, on a touch screen too |
|
|
55
|
+
|
|
56
|
+
The `Enter` that confirms a word in a Japanese, Chinese or Korean input method never sends,
|
|
57
|
+
including in Safari, which reports it as an ordinary `Enter`.
|
|
58
|
+
|
|
59
|
+
`onSend` gets the text trimmed, and is not called while the field is empty or only whitespace;
|
|
60
|
+
the send button is disabled then too. The focus stays in the field after sending, so a phone
|
|
61
|
+
keyboard stays open. The field grows with the text up to seven lines, then scrolls, and measures
|
|
62
|
+
again when its width changes.
|
|
63
|
+
|
|
64
|
+
**Buttons of your own.** `startContent` and `endContent` sit beside the field while the text fits
|
|
65
|
+
in it. Once it needs more lines than `rows` (a second one, by default), the field takes the whole
|
|
66
|
+
width and the buttons move to a row below it: `startContent` at the start, `endContent` and the
|
|
67
|
+
send button at the end, the usual toolbar of an AI chat. So several of them never leave the field
|
|
68
|
+
too narrow to write in. They move back once the field is empty, usually after sending, rather
|
|
69
|
+
than as soon as the wider field fits the text on one line, which would wrap it again. With only
|
|
70
|
+
the send button there is nothing to move, and it stays beside the field.
|
|
71
|
+
|
|
72
|
+
While `onStop` is set the send button is a stop button that calls it, and `Enter` sends nothing,
|
|
73
|
+
so the user can type the next message while an answer streams in.
|
|
74
|
+
|
|
75
|
+
**Key props:**
|
|
76
|
+
|
|
77
|
+
| Prop | Type | Description |
|
|
78
|
+
|------|------|-------------|
|
|
79
|
+
| `onSend` | `(text: string) => void` | Called with the trimmed text (required) |
|
|
80
|
+
| `value` | `string` | Controlled value. Sending clears it through `onChange('')` |
|
|
81
|
+
| `defaultValue` | `string` | Initial value, uncontrolled |
|
|
82
|
+
| `onChange` | `(value: string) => void` | Every change |
|
|
83
|
+
| `onStop` | `() => void` | Swaps send for a stop button while set |
|
|
84
|
+
| `onKeyDown` | `KeyboardEventHandler` | Runs before the composer's own keys; `preventDefault()` on an Enter keeps it from sending |
|
|
85
|
+
| `disabled` | `boolean` | Disables the field and both buttons |
|
|
86
|
+
| `rows` | `number` | The empty field's height in lines, default 1. It grows from there to seven |
|
|
87
|
+
| `startContent` | `ReactNode` | Before the field: attach, emoji. Below it once the text wraps, see above |
|
|
88
|
+
| `endContent` | `ReactNode` | Between the field and the send button. Below it once the text wraps |
|
|
89
|
+
| `ref` | `Ref<HTMLTextAreaElement>` | Ref to the inner `<textarea>`, e.g. to focus it when a conversation opens |
|
|
90
|
+
|
|
91
|
+
Every other native attribute (`maxLength`, `autoFocus`, `name`, `spellCheck`, `onPaste`, `aria-*`,
|
|
92
|
+
`data-*`) goes to the `<textarea>`, as on [Input](Input.md); `className` and `style` go to the
|
|
93
|
+
root. An `aria-label` replaces the placeholder as the field's name, and `readOnly` also keeps it
|
|
94
|
+
from sending.
|
|
95
|
+
|
|
96
|
+
**Global defaults:** adopts `ConfigProvider`. See [ConfigProvider.md](ConfigProvider.md).
|
|
97
|
+
|
|
98
|
+
**Slots:** `input` (the `<textarea>`) `button` (send or stop)
|
|
99
|
+
|
|
100
|
+
**Labels:** `placeholder` (also the field's accessible name) `send` `stop`: 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: `ChatComposerLabels`.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# ChatMessage
|
|
2
|
+
|
|
3
|
+
**When to use:** One message in a conversation: a messenger bubble, a support agent's reply, an AI assistant's answer. Put them in a [ChatMessageList](ChatMessageList.md), which also shows a whole chat put together. For a note between messages ("Lisa joined", a date), use a [Divider](Divider.md) with `text`.
|
|
4
|
+
|
|
5
|
+
**Keywords:** speech bubble, messenger, whatsapp, chatbot, assistant, LLM, reply, read receipt, seen, ticks, typing indicator
|
|
6
|
+
|
|
7
|
+
**Import:** `import { ChatMessage, ChatMessageStyleType, ChatMessageStatus } from '@ahrowe/ui'`
|
|
8
|
+
**Types:** `import type { ChatMessageProps, ChatMessageLabels } from '@ahrowe/ui'`
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { Avatar, ChatMessage, ChatMessageStatus, ChatMessageStyleType, CopyButton, Loader, LoaderType, ThemeColor } from '@ahrowe/ui';
|
|
12
|
+
|
|
13
|
+
// Messenger: two from Ava in a run, then your own with its delivery state
|
|
14
|
+
<ChatMessage author='Ava' avatar={<Avatar name='Ava' />} time='10:41'>Are we still on for tonight?</ChatMessage>
|
|
15
|
+
<ChatMessage author='Ava' avatar={<Avatar name='Ava' />} time='10:41' isContinued>8 o'clock?</ChatMessage>
|
|
16
|
+
<ChatMessage isOwn time='10:42' status={ChatMessageStatus.Read}>Yes, see you there</ChatMessage>
|
|
17
|
+
|
|
18
|
+
// AI assistant: no bubble, full width, actions underneath
|
|
19
|
+
<ChatMessage styleType={ChatMessageStyleType.Plain} actions={<CopyButton value={answer} />}>
|
|
20
|
+
<Markdown>{answer}</Markdown>
|
|
21
|
+
</ChatMessage>
|
|
22
|
+
|
|
23
|
+
// Someone typing, or an answer on its way: a Loader is the content
|
|
24
|
+
<ChatMessage avatar={<Avatar name='Ava' />}>
|
|
25
|
+
<Loader type={LoaderType.Dots} size='1em' color={ThemeColor.Neutral} labels={{ loading: 'Ava is typing' }} />
|
|
26
|
+
</ChatMessage>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Enums:**
|
|
30
|
+
|
|
31
|
+
- `ChatMessageStyleType`: `Filled` (default when `isOwn`) | `Light` (default otherwise) | `Plain`, see [ThemeColor.md](ThemeColor.md). A `Light` bubble keeps the page's text colour, as a `Light` Card does. `Plain` has no bubble and takes the full width, for an assistant's answer
|
|
32
|
+
- `ChatMessageStatus`: `Sending` (a clock) | `Sent` (a check) | `Delivered` (two checks) | `Read` (two checks at full strength, in `--info-color` except on a `Filled` bubble, where the rest are dimmed) | `Failed` (no icon in the bubble: "Not sent" under it, ahead of `actions`, where a retry button belongs, with the icon in `--error-color` and the text in the page's own)
|
|
33
|
+
|
|
34
|
+
**A run of messages.** Set `isContinued` on every message after the first from the same author:
|
|
35
|
+
it hides `author` from view, tightens the gap above, rounds off the corner that points at the
|
|
36
|
+
author, and keeps the space of `avatar` so the run stays aligned. Pass the same `author` and
|
|
37
|
+
`avatar` to all of them: screen readers still read `author` on each one. Start a new run after a
|
|
38
|
+
date `Divider`, or the first message under it shows no name.
|
|
39
|
+
|
|
40
|
+
**Who sent it, for screen readers.** Alignment and colour are not read out, so each message names
|
|
41
|
+
its sender: `author`, or "You" (`labels.you`, never shown) on an own message without one. In a
|
|
42
|
+
one-to-one chat that is enough to leave `author` out; in a group, pass it to everyone else's.
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
{messages.map((message, index) => (
|
|
46
|
+
<ChatMessage
|
|
47
|
+
key={message.id}
|
|
48
|
+
isOwn={message.authorId === me.id}
|
|
49
|
+
isContinued={messages[index - 1]?.authorId === message.authorId}
|
|
50
|
+
author={message.authorId === me.id ? undefined : message.authorName}
|
|
51
|
+
avatar={message.authorId === me.id ? undefined : <Avatar name={message.authorName} src={message.avatarUrl} />}
|
|
52
|
+
time={timeFormat.format(message.sentAt)}
|
|
53
|
+
status={message.status}
|
|
54
|
+
>
|
|
55
|
+
{message.text}
|
|
56
|
+
</ChatMessage>
|
|
57
|
+
))}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Content.** A string keeps its line breaks, so text typed with Shift+Enter shows as typed. Any
|
|
61
|
+
other content renders as given: the library renders no Markdown, so pass your renderer's output
|
|
62
|
+
(`react-markdown`, `marked`). A long word or URL wraps rather than widening the bubble. `time` and
|
|
63
|
+
`status` sit at the bubble's end: on the same line as a short message, on a line of their own
|
|
64
|
+
under a long one.
|
|
65
|
+
|
|
66
|
+
**Key props:**
|
|
67
|
+
|
|
68
|
+
| Prop | Type | Description |
|
|
69
|
+
|------|------|-------------|
|
|
70
|
+
| `children` | `ReactNode` | The message |
|
|
71
|
+
| `isOwn` | `boolean` | Sent by the current user: aligned to the end, `Filled` in `Primary` by default |
|
|
72
|
+
| `isContinued` | `boolean` | Follows a message from the same author, see above |
|
|
73
|
+
| `avatar` | `ReactNode` | Beside the bubble, typically an [Avatar](Avatar.md) |
|
|
74
|
+
| `author` | `ReactNode` | Name above the bubble, on the first message of a run. Screen readers get it on every one |
|
|
75
|
+
| `time` | `ReactNode` | In the bubble's corner. Format it yourself, e.g. with `Intl.DateTimeFormat` |
|
|
76
|
+
| `status` | `ChatMessageStatus` | Delivery state, an icon after `time` that screen readers read out. `Failed` is a notice under the bubble instead |
|
|
77
|
+
| `actions` | `ReactNode` | Under the bubble, after the `Failed` notice: a [CopyButton](CopyButton.md), retry, thumbs up and down |
|
|
78
|
+
| `styleType` | `ChatMessageStyleType` | See above |
|
|
79
|
+
| `color` | `ColorValue` | A `ThemeColor` or any CSS colour. Default `Primary` when `isOwn`, otherwise `Neutral` |
|
|
80
|
+
|
|
81
|
+
The bubble is at most 80% of the list's width (40em at most); `styles={{ body: { maxWidth } }}`
|
|
82
|
+
changes that.
|
|
83
|
+
|
|
84
|
+
**Global defaults:** adopts `ConfigProvider`. See [ConfigProvider.md](ConfigProvider.md).
|
|
85
|
+
|
|
86
|
+
**Slots:** `avatar` `body` (the column holding author, bubble and actions) `author` `bubble` `meta` (time and status) `actions` (the row under the bubble, with the `Failed` notice)
|
|
87
|
+
|
|
88
|
+
**Labels:** `sending` `sent` `delivered` `read` (the names screen readers give the status icon), `failed` (the notice under a failed message) and `you` (an own message's sender, for screen readers only). Pass `labels` to override any of them, on the component or app-wide through `ConfigProvider`; they merge per key. Type: `ChatMessageLabels`.
|