@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @ahrowe/ui
|
|
2
2
|
|
|
3
|
+
## 0.50.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d7ce064: Added `ChatComposer`: the message field with its send button, which sends on Enter (a new line on a touch screen), never on the Enter that confirms an IME word, grows to seven lines, moves buttons of your own to a row below once the text wraps, and swaps to a stop button through `onStop` while an AI answer streams. See docs/ChatComposer.md
|
|
8
|
+
- d7ce064: Added `ChatMessageList`: the scrolling conversation, which follows new and streaming messages while the user is at the bottom, holds still while they read further up, offers a button back down, and loads older messages at the top through `onLoadMore` without moving the view, in Safari too. See docs/ChatMessageList.md
|
|
9
|
+
- d7ce064: Added `ChatMessage`: one message of a chat as a bubble, or without one for an AI answer, with author, avatar, time, delivery status (`ChatMessageStatus`) and actions, and `isContinued` for a run from the same author. See docs/ChatMessage.md
|
|
10
|
+
- 9bb5c12: Added `endContent` to `TabHeaderTab`, shown after the labels and still visible when a narrow screen collapses the tab to its icon, with an `endContent` slot. See docs/TabHeader.md
|
|
11
|
+
- 9bb5c12: `TabHeaderTab`'s `title` and `subtitle` now take any `ReactNode`, not only a string
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- d7ce064: Fixed `Textarea` with `autoResize` keeping its old height when a change of width rewrapped its text
|
|
16
|
+
|
|
17
|
+
## 0.49.0
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- fa4844e: Button takes a `shortcut` and draws it in a pill after the label, `⌘S` or `Ctrl+S` for the platform, with `aria-keyshortcuts` to match; binding it stays with `useHotkeys`
|
|
22
|
+
- fa4844e: `useHotkeys` fires once for a held combo, so holding ⌘S saves once; `repeat: true` brings the repeats back for keys meant to be held, see the migration section in Hooks.md
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- fa4844e: Menu and CommandPalette announce an entry's shortcut through `aria-keyshortcuts` instead of reading it out as part of the entry's name
|
|
27
|
+
|
|
3
28
|
## 0.48.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -130,6 +130,9 @@ runs where `@ahrowe/ui` is installed.
|
|
|
130
130
|
| `Calendar`, `CalendarView` | Showing and editing your own events over time, in month, week, day and agenda views: bookings, shifts, appointments |
|
|
131
131
|
| `Card`, `CardStyleType`, `CardHeader`, `CardMedia`, `CardContent`, `CardActions`, `ThemeColor` | Surface container for grouping related content — list items, profile cards, dashboard tiles, product listings |
|
|
132
132
|
| `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 |
|
|
133
|
+
| `ChatComposer` | The field a chat message is written in, with its send button: under a ChatMessageList, which shows a whole chat put together |
|
|
134
|
+
| `ChatMessage`, `ChatMessageStyleType`, `ChatMessageStatus` | One message in a conversation: a messenger bubble, a support agent's reply, an AI assistant's answer |
|
|
135
|
+
| `ChatMessageList` | The scrolling conversation of a chat: a messenger, a support widget, an AI assistant |
|
|
133
136
|
| `Checkbox` | Boolean toggle input — settings, multi-select lists, accept terms, feature flags |
|
|
134
137
|
| `Chip`, `ChipContainer`, `ChipStyleType`, `ThemeColor` | Compact tag, label, or filter token — selected items, applied filters, category badges, user avatars with remove |
|
|
135
138
|
| `ColorPicker` | Hex colour selection — theme editors, annotation tools, customisation panels |
|
|
@@ -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'> {
|
|
@@ -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';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{ThemeColor as t}from"../themeProvider/color.types.mjs";import{toneProps as n,useToneCheck as r}from"../styles/tone.mjs";import i from"./button.module.mjs";import a from"../loader/loader.mjs";import{ButtonSize as o,ButtonStyleType as s,ButtonType as c}from"./button.types.mjs";import l from"../ripple/ripple.mjs";import{renderIcon as u}from"../utils/renderIcon.mjs";import{
|
|
1
|
+
"use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{ThemeColor as t}from"../themeProvider/color.types.mjs";import{toneProps as n,useToneCheck as r}from"../styles/tone.mjs";import i from"./button.module.mjs";import a from"../loader/loader.mjs";import{ButtonSize as o,ButtonStyleType as s,ButtonType as c}from"./button.types.mjs";import l from"../ripple/ripple.mjs";import{renderIcon as u}from"../utils/renderIcon.mjs";import{ariaKeyshortcuts as d,formatHotkey as f}from"../hooks/useHotkeys.mjs";import{useRef as p}from"react";import m from"classnames";import{jsx as h,jsxs as g}from"react/jsx-runtime";var _={filled:i.buttonStyleFilled,light:i.buttonStyleLight,outlined:i.buttonStyleOutlined,plain:i.buttonStylePlain},v={small:i.buttonSizeSmall,medium:i.buttonSizeMedium,large:i.buttonSizeLarge,icon:i.buttonSizeIcon},y=`color-mix(in srgb, currentcolor 20%, transparent)`;function b(b){let{className:x,style:S,type:C=c.Button,styleType:w=s.Outlined,color:T=t.Primary,size:E=o.Medium,onClick:D=()=>{},children:O,icon:k,shortcut:A,"aria-keyshortcuts":j=d(A),isLoading:M=!1,disabled:N=!1,title:P=void 0,ref:F,classNames:I,styles:L,...R}=e(`Button`,b),z=p(null),B=n(T);r(`Button`,[T]);function V(e){return e.detail===0&&z.current?.addRipple(),D(e)}return h(`button`,{...R,className:m(i.button,_[w],B.className,v[E],{[i.buttonNotAllowed]:M||N,[i.buttonDisabled]:N},x),style:{...B.style,...S},type:C,onClick:V,disabled:N||M,title:P,"aria-keyshortcuts":j,ref:F,children:h(l,{ref:z,className:i.buttonRipple,disabled:M||N,rippleBackground:y,children:h(`div`,{className:m(i.buttonBackground,I?.background),style:L?.background,children:g(`div`,{className:m(i.buttonContent,I?.content),style:L?.content,children:[M?h(`div`,{className:m(i.buttonLoadingContainer,I?.loadingContainer),style:L?.loadingContainer,children:h(a,{size:`1.2em`,color:t.Neutral})}):null,g(`div`,{className:m(I?.label,{[i.buttonLoading]:M,[i.buttonWithIcon]:!!k}),style:L?.label,children:[k?u(k):null,A?g(`span`,{children:[O,h(`span`,{"aria-hidden":!0,className:m(i.buttonShortcut,I?.shortcut),style:L?.shortcut,children:f(A)})]}):O]})]})})})})}b.StyleType=s,b.Size=o,b.Type=c;export{b as default};
|
|
2
2
|
//# sourceMappingURL=button.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.mjs","names":[],"sources":["../../../../package/common/button/button.tsx"],"sourcesContent":["import cx from 'classnames';\nimport { useRef, type ReactElement } from 'react';\n\nimport styles from './button.module.pcss';\nimport Loader from '../loader';\nimport { ButtonStyleType, ButtonSize, ButtonType } from './button.types';\nimport { ThemeColor } from '../themeProvider/color.types';\nimport type { ButtonProps } from './button.types';\nimport Ripple from '../ripple';\nimport type { RippleHandle } from '../ripple';\nimport { useComponentDefaults } from '../configProvider';\nimport { renderIcon } from '../utils/renderIcon';\nimport { toneProps, useToneCheck } from '../styles/tone';\n\nconst CLASS_BY_STYLE: Record<ButtonStyleType, string> = {\n filled: styles.buttonStyleFilled,\n light: styles.buttonStyleLight,\n outlined: styles.buttonStyleOutlined,\n plain: styles.buttonStylePlain,\n};\n\nconst CLASS_BY_SIZE: Record<ButtonSize, string> = {\n small: styles.buttonSizeSmall,\n medium: styles.buttonSizeMedium,\n large: styles.buttonSizeLarge,\n icon: styles.buttonSizeIcon,\n};\n\n// Ripple's own default is white, which vanishes on a transparent button over a light page.\nconst RIPPLE_BACKGROUND = 'color-mix(in srgb, currentcolor 20%, transparent)';\n\nfunction Button(props: ButtonProps): ReactElement {\n const {\n className,\n style,\n type = ButtonType.Button,\n styleType = ButtonStyleType.Outlined,\n color = ThemeColor.Primary,\n size = ButtonSize.Medium,\n onClick = (): void => {},\n children,\n icon,\n isLoading = false,\n disabled = false,\n title = undefined,\n ref,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('Button', props);\n\n const rippleRef = useRef<RippleHandle>(null);\n const tone = toneProps(color);\n useToneCheck('Button', [color]);\n\n function handleClick(event: React.MouseEvent<HTMLButtonElement>) {\n if (event.detail === 0) rippleRef.current?.addRipple();\n return onClick(event);\n }\n\n return (\n <button\n {...rest}\n className={cx(\n styles.button,\n CLASS_BY_STYLE[styleType],\n tone.className,\n CLASS_BY_SIZE[size],\n {\n [styles.buttonNotAllowed]: isLoading || disabled,\n [styles.buttonDisabled]: disabled,\n },\n className,\n )}\n style={{ ...tone.style, ...style }}\n type={type}\n onClick={handleClick}\n disabled={disabled || isLoading}\n title={title}\n ref={ref}\n >\n <Ripple\n ref={rippleRef}\n className={styles.buttonRipple}\n disabled={isLoading || disabled}\n rippleBackground={RIPPLE_BACKGROUND}\n >\n <div className={cx(styles.buttonBackground, classNames?.background)} style={slotStyles?.background}>\n <div className={cx(styles.buttonContent, classNames?.content)} style={slotStyles?.content}>\n {isLoading ? (\n <div\n className={cx(styles.buttonLoadingContainer, classNames?.loadingContainer)}\n style={slotStyles?.loadingContainer}\n >\n <Loader size='1.2em' color={ThemeColor.Neutral} />\n </div>\n ) : null}\n <div\n className={cx(classNames?.label, {\n [styles.buttonLoading]: isLoading,\n [styles.buttonWithIcon]: !!icon,\n })}\n style={slotStyles?.label}\n >\n {icon ? renderIcon(icon) : null}\n {children}\n </div>\n </div>\n </div>\n </Ripple>\n </button>\n );\n}\n\nButton.StyleType = ButtonStyleType;\nButton.Size = ButtonSize;\nButton.Type = ButtonType;\n\nexport default Button;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"button.mjs","names":[],"sources":["../../../../package/common/button/button.tsx"],"sourcesContent":["import cx from 'classnames';\nimport { useRef, type ReactElement } from 'react';\n\nimport styles from './button.module.pcss';\nimport Loader from '../loader';\nimport { ButtonStyleType, ButtonSize, ButtonType } from './button.types';\nimport { ThemeColor } from '../themeProvider/color.types';\nimport type { ButtonProps } from './button.types';\nimport Ripple from '../ripple';\nimport type { RippleHandle } from '../ripple';\nimport { useComponentDefaults } from '../configProvider';\nimport { renderIcon } from '../utils/renderIcon';\nimport { ariaKeyshortcuts, formatHotkey } from '../hooks/useHotkeys';\nimport { toneProps, useToneCheck } from '../styles/tone';\n\nconst CLASS_BY_STYLE: Record<ButtonStyleType, string> = {\n filled: styles.buttonStyleFilled,\n light: styles.buttonStyleLight,\n outlined: styles.buttonStyleOutlined,\n plain: styles.buttonStylePlain,\n};\n\nconst CLASS_BY_SIZE: Record<ButtonSize, string> = {\n small: styles.buttonSizeSmall,\n medium: styles.buttonSizeMedium,\n large: styles.buttonSizeLarge,\n icon: styles.buttonSizeIcon,\n};\n\n// Ripple's own default is white, which vanishes on a transparent button over a light page.\nconst RIPPLE_BACKGROUND = 'color-mix(in srgb, currentcolor 20%, transparent)';\n\nfunction Button(props: ButtonProps): ReactElement {\n const {\n className,\n style,\n type = ButtonType.Button,\n styleType = ButtonStyleType.Outlined,\n color = ThemeColor.Primary,\n size = ButtonSize.Medium,\n onClick = (): void => {},\n children,\n icon,\n shortcut,\n // The caller's own wins: it may list a second binding the hint leaves out.\n 'aria-keyshortcuts': keyshortcuts = ariaKeyshortcuts(shortcut),\n isLoading = false,\n disabled = false,\n title = undefined,\n ref,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('Button', props);\n\n const rippleRef = useRef<RippleHandle>(null);\n const tone = toneProps(color);\n useToneCheck('Button', [color]);\n\n function handleClick(event: React.MouseEvent<HTMLButtonElement>) {\n if (event.detail === 0) rippleRef.current?.addRipple();\n return onClick(event);\n }\n\n return (\n <button\n {...rest}\n className={cx(\n styles.button,\n CLASS_BY_STYLE[styleType],\n tone.className,\n CLASS_BY_SIZE[size],\n {\n [styles.buttonNotAllowed]: isLoading || disabled,\n [styles.buttonDisabled]: disabled,\n },\n className,\n )}\n style={{ ...tone.style, ...style }}\n type={type}\n onClick={handleClick}\n disabled={disabled || isLoading}\n title={title}\n aria-keyshortcuts={keyshortcuts}\n ref={ref}\n >\n <Ripple\n ref={rippleRef}\n className={styles.buttonRipple}\n disabled={isLoading || disabled}\n rippleBackground={RIPPLE_BACKGROUND}\n >\n <div className={cx(styles.buttonBackground, classNames?.background)} style={slotStyles?.background}>\n <div className={cx(styles.buttonContent, classNames?.content)} style={slotStyles?.content}>\n {isLoading ? (\n <div\n className={cx(styles.buttonLoadingContainer, classNames?.loadingContainer)}\n style={slotStyles?.loadingContainer}\n >\n <Loader size='1.2em' color={ThemeColor.Neutral} />\n </div>\n ) : null}\n <div\n className={cx(classNames?.label, {\n [styles.buttonLoading]: isLoading,\n [styles.buttonWithIcon]: !!icon,\n })}\n style={slotStyles?.label}\n >\n {icon ? renderIcon(icon) : null}\n {shortcut ? (\n // One inline run with the label, so the hint sits on its baseline: the icon's flex\n // row would make the hint its own item and centre it instead.\n <span>\n {children}\n {/* Out of the accessible name, which would read \"Save Ctrl+S\": aria-keyshortcuts says it. */}\n <span\n aria-hidden\n className={cx(styles.buttonShortcut, classNames?.shortcut)}\n style={slotStyles?.shortcut}\n >\n {formatHotkey(shortcut)}\n </span>\n </span>\n ) : (\n children\n )}\n </div>\n </div>\n </div>\n </Ripple>\n </button>\n );\n}\n\nButton.StyleType = ButtonStyleType;\nButton.Size = ButtonSize;\nButton.Type = ButtonType;\n\nexport default Button;\n"],"mappings":"2oBAeA,IAAM,EAAkD,CACtD,OAAQ,EAAO,kBACf,MAAO,EAAO,iBACd,SAAU,EAAO,oBACjB,MAAO,EAAO,gBAChB,EAEM,EAA4C,CAChD,MAAO,EAAO,gBACd,OAAQ,EAAO,iBACf,MAAO,EAAO,gBACd,KAAM,EAAO,cACf,EAGM,EAAoB,oDAE1B,SAAS,EAAO,EAAkC,CAChD,GAAM,CACJ,YACA,QACA,OAAO,EAAW,OAClB,YAAY,EAAgB,SAC5B,QAAQ,EAAW,QACnB,OAAO,EAAW,OAClB,cAAsB,CAAC,EACvB,WACA,OACA,WAEA,oBAAqB,EAAe,EAAiB,CAAQ,EAC7D,YAAY,GACZ,WAAW,GACX,QAAQ,IAAA,GACR,MACA,aACA,OAAQ,EACR,GAAG,GACD,EAAqB,SAAU,CAAK,EAElC,EAAY,EAAqB,IAAI,EACrC,EAAO,EAAU,CAAK,EAC5B,EAAa,SAAU,CAAC,CAAK,CAAC,EAE9B,SAAS,EAAY,EAA4C,CAE/D,OADI,EAAM,SAAW,GAAG,EAAU,SAAS,UAAU,EAC9C,EAAQ,CAAK,CACtB,CAEA,OACE,EAAC,SAAD,CACE,GAAI,EACJ,UAAW,EACT,EAAO,OACP,EAAe,GACf,EAAK,UACL,EAAc,GACd,EACG,EAAO,kBAAmB,GAAa,GACvC,EAAO,gBAAiB,CAC3B,EACA,CACF,EACA,MAAO,CAAE,GAAG,EAAK,MAAO,GAAG,CAAM,EAC3B,OACN,QAAS,EACT,SAAU,GAAY,EACf,QACP,oBAAmB,EACd,MAEL,SAAA,EAAC,EAAD,CACE,IAAK,EACL,UAAW,EAAO,aAClB,SAAU,GAAa,EACvB,iBAAkB,EAElB,SAAA,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,iBAAkB,GAAY,UAAU,EAAG,MAAO,GAAY,WACtF,SAAA,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,cAAe,GAAY,OAAO,EAAG,MAAO,GAAY,QAAlF,SAAA,CACG,EACC,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,uBAAwB,GAAY,gBAAgB,EACzE,MAAO,GAAY,iBAEnB,SAAA,EAAC,EAAD,CAAQ,KAAK,QAAQ,MAAO,EAAW,OAAU,CAAA,CAC9C,CAAA,EACH,KACJ,EAAC,MAAD,CACE,UAAW,EAAG,GAAY,MAAO,EAC9B,EAAO,eAAgB,GACvB,EAAO,gBAAiB,CAAC,CAAC,CAC7B,CAAC,EACD,MAAO,GAAY,MALrB,SAAA,CAOG,EAAO,EAAW,CAAI,EAAI,KAC1B,EAGC,EAAC,OAAD,CAAA,SAAA,CACG,EAED,EAAC,OAAD,CACE,cAAA,GACA,UAAW,EAAG,EAAO,eAAgB,GAAY,QAAQ,EACzD,MAAO,GAAY,SAElB,SAAA,EAAa,CAAQ,CAClB,CAAA,CACF,CAAA,CAAA,EAEN,CAEC,CACF,CAAA,CAAA,GACF,CAAA,CACC,CAAA,CACF,CAAA,CAEZ,CAEA,EAAO,UAAY,EACnB,EAAO,KAAO,EACd,EAAO,KAAO"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use client";var e=`button_wYLPJ`,t=`button-notAllowed_xGT-E`,n=`button-disabled_XDm5w`,r=`button-content_Q-0-4`,i=`button-loading_TY6y7`,a=`button-loading-container_pMSta`,o=`button-withIcon_IXEKA`,s=`button-style-filled_gsaJ4`,
|
|
1
|
+
"use client";var e=`button_wYLPJ`,t=`button-notAllowed_xGT-E`,n=`button-disabled_XDm5w`,r=`button-content_Q-0-4`,i=`button-loading_TY6y7`,a=`button-loading-container_pMSta`,o=`button-withIcon_IXEKA`,s=`button-shortcut_0bg8S`,c=`button-style-filled_gsaJ4`,l=`button-style-light_HBi34`,u=`button-style-outlined_OSfMq`,d=`button-style-plain_CUtTy`,f=`button-background_9Vfo5`,p=`button-size-small_mn6jE`,m=`button-size-medium_gNVDM`,h=`button-size-large_xSIcS`,g=`button-size-icon_pkqwl`,_=`button-ripple_UGtt8`,v={button:e,"button-notAllowed":`button-notAllowed_xGT-E`,buttonNotAllowed:t,"button-disabled":`button-disabled_XDm5w`,buttonDisabled:n,"button-content":`button-content_Q-0-4`,buttonContent:r,"button-loading":`button-loading_TY6y7`,buttonLoading:i,"button-loading-container":`button-loading-container_pMSta`,buttonLoadingContainer:a,"button-withIcon":`button-withIcon_IXEKA`,buttonWithIcon:o,"button-shortcut":`button-shortcut_0bg8S`,buttonShortcut:s,"button-style-filled":`button-style-filled_gsaJ4`,buttonStyleFilled:c,"button-style-light":`button-style-light_HBi34`,buttonStyleLight:l,"button-style-outlined":`button-style-outlined_OSfMq`,buttonStyleOutlined:u,"button-style-plain":`button-style-plain_CUtTy`,buttonStylePlain:d,"button-background":`button-background_9Vfo5`,buttonBackground:f,"button-size-small":`button-size-small_mn6jE`,buttonSizeSmall:p,"button-size-medium":`button-size-medium_gNVDM`,buttonSizeMedium:m,"button-size-large":`button-size-large_xSIcS`,buttonSizeLarge:h,"button-size-icon":`button-size-icon_pkqwl`,buttonSizeIcon:g,"button-ripple":`button-ripple_UGtt8`,buttonRipple:_};export{e as button,f as buttonBackground,r as buttonContent,n as buttonDisabled,i as buttonLoading,a as buttonLoadingContainer,t as buttonNotAllowed,_ as buttonRipple,s as buttonShortcut,g as buttonSizeIcon,h as buttonSizeLarge,m as buttonSizeMedium,p as buttonSizeSmall,c as buttonStyleFilled,l as buttonStyleLight,u as buttonStyleOutlined,d as buttonStylePlain,o as buttonWithIcon,v as default};
|
|
2
2
|
//# sourceMappingURL=button.module.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.module.mjs","names":[],"sources":["../../../../package/common/button/button.module.pcss"],"sourcesContent":[".button {\n appearance: none;\n -webkit-appearance: none;\n box-sizing: border-box;\n font-size: var(--default-font-size);\n background: transparent;\n border: 1px solid transparent;\n padding: 0;\n font-family: var(--default-font);\n border-radius: var(--default-border-radius);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n position: relative;\n overflow: hidden;\n min-width: min-content;\n\n /* Hover and press layer in currentcolor, so one rule fits every style. On the root, which a\n stretched button's inner element would not fill; under the Ripple wrapper, which comes later. */\n &::before {\n content: '';\n position: absolute;\n inset: 0;\n background: currentcolor;\n opacity: 0;\n transition: opacity 0.15s;\n pointer-events: none;\n }\n\n &-notAllowed {\n cursor: not-allowed;\n }\n\n /* Disabled only, not loading: a faded spinner would read as disabled rather than working. */\n &-disabled {\n opacity: 0.5;\n }\n\n /* Inside the edge: outside, a ButtonGroup neighbour with the same fill swallows the ring. A\n Filled button draws it in its text colour instead, as Pagination's active item does. */\n &:focus-visible {\n outline: 2px solid var(--focus-border-color, var(--primary-color));\n outline-offset: -4px;\n }\n\n /* (hover: hover) because on touch a tap latches :hover until the next tap elsewhere. */\n @media (hover: hover) {\n &:hover:not(:disabled)::before {\n opacity: 0.08;\n }\n }\n\n /* After the hover rule, which it ties on specificity: a press is a hover too. */\n &:active:not(:disabled)::before {\n opacity: 0.16;\n }\n\n &-content {\n position: relative;\n white-space: break-spaces;\n }\n\n &-loading {\n visibility: hidden;\n\n &-container {\n position: absolute;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n }\n\n &-withIcon {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.5em;\n }\n\n &-style {\n &-filled {\n background: var(--tone-fill);\n\n /* Must follow the shorthand above, which resets background-image. */\n background-image: var(--tone-gradient);\n color: var(--tone-contrast);\n\n /* After the base :focus-visible rule, which it ties on specificity. */\n &:focus-visible {\n outline-color: currentcolor;\n }\n }\n\n &-light {\n background: var(--tone-light);\n color: var(--tone-text);\n }\n\n &-outlined {\n color: var(--tone-text);\n border-color: var(--tone-outline);\n }\n\n &-plain {\n color: var(--tone-text);\n }\n }\n\n &-background {\n display: block;\n padding: 0.5em 0.75em;\n }\n\n /* Multiples of the theme's base, not em: an em on font-size resolves against the parent,\n so these would follow whatever the button sits in rather than the theme. */\n &-size {\n &-small {\n font-size: calc(var(--default-font-size) * 0.75);\n }\n\n &-medium {\n font-size: var(--default-font-size);\n }\n\n &-large {\n font-size: calc(var(--default-font-size) * 1.25);\n }\n\n /* Square, padding-free button for a lone icon; the caller sets the box size. */\n &-icon {\n display: inline-flex;\n\n /* Inherits, unlike the other sizes: a hardcoded px value would have to be out-specified\n from another stylesheet, a specificity tie decided only by bundle order. */\n font-size: inherit;\n\n .button-ripple,\n .button-background,\n .button-content {\n display: flex;\n flex: 1;\n align-items: center;\n justify-content: center;\n padding: 0;\n }\n }\n }\n}\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"button.module.mjs","names":[],"sources":["../../../../package/common/button/button.module.pcss"],"sourcesContent":[".button {\n appearance: none;\n -webkit-appearance: none;\n box-sizing: border-box;\n font-size: var(--default-font-size);\n background: transparent;\n border: 1px solid transparent;\n padding: 0;\n font-family: var(--default-font);\n border-radius: var(--default-border-radius);\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n position: relative;\n overflow: hidden;\n min-width: min-content;\n\n /* Hover and press layer in currentcolor, so one rule fits every style. On the root, which a\n stretched button's inner element would not fill; under the Ripple wrapper, which comes later. */\n &::before {\n content: '';\n position: absolute;\n inset: 0;\n background: currentcolor;\n opacity: 0;\n transition: opacity 0.15s;\n pointer-events: none;\n }\n\n &-notAllowed {\n cursor: not-allowed;\n }\n\n /* Disabled only, not loading: a faded spinner would read as disabled rather than working. */\n &-disabled {\n opacity: 0.5;\n }\n\n /* Inside the edge: outside, a ButtonGroup neighbour with the same fill swallows the ring. A\n Filled button draws it in its text colour instead, as Pagination's active item does. */\n &:focus-visible {\n outline: 2px solid var(--focus-border-color, var(--primary-color));\n outline-offset: -4px;\n }\n\n /* (hover: hover) because on touch a tap latches :hover until the next tap elsewhere. */\n @media (hover: hover) {\n &:hover:not(:disabled)::before {\n opacity: 0.08;\n }\n }\n\n /* After the hover rule, which it ties on specificity: a press is a hover too. */\n &:active:not(:disabled)::before {\n opacity: 0.16;\n }\n\n &-content {\n position: relative;\n white-space: break-spaces;\n }\n\n &-loading {\n visibility: hidden;\n\n &-container {\n position: absolute;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n }\n\n &-withIcon {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.5em;\n }\n\n /* A pill, because plain text right after the label reads as part of it. On the label's\n baseline rather than centred, so it follows the label wherever the font puts it. */\n &-shortcut {\n /* Not inline: Chrome trims only block containers, see the @supports below. */\n display: inline-block;\n\n /* Negative margins cancel the vertical padding in layout, so the button is no taller. */\n margin: -0.2em 0 -0.2em 0.6em;\n padding: 0.2em 0.6em;\n border-radius: 99999px;\n background: color-mix(in srgb, currentcolor 15%, transparent);\n font-size: 0.85em;\n\n /* Smaller text on a shared baseline sits lower. Raised so its capitals centre on the label's:\n (1 - 0.85) / 2 x ~0.72 cap height / 0.85. */\n vertical-align: 0.065em;\n white-space: nowrap;\n\n /* Even padding around the letters. Untrimmed, engines round the font's ascent and descent\n differently at each size, and the letters sat up to 2px off-centre. */\n @supports (text-box: trim-both cap alphabetic) {\n margin-block: -0.4em;\n padding-block: 0.4em;\n text-box: trim-both cap alphabetic;\n }\n\n /* A touch screen usually has no keyboard to press it on. */\n @media (pointer: coarse) {\n display: none;\n }\n }\n\n &-style {\n &-filled {\n background: var(--tone-fill);\n\n /* Must follow the shorthand above, which resets background-image. */\n background-image: var(--tone-gradient);\n color: var(--tone-contrast);\n\n /* After the base :focus-visible rule, which it ties on specificity. */\n &:focus-visible {\n outline-color: currentcolor;\n }\n }\n\n &-light {\n background: var(--tone-light);\n color: var(--tone-text);\n }\n\n &-outlined {\n color: var(--tone-text);\n border-color: var(--tone-outline);\n }\n\n &-plain {\n color: var(--tone-text);\n }\n }\n\n &-background {\n display: block;\n padding: 0.5em 0.75em;\n }\n\n /* Multiples of the theme's base, not em: an em on font-size resolves against the parent,\n so these would follow whatever the button sits in rather than the theme. */\n &-size {\n &-small {\n font-size: calc(var(--default-font-size) * 0.75);\n }\n\n &-medium {\n font-size: var(--default-font-size);\n }\n\n &-large {\n font-size: calc(var(--default-font-size) * 1.25);\n }\n\n /* Square, padding-free button for a lone icon; the caller sets the box size. */\n &-icon {\n display: inline-flex;\n\n /* Inherits, unlike the other sizes: a hardcoded px value would have to be out-specified\n from another stylesheet, a specificity tie decided only by bundle order. */\n font-size: inherit;\n\n .button-ripple,\n .button-background,\n .button-content {\n display: flex;\n flex: 1;\n align-items: center;\n justify-content: center;\n padding: 0;\n }\n }\n }\n}\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"button.types.mjs","names":[],"sources":["../../../../package/common/button/button.types.ts"],"sourcesContent":["import type React from 'react';\nimport type { CSSProperties } from 'react';\nimport type { IconContent } from '../types/icon.types';\nimport type { SlotClassNames, SlotStyles, DataProps } from '../types/slots.types';\nimport type { ColorValue } from '../themeProvider/color.types';\n\n/** How much the button stands out. Independent of its colour. */\nexport enum ButtonStyleType {\n /** Solid fill, for the one main action. */\n Filled = 'filled',\n /** A light tint of the colour behind text in the colour. */\n Light = 'light',\n /** Border and text in the colour, no fill. The default. */\n Outlined = 'outlined',\n /** Text in the colour, no fill and no border. For toolbar, icon and close buttons. */\n Plain = 'plain',\n}\n\nexport enum ButtonSize {\n Small = 'small',\n /** The default. */\n Medium = 'medium',\n Large = 'large',\n /** Square, padding-free button for a lone icon. Give it its own width/height. */\n Icon = 'icon',\n}\n\nexport enum ButtonType {\n Button = 'button',\n Reset = 'reset',\n Submit = 'submit',\n}\n\nexport type ButtonSlots = 'background' | 'content' | 'loadingContainer' | 'label';\n\n/**\n * `DataProps` supplies the `data-*` index signature, which `ButtonHTMLAttributes` doesn't cover.\n * `type`, `color` and `onClick` below deliberately narrow their inherited counterparts.\n */\nexport interface ButtonProps extends DataProps, React.ButtonHTMLAttributes<HTMLButtonElement> {\n className?: string;\n style?: CSSProperties;\n type?: ButtonType;\n /** Emphasis: `Filled`, `Light`, `Outlined` (default) or `Plain`. */\n styleType?: ButtonStyleType;\n /** A `ThemeColor` (default `Primary`) or any CSS colour, such as `'var(--brand)'`. */\n color?: ColorValue;\n size?: ButtonSize;\n onClick?: (event: React.MouseEvent<HTMLButtonElement>) => Promise<void> | void;\n children?: React.ReactNode;\n icon?: IconContent;\n isLoading?: boolean;\n disabled?: boolean;\n title?: string;\n ref?: React.Ref<HTMLButtonElement>;\n classNames?: SlotClassNames<ButtonSlots>;\n styles?: SlotStyles<ButtonSlots>;\n}\n"],"mappings":"aAOA,IAAY,EAAL,SAAA,EAAA,OAEL,GAAA,OAAA,SAEA,EAAA,MAAA,QAEA,EAAA,SAAA,WAEA,EAAA,MAAA,SACF,EAAA,CAAA,CAAA,EAEY,EAAL,SAAA,EAAA,OACL,GAAA,MAAA,QAEA,EAAA,OAAA,SACA,EAAA,MAAA,QAEA,EAAA,KAAA,QACF,EAAA,CAAA,CAAA,EAEY,EAAL,SAAA,EAAA,OACL,GAAA,OAAA,SACA,EAAA,MAAA,QACA,EAAA,OAAA,UACF,EAAA,CAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"button.types.mjs","names":[],"sources":["../../../../package/common/button/button.types.ts"],"sourcesContent":["import type React from 'react';\nimport type { CSSProperties } from 'react';\nimport type { IconContent } from '../types/icon.types';\nimport type { SlotClassNames, SlotStyles, DataProps } from '../types/slots.types';\nimport type { ColorValue } from '../themeProvider/color.types';\n\n/** How much the button stands out. Independent of its colour. */\nexport enum ButtonStyleType {\n /** Solid fill, for the one main action. */\n Filled = 'filled',\n /** A light tint of the colour behind text in the colour. */\n Light = 'light',\n /** Border and text in the colour, no fill. The default. */\n Outlined = 'outlined',\n /** Text in the colour, no fill and no border. For toolbar, icon and close buttons. */\n Plain = 'plain',\n}\n\nexport enum ButtonSize {\n Small = 'small',\n /** The default. */\n Medium = 'medium',\n Large = 'large',\n /** Square, padding-free button for a lone icon. Give it its own width/height. */\n Icon = 'icon',\n}\n\nexport enum ButtonType {\n Button = 'button',\n Reset = 'reset',\n Submit = 'submit',\n}\n\nexport type ButtonSlots = 'background' | 'content' | 'loadingContainer' | 'label' | 'shortcut';\n\n/**\n * `DataProps` supplies the `data-*` index signature, which `ButtonHTMLAttributes` doesn't cover.\n * `type`, `color` and `onClick` below deliberately narrow their inherited counterparts.\n */\nexport interface ButtonProps extends DataProps, React.ButtonHTMLAttributes<HTMLButtonElement> {\n className?: string;\n style?: CSSProperties;\n type?: ButtonType;\n /** Emphasis: `Filled`, `Light`, `Outlined` (default) or `Plain`. */\n styleType?: ButtonStyleType;\n /** A `ThemeColor` (default `Primary`) or any CSS colour, such as `'var(--brand)'`. */\n color?: ColorValue;\n size?: ButtonSize;\n onClick?: (event: React.MouseEvent<HTMLButtonElement>) => Promise<void> | void;\n children?: React.ReactNode;\n icon?: IconContent;\n /**\n * A shortcut drawn after the label, in `useHotkeys` spelling (`'mod+s'`): `⌘S` on Apple\n * platforms, `Ctrl+S` elsewhere. Drawn only; bind it with `useHotkeys`.\n */\n shortcut?: string;\n isLoading?: boolean;\n disabled?: boolean;\n title?: string;\n ref?: React.Ref<HTMLButtonElement>;\n classNames?: SlotClassNames<ButtonSlots>;\n styles?: SlotStyles<ButtonSlots>;\n}\n"],"mappings":"aAOA,IAAY,EAAL,SAAA,EAAA,OAEL,GAAA,OAAA,SAEA,EAAA,MAAA,QAEA,EAAA,SAAA,WAEA,EAAA,MAAA,SACF,EAAA,CAAA,CAAA,EAEY,EAAL,SAAA,EAAA,OACL,GAAA,MAAA,QAEA,EAAA,OAAA,SACA,EAAA,MAAA,QAEA,EAAA,KAAA,QACF,EAAA,CAAA,CAAA,EAEY,EAAL,SAAA,EAAA,OACL,GAAA,OAAA,SACA,EAAA,MAAA,QACA,EAAA,OAAA,UACF,EAAA,CAAA,CAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import{ThemeColor as n}from"../themeProvider/color.types.mjs";import{ButtonStyleType as r}from"../button/button.types.mjs";import i from"../actionIcon/actionIcon.mjs";import a from"../hooks/useCoarsePointer.mjs";import{mergeRefs as o}from"../utils/mergeRefs.mjs";import{useAutoGrow as s}from"../hooks/useAutoGrow.mjs";import c from"./chatComposer.module.mjs";import{useRef as l,useState as u}from"react";import d from"classnames";import{faPaperPlane as f,faStop as p}from"@fortawesome/free-solid-svg-icons";import{jsx as m,jsxs as h}from"react/jsx-runtime";var g={placeholder:`Message`,send:`Send`,stop:`Stop`};function _(_){let{onSend:v,value:y,defaultValue:b=``,onChange:x,onKeyDown:S,onStop:C,disabled:w=!1,readOnly:T=!1,rows:E=1,startContent:D,endContent:O,ref:k,labels:A,className:j,style:M,classNames:N,styles:P,...F}=e(`ChatComposer`,_),I=t(`ChatComposer`,A,g),L=a(),R=l(null),[z,B]=u(b),V=y??z,H=!w&&!T&&V.trim()!==``,[U,W]=u(!1);U&&V===``&&W(!1);let G=U&&(D!=null||O!=null);s(R,V,{onGrow:e=>{e&&R.current?.value&&W(!0)}});let K=e=>{y===void 0&&B(e),x?.(e)},q=()=>{H&&!C&&(v(V.trim()),K(``),R.current?.focus())},J=e=>{S?.(e),!(e.defaultPrevented||e.key!==`Enter`||e.shiftKey)&&(e.nativeEvent.isComposing||e.keyCode===229||(!L||e.metaKey||e.ctrlKey)&&(e.preventDefault(),q()))},Y=e=>e.preventDefault();return h(`div`,{className:d(c.chatComposer,G&&c.chatComposerStacked,w&&c.chatComposerDisabled,j),style:M,children:[D!=null&&m(`div`,{className:c.chatComposerSection,children:D}),m(`textarea`,{"aria-label":I.placeholder,...F,ref:o(R,k),rows:E,value:V,placeholder:I.placeholder,disabled:w,readOnly:T,className:d(c.chatComposerInput,N?.input),style:P?.input,onChange:e=>K(e.target.value),onKeyDown:J}),O!=null&&m(`div`,{className:c.chatComposerSection,children:O}),C?m(i,{icon:p,styleType:r.Filled,color:n.Primary,"aria-label":I.stop,title:I.stop,disabled:w,className:d(c.chatComposerButton,N?.button),style:P?.button,onMouseDown:Y,onClick:C}):m(i,{icon:f,styleType:r.Filled,color:n.Primary,"aria-label":I.send,title:I.send,disabled:!H,className:d(c.chatComposerButton,N?.button),style:P?.button,onMouseDown:Y,onClick:q})]})}export{_ as default};
|
|
2
|
+
//# sourceMappingURL=chatComposer.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatComposer.mjs","names":[],"sources":["../../../../package/common/chatComposer/chatComposer.tsx"],"sourcesContent":["import { useRef, useState, type KeyboardEvent, type MouseEvent, type ReactElement } from 'react';\nimport cx from 'classnames';\nimport { faPaperPlane, faStop } from '@fortawesome/free-solid-svg-icons';\n\nimport ActionIcon from '../actionIcon';\nimport { ButtonStyleType } from '../button/button.types';\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { useAutoGrow } from '../hooks/useAutoGrow';\nimport { useCoarsePointer } from '../hooks/useCoarsePointer';\nimport { ThemeColor } from '../themeProvider/color.types';\nimport { mergeRefs } from '../utils/mergeRefs';\nimport type { ChatComposerLabels, ChatComposerProps } from './chatComposer.types';\nimport styles from './chatComposer.module.pcss';\n\nconst DEFAULT_LABELS: Required<ChatComposerLabels> = {\n placeholder: 'Message',\n send: 'Send',\n stop: 'Stop',\n};\n\nfunction ChatComposer(props: ChatComposerProps): ReactElement {\n const {\n onSend,\n value,\n defaultValue = '',\n onChange,\n onKeyDown,\n onStop,\n disabled = false,\n readOnly = false,\n rows = 1,\n startContent,\n endContent,\n ref,\n labels,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('ChatComposer', props);\n const text = useLabels('ChatComposer', labels, DEFAULT_LABELS);\n const isCoarsePointer = useCoarsePointer();\n const inputRef = useRef<HTMLTextAreaElement>(null);\n const [internalValue, setInternalValue] = useState(defaultValue);\n const current = value ?? internalValue;\n const canSend = !disabled && !readOnly && current.trim() !== '';\n const [isGrown, setIsGrown] = useState(false);\n\n // Back to one row only once the field is empty. Below the buttons the field is wider, and text\n // that wrapped beside them may fit on one line there: dropping back then would wrap it again.\n if (isGrown && current === '') setIsGrown(false);\n // With nothing besides the send button, a row of its own would cost more height than it frees.\n const isStacked = isGrown && (startContent != null || endContent != null);\n\n useAutoGrow(inputRef, current, {\n onGrow: (grown) => {\n // An empty field can measure taller than a line: Chrome counts a wrapping placeholder.\n if (grown && inputRef.current?.value) setIsGrown(true);\n },\n });\n\n const setValue = (next: string) => {\n if (value === undefined) setInternalValue(next);\n onChange?.(next);\n };\n\n const send = () => {\n if (!canSend || onStop) return;\n onSend(current.trim());\n setValue('');\n inputRef.current?.focus();\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {\n onKeyDown?.(event);\n // A consumer's own use of Enter comes first, such as picking from an open mention list.\n if (event.defaultPrevented || event.key !== 'Enter' || event.shiftKey) return;\n // The Enter that confirms a word in a Japanese, Chinese or Korean IME. Safari sends its keydown\n // after compositionend, with isComposing already false, and only keyCode 229 gives it away.\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n // On a touch keyboard Enter is the only way to start a new line, so there the button sends.\n if (isCoarsePointer && !event.metaKey && !event.ctrlKey) return;\n event.preventDefault();\n send();\n };\n\n // Keeps the focus in the field when the button is pressed, so a phone keyboard stays open.\n const keepFocus = (event: MouseEvent) => event.preventDefault();\n\n return (\n <div\n className={cx(\n styles.chatComposer,\n isStacked && styles.chatComposerStacked,\n disabled && styles.chatComposerDisabled,\n className,\n )}\n style={style}\n >\n {startContent != null && <div className={styles.chatComposerSection}>{startContent}</div>}\n <textarea\n // Before the spread, so a name of the consumer's own wins over the placeholder.\n aria-label={text.placeholder}\n {...rest}\n ref={mergeRefs(inputRef, ref)}\n rows={rows}\n value={current}\n placeholder={text.placeholder}\n disabled={disabled}\n readOnly={readOnly}\n className={cx(styles.chatComposerInput, classNames?.input)}\n style={slotStyles?.input}\n onChange={(event) => setValue(event.target.value)}\n onKeyDown={handleKeyDown}\n />\n {endContent != null && <div className={styles.chatComposerSection}>{endContent}</div>}\n {onStop ? (\n <ActionIcon\n icon={faStop}\n styleType={ButtonStyleType.Filled}\n color={ThemeColor.Primary}\n aria-label={text.stop}\n title={text.stop}\n disabled={disabled}\n className={cx(styles.chatComposerButton, classNames?.button)}\n style={slotStyles?.button}\n onMouseDown={keepFocus}\n onClick={onStop}\n />\n ) : (\n <ActionIcon\n icon={faPaperPlane}\n styleType={ButtonStyleType.Filled}\n color={ThemeColor.Primary}\n aria-label={text.send}\n title={text.send}\n disabled={!canSend}\n className={cx(styles.chatComposerButton, classNames?.button)}\n style={slotStyles?.button}\n onMouseDown={keepFocus}\n onClick={send}\n />\n )}\n </div>\n );\n}\n\nexport default ChatComposer;\n"],"mappings":"wsBAcA,IAAM,EAA+C,CACnD,YAAa,UACb,KAAM,OACN,KAAM,MACR,EAEA,SAAS,EAAa,EAAwC,CAC5D,GAAM,CACJ,SACA,QACA,eAAe,GACf,WACA,YACA,SACA,WAAW,GACX,WAAW,GACX,OAAO,EACP,eACA,aACA,MACA,SACA,YACA,QACA,aACA,OAAQ,EACR,GAAG,GACD,EAAqB,eAAgB,CAAK,EACxC,EAAO,EAAU,eAAgB,EAAQ,CAAc,EACvD,EAAkB,EAAiB,EACnC,EAAW,EAA4B,IAAI,EAC3C,CAAC,EAAe,GAAoB,EAAS,CAAY,EACzD,EAAU,GAAS,EACnB,EAAU,CAAC,GAAY,CAAC,GAAY,EAAQ,KAAK,IAAM,GACvD,CAAC,EAAS,GAAc,EAAS,EAAK,EAIxC,GAAW,IAAY,IAAI,EAAW,EAAK,EAE/C,IAAM,EAAY,IAAY,GAAgB,MAAQ,GAAc,MAEpE,EAAY,EAAU,EAAS,CAC7B,OAAS,GAAU,CAEb,GAAS,EAAS,SAAS,OAAO,EAAW,EAAI,CACvD,CACF,CAAC,EAED,IAAM,EAAY,GAAiB,CAC7B,IAAU,IAAA,IAAW,EAAiB,CAAI,EAC9C,IAAW,CAAI,CACjB,EAEM,MAAa,CACZ,GAAW,KAChB,EAAO,EAAQ,KAAK,CAAC,EACrB,EAAS,EAAE,EACX,EAAS,SAAS,MAAM,EAC1B,EAEM,EAAiB,GAA8C,CACnE,IAAY,CAAK,EAEb,IAAM,kBAAoB,EAAM,MAAQ,SAAW,EAAM,YAGzD,EAAM,YAAY,aAAe,EAAM,UAAY,MAEnD,IAAoB,EAAM,SAAY,EAAM,WAChD,EAAM,eAAe,EACrB,EAAK,GACP,EAGM,EAAa,GAAsB,EAAM,eAAe,EAE9D,OACE,EAAC,MAAD,CACE,UAAW,EACT,EAAO,aACP,GAAa,EAAO,oBACpB,GAAY,EAAO,qBACnB,CACF,EACO,QAPT,SAAA,CASG,GAAgB,MAAQ,EAAC,MAAD,CAAK,UAAW,EAAO,oBAAsB,SAAA,CAAkB,CAAA,EACxF,EAAC,WAAD,CAEE,aAAY,EAAK,YACjB,GAAI,EACJ,IAAK,EAAU,EAAU,CAAG,EACtB,OACN,MAAO,EACP,YAAa,EAAK,YACR,WACA,WACV,UAAW,EAAG,EAAO,kBAAmB,GAAY,KAAK,EACzD,MAAO,GAAY,MACnB,SAAW,GAAU,EAAS,EAAM,OAAO,KAAK,EAChD,UAAW,CACZ,CAAA,EACA,GAAc,MAAQ,EAAC,MAAD,CAAK,UAAW,EAAO,oBAAsB,SAAA,CAAgB,CAAA,EACnF,EACC,EAAC,EAAD,CACE,KAAM,EACN,UAAW,EAAgB,OAC3B,MAAO,EAAW,QAClB,aAAY,EAAK,KACjB,MAAO,EAAK,KACF,WACV,UAAW,EAAG,EAAO,mBAAoB,GAAY,MAAM,EAC3D,MAAO,GAAY,OACnB,YAAa,EACb,QAAS,CACV,CAAA,EAED,EAAC,EAAD,CACE,KAAM,EACN,UAAW,EAAgB,OAC3B,MAAO,EAAW,QAClB,aAAY,EAAK,KACjB,MAAO,EAAK,KACZ,SAAU,CAAC,EACX,UAAW,EAAG,EAAO,mBAAoB,GAAY,MAAM,EAC3D,MAAO,GAAY,OACnB,YAAa,EACb,QAAS,CACV,CAAA,CAEA,GAET"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";var e=`chatComposer_JUIOk`,t=`chatComposer-stacked_At2fJ`,n=`chatComposer-disabled_S2nwO`,r=`chatComposer-section_-H4W4`,i=`chatComposer-input_eCZ2d`,a=`chatComposer-button_O6siA`,o={chatComposer:e,"chatComposer-stacked":`chatComposer-stacked_At2fJ`,chatComposerStacked:t,"chatComposer-disabled":`chatComposer-disabled_S2nwO`,chatComposerDisabled:n,"chatComposer-section":`chatComposer-section_-H4W4`,chatComposerSection:r,"chatComposer-input":`chatComposer-input_eCZ2d`,chatComposerInput:i,"chatComposer-button":`chatComposer-button_O6siA`,chatComposerButton:a};export{e as chatComposer,a as chatComposerButton,n as chatComposerDisabled,i as chatComposerInput,r as chatComposerSection,t as chatComposerStacked,o as default};
|
|
2
|
+
//# sourceMappingURL=chatComposer.module.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatComposer.module.mjs","names":[],"sources":["../../../../package/common/chatComposer/chatComposer.module.pcss"],"sourcesContent":[".chatComposer {\n display: flex;\n align-items: flex-end;\n gap: 0.25em;\n box-sizing: border-box;\n padding: 0.375em;\n border: 1px solid var(--border-color);\n border-radius: calc(var(--default-border-radius) * 1.5);\n background: var(--input-background);\n color: var(--text-color);\n\n &:focus-within {\n border-color: var(--focus-border-color, var(--primary-color));\n }\n\n /* Once the text needs a second line, the field takes the whole width and the buttons a row\n below it: beside it, a few of them would leave it too narrow to write in. Only the order\n changes, never the element, so the focus and an IME composition survive the switch. */\n &-stacked {\n flex-wrap: wrap;\n }\n\n &-disabled {\n background-color: var(--disabled-input-color);\n }\n\n /* As tall as one line of the field, so a smaller button centres on the first line rather than\n sitting on the field's bottom edge. */\n &-section {\n display: flex;\n flex: none;\n align-items: center;\n gap: 0.25em;\n min-height: 2.25em;\n }\n\n &-input {\n flex: 1 1 auto;\n min-width: 0;\n box-sizing: border-box;\n\n /* Seven lines of line-height 1.5, plus the padding. */\n max-height: 11.25em;\n margin: 0;\n padding: 0.375em 0.5em;\n border: 0;\n outline: none;\n background: transparent;\n color: inherit;\n font: inherit;\n line-height: 1.5;\n resize: none;\n overflow-y: auto;\n\n &::placeholder {\n color: var(--text-dark);\n }\n\n &:disabled {\n color: var(--disabled-font-color);\n -webkit-text-fill-color: var(--disabled-font-color);\n }\n }\n}\n\n.chatComposer-stacked .chatComposer-input {\n flex-basis: 100%;\n order: -1;\n}\n\n/* The end content, or the send button when there is none, and with it everything after: to the\n far end of the row below. Two classes, as it can be the ActionIcon. */\n.chatComposer-stacked .chatComposer-input + * {\n margin-inline-start: auto;\n}\n\n/* Two classes: it is an ActionIcon, whose own one-class rules it would otherwise tie with. As tall\n as one line of the field, line-height plus padding, so the two line up while it is empty. */\n.chatComposer .chatComposer-button {\n flex: none;\n width: 2.25em;\n height: 2.25em;\n border-radius: 50%;\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import{ThemeColor as n}from"../themeProvider/color.types.mjs";import{toneProps as r,useToneCheck as i}from"../styles/tone.mjs";import{ChatMessageStatus as a,ChatMessageStyleType as o}from"./chatMessage.types.mjs";import s from"./chatMessage.module.mjs";import c from"classnames";import{FontAwesomeIcon as l}from"@fortawesome/react-fontawesome";import{faCheck as u,faCheckDouble as d,faCircleExclamation as f,faClock as p}from"@fortawesome/free-solid-svg-icons";import{jsx as m,jsxs as h}from"react/jsx-runtime";var g={sending:`Sending`,sent:`Sent`,delivered:`Delivered`,read:`Read`,failed:`Not sent`,you:`You`},_={[a.Sending]:p,[a.Sent]:u,[a.Delivered]:d,[a.Read]:d},v={[o.Filled]:s.chatMessageBubbleFilled,[o.Light]:s.chatMessageBubbleLight,[o.Plain]:s.chatMessageBubblePlain},y=e=>e!=null&&typeof e!=`boolean`&&e!==``;function b(u){let{children:d,isOwn:p=!1,isContinued:b=!1,avatar:x,author:S,time:C,status:w,actions:T,styleType:E=p?o.Filled:o.Light,color:D=p?n.Primary:n.Neutral,labels:O,className:k,style:A,classNames:j,styles:M,...N}=e(`ChatMessage`,u),P=t(`ChatMessage`,O,g),F=r(D);i(`ChatMessage`,[D]);let I=w===a.Failed,L=w==null||I?null:_[w],R=y(S)?S:p?P.you:null;return h(`div`,{...N,className:c(s.chatMessage,p&&s.chatMessageOwn,b&&s.chatMessageContinued,E===o.Plain&&s.chatMessagePlain,k),style:A,children:[y(x)&&m(`div`,{className:c(s.chatMessageAvatar,j?.avatar),style:M?.avatar,children:x}),h(`div`,{className:c(s.chatMessageBody,j?.body),style:M?.body,children:[R!=null&&m(`div`,{className:c(s.chatMessageAuthor,(b||!y(S))&&s.chatMessageAuthorHidden,j?.author),style:M?.author,children:R}),h(`div`,{className:c(s.chatMessageBubble,F.className,v[E],j?.bubble),style:{...F.style,...M?.bubble},children:[m(`div`,{className:c(s.chatMessageContent,typeof d==`string`&&s.chatMessageContentText),children:d}),(y(C)||L)&&h(`span`,{className:c(s.chatMessageMeta,j?.meta),style:M?.meta,children:[C,w!=null&&L&&m(`span`,{role:`img`,"aria-label":P[w],className:c(s.chatMessageStatus,w===a.Read&&s.chatMessageStatusRead),children:m(l,{icon:L})})]})]}),(I||y(T))&&h(`div`,{className:c(s.chatMessageActions,j?.actions),style:M?.actions,children:[I&&h(`span`,{className:s.chatMessageFailed,children:[m(l,{icon:f,className:s.chatMessageFailedIcon}),P.failed]}),T]})]})]})}export{b as default};
|
|
2
|
+
//# sourceMappingURL=chatMessage.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatMessage.mjs","names":[],"sources":["../../../../package/common/chatMessage/chatMessage.tsx"],"sourcesContent":["import type { ReactElement, ReactNode } from 'react';\nimport cx from 'classnames';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faCheck, faCheckDouble, faCircleExclamation, faClock } from '@fortawesome/free-solid-svg-icons';\nimport type { IconDefinition } from '@fortawesome/fontawesome-svg-core';\n\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { ThemeColor } from '../themeProvider/color.types';\nimport { toneProps, useToneCheck } from '../styles/tone';\nimport { ChatMessageStatus, ChatMessageStyleType } from './chatMessage.types';\nimport type { ChatMessageLabels, ChatMessageProps } from './chatMessage.types';\nimport styles from './chatMessage.module.pcss';\n\nconst DEFAULT_LABELS: Required<ChatMessageLabels> = {\n sending: 'Sending',\n sent: 'Sent',\n delivered: 'Delivered',\n read: 'Read',\n failed: 'Not sent',\n you: 'You',\n};\n\n// Failed has no icon in the bubble: it is a notice under it, where it has the page's background\n// to stand out against rather than the bubble's own colour.\nconst ICON_BY_STATUS: Record<Exclude<ChatMessageStatus, ChatMessageStatus.Failed>, IconDefinition> = {\n [ChatMessageStatus.Sending]: faClock,\n [ChatMessageStatus.Sent]: faCheck,\n [ChatMessageStatus.Delivered]: faCheckDouble,\n [ChatMessageStatus.Read]: faCheckDouble,\n};\n\nconst CLASS_BY_STYLE: Record<ChatMessageStyleType, string> = {\n [ChatMessageStyleType.Filled]: styles.chatMessageBubbleFilled,\n [ChatMessageStyleType.Light]: styles.chatMessageBubbleLight,\n [ChatMessageStyleType.Plain]: styles.chatMessageBubblePlain,\n};\n\n// So `actions={isFailed && <RetryButton />}` leaves no empty row behind when it is false.\nconst isShown = (node: ReactNode) => node != null && typeof node !== 'boolean' && node !== '';\n\nfunction ChatMessage(props: ChatMessageProps): ReactElement {\n const {\n children,\n isOwn = false,\n isContinued = false,\n avatar,\n author,\n time,\n status,\n actions,\n styleType = isOwn ? ChatMessageStyleType.Filled : ChatMessageStyleType.Light,\n color = isOwn ? ThemeColor.Primary : ThemeColor.Neutral,\n labels,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('ChatMessage', props);\n const text = useLabels('ChatMessage', labels, DEFAULT_LABELS);\n const tone = toneProps(color);\n useToneCheck('ChatMessage', [color]);\n const isFailed = status === ChatMessageStatus.Failed;\n const statusIcon = status == null || isFailed ? null : ICON_BY_STATUS[status];\n // Alignment is what tells an own message apart, and that is not read out.\n const sender = isShown(author) ? author : isOwn ? text.you : null;\n\n return (\n <div\n {...rest}\n className={cx(\n styles.chatMessage,\n isOwn && styles.chatMessageOwn,\n isContinued && styles.chatMessageContinued,\n styleType === ChatMessageStyleType.Plain && styles.chatMessagePlain,\n className,\n )}\n style={style}\n >\n {isShown(avatar) && (\n <div className={cx(styles.chatMessageAvatar, classNames?.avatar)} style={slotStyles?.avatar}>\n {avatar}\n </div>\n )}\n <div className={cx(styles.chatMessageBody, classNames?.body)} style={slotStyles?.body}>\n {sender != null && (\n <div\n className={cx(\n styles.chatMessageAuthor,\n (isContinued || !isShown(author)) && styles.chatMessageAuthorHidden,\n classNames?.author,\n )}\n style={slotStyles?.author}\n >\n {sender}\n </div>\n )}\n <div\n className={cx(styles.chatMessageBubble, tone.className, CLASS_BY_STYLE[styleType], classNames?.bubble)}\n style={{ ...tone.style, ...slotStyles?.bubble }}\n >\n <div className={cx(styles.chatMessageContent, typeof children === 'string' && styles.chatMessageContentText)}>\n {children}\n </div>\n {(isShown(time) || statusIcon) && (\n <span className={cx(styles.chatMessageMeta, classNames?.meta)} style={slotStyles?.meta}>\n {time}\n {status != null && statusIcon && (\n <span\n role='img'\n aria-label={text[status]}\n className={cx(\n styles.chatMessageStatus,\n status === ChatMessageStatus.Read && styles.chatMessageStatusRead,\n )}\n >\n <FontAwesomeIcon icon={statusIcon} />\n </span>\n )}\n </span>\n )}\n </div>\n {(isFailed || isShown(actions)) && (\n <div className={cx(styles.chatMessageActions, classNames?.actions)} style={slotStyles?.actions}>\n {isFailed && (\n <span className={styles.chatMessageFailed}>\n <FontAwesomeIcon icon={faCircleExclamation} className={styles.chatMessageFailedIcon} />\n {text.failed}\n </span>\n )}\n {actions}\n </div>\n )}\n </div>\n </div>\n );\n}\n\nexport default ChatMessage;\n"],"mappings":"0pBAaA,IAAM,EAA8C,CAClD,QAAS,UACT,KAAM,OACN,UAAW,YACX,KAAM,OACN,OAAQ,WACR,IAAK,KACP,EAIM,EAA+F,EAClG,EAAkB,SAAU,GAC5B,EAAkB,MAAO,GACzB,EAAkB,WAAY,GAC9B,EAAkB,MAAO,CAC5B,EAEM,EAAuD,EAC1D,EAAqB,QAAS,EAAO,yBACrC,EAAqB,OAAQ,EAAO,wBACpC,EAAqB,OAAQ,EAAO,sBACvC,EAGM,EAAW,GAAoB,GAAQ,MAAQ,OAAO,GAAS,WAAa,IAAS,GAE3F,SAAS,EAAY,EAAuC,CAC1D,GAAM,CACJ,WACA,QAAQ,GACR,cAAc,GACd,SACA,SACA,OACA,SACA,UACA,YAAY,EAAQ,EAAqB,OAAS,EAAqB,MACvE,QAAQ,EAAQ,EAAW,QAAU,EAAW,QAChD,SACA,YACA,QACA,aACA,OAAQ,EACR,GAAG,GACD,EAAqB,cAAe,CAAK,EACvC,EAAO,EAAU,cAAe,EAAQ,CAAc,EACtD,EAAO,EAAU,CAAK,EAC5B,EAAa,cAAe,CAAC,CAAK,CAAC,EACnC,IAAM,EAAW,IAAW,EAAkB,OACxC,EAAa,GAAU,MAAQ,EAAW,KAAO,EAAe,GAEhE,EAAS,EAAQ,CAAM,EAAI,EAAS,EAAQ,EAAK,IAAM,KAE7D,OACE,EAAC,MAAD,CACE,GAAI,EACJ,UAAW,EACT,EAAO,YACP,GAAS,EAAO,eAChB,GAAe,EAAO,qBACtB,IAAc,EAAqB,OAAS,EAAO,iBACnD,CACF,EACO,QATT,SAAA,CAWG,EAAQ,CAAM,GACb,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,kBAAmB,GAAY,MAAM,EAAG,MAAO,GAAY,OAClF,SAAA,CACE,CAAA,EAEP,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,gBAAiB,GAAY,IAAI,EAAG,MAAO,GAAY,KAAjF,SAAA,CACG,GAAU,MACT,EAAC,MAAD,CACE,UAAW,EACT,EAAO,mBACN,GAAe,CAAC,EAAQ,CAAM,IAAM,EAAO,wBAC5C,GAAY,MACd,EACA,MAAO,GAAY,OAElB,SAAA,CACE,CAAA,EAEP,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,kBAAmB,EAAK,UAAW,EAAe,GAAY,GAAY,MAAM,EACrG,MAAO,CAAE,GAAG,EAAK,MAAO,GAAG,GAAY,MAAO,EAFhD,SAAA,CAIE,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,mBAAoB,OAAO,GAAa,UAAY,EAAO,sBAAsB,EACxG,UACE,CAAA,GACH,EAAQ,CAAI,GAAK,IACjB,EAAC,OAAD,CAAM,UAAW,EAAG,EAAO,gBAAiB,GAAY,IAAI,EAAG,MAAO,GAAY,KAAlF,SAAA,CACG,EACA,GAAU,MAAQ,GACjB,EAAC,OAAD,CACE,KAAK,MACL,aAAY,EAAK,GACjB,UAAW,EACT,EAAO,kBACP,IAAW,EAAkB,MAAQ,EAAO,qBAC9C,EAEA,SAAA,EAAC,EAAD,CAAiB,KAAM,CAAa,CAAA,CAChC,CAAA,CAEJ,CAEL,CAAA,CAAA,KACH,GAAY,EAAQ,CAAO,IAC3B,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,mBAAoB,GAAY,OAAO,EAAG,MAAO,GAAY,QAAvF,SAAA,CACG,GACC,EAAC,OAAD,CAAM,UAAW,EAAO,kBAAxB,SAAA,CACE,EAAC,EAAD,CAAiB,KAAM,EAAqB,UAAW,EAAO,qBAAwB,CAAA,EACrF,EAAK,MACF,CAEP,CAAA,EAAA,CACE,GAEJ,CACF,CAAA,CAAA,GAET"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";var e=`chatMessage_Q23vT`,t=`chatMessage-own_R-kPP`,n=`chatMessage-continued_NdGHe`,r=`chatMessage-avatar_KIgAU`,i=`chatMessage-body_h4IE4`,a=`chatMessage-author_TTIka`,o=`chatMessage-author-hidden_UdBUe`,s=`chatMessage-bubble_sngY-`,c=`chatMessage-bubble-filled_lJPjg`,l=`chatMessage-bubble-light_B136D`,u=`chatMessage-bubble-plain_Piy3I`,d=`chatMessage-content_ZlKd3`,f=`chatMessage-content-text_Yu8Xq`,p=`chatMessage-meta_mZXpW`,m=`chatMessage-status_JKUn1`,h=`chatMessage-status-read_tIIiI`,g=`chatMessage-actions_fDFS2`,_=`chatMessage-failed_855q3`,v=`chatMessage-failed-icon_d-5hO`,y=`chatMessage-plain_DCwiq`,b={chatMessage:e,"chatMessage-own":`chatMessage-own_R-kPP`,chatMessageOwn:t,"chatMessage-continued":`chatMessage-continued_NdGHe`,chatMessageContinued:n,"chatMessage-avatar":`chatMessage-avatar_KIgAU`,chatMessageAvatar:r,"chatMessage-body":`chatMessage-body_h4IE4`,chatMessageBody:i,"chatMessage-author":`chatMessage-author_TTIka`,chatMessageAuthor:a,"chatMessage-author-hidden":`chatMessage-author-hidden_UdBUe`,chatMessageAuthorHidden:o,"chatMessage-bubble":`chatMessage-bubble_sngY-`,chatMessageBubble:s,"chatMessage-bubble-filled":`chatMessage-bubble-filled_lJPjg`,chatMessageBubbleFilled:c,"chatMessage-bubble-light":`chatMessage-bubble-light_B136D`,chatMessageBubbleLight:l,"chatMessage-bubble-plain":`chatMessage-bubble-plain_Piy3I`,chatMessageBubblePlain:u,"chatMessage-content":`chatMessage-content_ZlKd3`,chatMessageContent:d,"chatMessage-content-text":`chatMessage-content-text_Yu8Xq`,chatMessageContentText:f,"chatMessage-meta":`chatMessage-meta_mZXpW`,chatMessageMeta:p,"chatMessage-status":`chatMessage-status_JKUn1`,chatMessageStatus:m,"chatMessage-status-read":`chatMessage-status-read_tIIiI`,chatMessageStatusRead:h,"chatMessage-actions":`chatMessage-actions_fDFS2`,chatMessageActions:g,"chatMessage-failed":`chatMessage-failed_855q3`,chatMessageFailed:_,"chatMessage-failed-icon":`chatMessage-failed-icon_d-5hO`,chatMessageFailedIcon:v,"chatMessage-plain":`chatMessage-plain_DCwiq`,chatMessagePlain:y};export{e as chatMessage,g as chatMessageActions,a as chatMessageAuthor,o as chatMessageAuthorHidden,r as chatMessageAvatar,i as chatMessageBody,s as chatMessageBubble,c as chatMessageBubbleFilled,l as chatMessageBubbleLight,u as chatMessageBubblePlain,d as chatMessageContent,f as chatMessageContentText,n as chatMessageContinued,_ as chatMessageFailed,v as chatMessageFailedIcon,p as chatMessageMeta,t as chatMessageOwn,y as chatMessagePlain,m as chatMessageStatus,h as chatMessageStatusRead,b as default};
|
|
2
|
+
//# sourceMappingURL=chatMessage.module.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatMessage.module.mjs","names":[],"sources":["../../../../package/common/chatMessage/chatMessage.module.pcss"],"sourcesContent":[".chatMessage {\n position: relative;\n display: flex;\n align-items: flex-start;\n gap: 0.5em;\n margin-top: 0.75em;\n\n &:first-child {\n margin-top: 0;\n }\n\n &-own {\n flex-direction: row-reverse;\n }\n\n &-continued {\n margin-top: 0.125em;\n }\n\n &-avatar {\n flex: none;\n display: flex;\n }\n\n &-body {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n min-width: 0;\n max-width: min(80%, 40em);\n }\n\n &-author {\n margin-bottom: 0.25em;\n padding-inline: 0.75em;\n color: var(--text-dark);\n font-size: var(--text-small-font-size);\n font-weight: 600;\n\n /* Out of view but read out. Pinned to the message, or its containing block could be the page\n and leave empty space to scroll through below it. */\n &-hidden {\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n height: 1px;\n margin: 0;\n padding: 0;\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n }\n }\n\n /* Wraps rather than stacks: a short message keeps its meta on the same line, a long one pushes\n it onto a line of its own at the end. */\n &-bubble {\n display: flex;\n flex-wrap: wrap;\n align-items: flex-end;\n column-gap: 0.5em;\n box-sizing: border-box;\n max-width: 100%;\n padding: 0.5em 0.75em;\n border-radius: calc(var(--default-border-radius) * 1.5);\n border-start-start-radius: 0.25em;\n overflow-wrap: anywhere;\n\n &-filled {\n background: var(--tone-fill);\n\n /* Must follow the shorthand above, which resets background-image. */\n background-image: var(--tone-gradient);\n color: var(--tone-contrast);\n }\n\n /* Text keeps the page colour, as on a Light Card: a paragraph in the tone would not read. */\n &-light {\n background: var(--tone-light);\n color: var(--text-color);\n }\n\n &-plain {\n padding: 0;\n border-radius: 0;\n color: var(--text-color);\n }\n }\n\n &-content {\n flex: 1 1 auto;\n min-width: 0;\n\n &-text {\n white-space: pre-wrap;\n }\n }\n\n &-meta {\n display: inline-flex;\n align-items: center;\n gap: 0.25em;\n margin-inline-start: auto;\n color: color-mix(in srgb, currentcolor 70%, transparent);\n font-size: 0.75em;\n white-space: nowrap;\n }\n\n /* Read stands out by brightness as well as colour, which alone would not reach everyone, and on\n a filled bubble in a blue theme reaches no one. */\n &-status {\n display: inline-flex;\n opacity: 0.7;\n\n &-read {\n color: var(--info-color);\n opacity: 1;\n }\n }\n\n &-actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 0.25em;\n margin-top: 0.25em;\n }\n\n /* The text in the page's own colour: the default dark theme's red reaches 2.1:1 on its\n background, too little to read. The text carries the meaning, the red icon the signal. */\n &-failed {\n display: inline-flex;\n align-items: center;\n gap: 0.375em;\n padding-inline: 0.25em;\n color: var(--text-color);\n font-size: var(--text-small-font-size);\n\n &-icon {\n color: var(--error-color);\n }\n }\n}\n\n.chatMessage-own .chatMessage-body {\n align-items: flex-end;\n}\n\n.chatMessage-own .chatMessage-bubble {\n border-start-start-radius: calc(var(--default-border-radius) * 1.5);\n border-start-end-radius: 0.25em;\n}\n\n/* The corner pointing at the author marks the first message of a run, so the rest round it off.\n After the own rule, which it ties with. */\n.chatMessage-continued .chatMessage-bubble {\n border-start-start-radius: calc(var(--default-border-radius) * 1.5);\n border-start-end-radius: calc(var(--default-border-radius) * 1.5);\n}\n\n/* Keeps the column, so a run of messages stays aligned under the avatar of its first. */\n.chatMessage-continued .chatMessage-avatar {\n visibility: hidden;\n}\n\n.chatMessage-plain .chatMessage-body {\n flex: 1 1 auto;\n align-items: stretch;\n max-width: none;\n}\n\n.chatMessage-plain .chatMessage-author {\n padding-inline: 0;\n}\n\n/* The info blue would sink into a filled bubble. */\n.chatMessage-bubble-filled .chatMessage-status-read {\n color: var(--tone-contrast);\n}\n"],"mappings":""}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";var e=function(e){return e.Filled=`filled`,e.Light=`light`,e.Plain=`plain`,e}({}),t=function(e){return e.Sending=`sending`,e.Sent=`sent`,e.Delivered=`delivered`,e.Read=`read`,e.Failed=`failed`,e}({});export{t as ChatMessageStatus,e as ChatMessageStyleType};
|
|
2
|
+
//# sourceMappingURL=chatMessage.types.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatMessage.types.mjs","names":[],"sources":["../../../../package/common/chatMessage/chatMessage.types.ts"],"sourcesContent":["import type { CSSProperties, ReactNode } from 'react';\nimport type { SlotClassNames, SlotStyles, HtmlProps } from '../types/slots.types';\nimport type { ColorValue } from '../themeProvider/color.types';\n\n/** How much the message stands out. Independent of its colour. */\nexport enum ChatMessageStyleType {\n /** Solid bubble, text in a contrasting colour. The default for `isOwn`. */\n Filled = 'filled',\n /** A light tint of the colour. The default for everyone else. */\n Light = 'light',\n /** No bubble, full width: an AI assistant's answer. */\n Plain = 'plain',\n}\n\n/** Delivery state of an own message: an icon next to `time`, and `Failed` a notice under the bubble. */\nexport enum ChatMessageStatus {\n Sending = 'sending',\n Sent = 'sent',\n Delivered = 'delivered',\n Read = 'read',\n Failed = 'failed',\n}\n\nexport type ChatMessageSlots = 'avatar' | 'body' | 'author' | 'bubble' | 'meta' | 'actions';\n\n/** Text ChatMessage renders of its own, for translating it. */\nexport interface ChatMessageLabels {\n /** Read out for the status icon. */\n sending?: string;\n sent?: string;\n delivered?: string;\n read?: string;\n /** Shown under the bubble. */\n failed?: string;\n /** Names an own message without `author` for screen readers. */\n you?: string;\n}\n\nexport interface ChatMessageProps extends HtmlProps<HTMLDivElement> {\n /** The message. A string keeps its line breaks; anything else (rendered Markdown, an image) renders as given. */\n children?: ReactNode;\n /** Sent by the current user: aligned to the end, `Filled` in `Primary` by default. */\n isOwn?: boolean;\n /** Follows a message from the same author: hides `author` from view, keeps the space of `avatar` and tightens the gap above. */\n isContinued?: boolean;\n /** Shown beside the bubble, typically an `Avatar`. */\n avatar?: ReactNode;\n /** Name above the bubble. Screen readers get it on every message of a run, not only the first. */\n author?: ReactNode;\n /** Shown in the bubble's corner. Format it yourself, e.g. with `Intl.DateTimeFormat`. */\n time?: ReactNode;\n status?: ChatMessageStatus;\n /** Shown below the bubble, after the `Failed` notice: copy, retry, rate. */\n actions?: ReactNode;\n /** Default `Filled` when `isOwn`, otherwise `Light`. */\n styleType?: ChatMessageStyleType;\n /** A `ThemeColor` or any CSS colour. Default `Primary` when `isOwn`, otherwise `Neutral`. */\n color?: ColorValue;\n /** Overrides the text ChatMessage renders of its own. Merged per key. */\n labels?: ChatMessageLabels;\n className?: string;\n style?: CSSProperties;\n classNames?: SlotClassNames<ChatMessageSlots>;\n styles?: SlotStyles<ChatMessageSlots>;\n}\n"],"mappings":"aAKA,IAAY,EAAL,SAAA,EAAA,OAEL,GAAA,OAAA,SAEA,EAAA,MAAA,QAEA,EAAA,MAAA,SACF,EAAA,CAAA,CAAA,EAGY,EAAL,SAAA,EAAA,OACL,GAAA,QAAA,UACA,EAAA,KAAA,OACA,EAAA,UAAA,YACA,EAAA,KAAA,OACA,EAAA,OAAA,UACF,EAAA,CAAA,CAAA"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{useLabels as t}from"../configProvider/useLabels.mjs";import{ThemeColor as n}from"../themeProvider/color.types.mjs";import r from"../loader/loader.mjs";import{ButtonStyleType as i}from"../button/button.types.mjs";import a from"../actionIcon/actionIcon.mjs";import{useInfiniteScroll as o}from"../infiniteBlock/useInfiniteScroll.mjs";import s from"./chatMessageList.module.mjs";import{useCallback as c,useImperativeHandle as l,useLayoutEffect as u,useRef as d,useState as f}from"react";import p from"classnames";import{faArrowDown as m}from"@fortawesome/free-solid-svg-icons";import{jsx as h,jsxs as g}from"react/jsx-runtime";var _={messages:`Messages`,scrollToBottom:`Jump to latest`},v=32,y=()=>Promise.resolve();function b(e,t,n){let r=e.getBoundingClientRect().top,i=t.children,a=+(i[0]===n),o=i.length-1,s=null;for(;a<=o;){let e=a+o>>1;i[e].getBoundingClientRect().bottom>r?(s=i[e],o=e-1):a=e+1}return s&&{element:s,top:s.getBoundingClientRect().top-r}}function x(){return typeof window.matchMedia==`function`&&window.matchMedia(`(prefers-reduced-motion: reduce)`).matches}function S(S){let{children:C,onLoadMore:w,ref:T,labels:E,className:D,style:O,classNames:k,styles:A,...j}=e(`ChatMessageList`,S),M=t(`ChatMessageList`,E,_),N=d(null),P=d(null),F=d(!0),I=d(0),L=d(null),R=d(null),[z,B]=f(!1),[V,H]=f(!1),{isLoading:U,sentinelRef:W}=o(N,w??y,!w),G=()=>{let e=N.current,t=P.current;if(!e||!t)return;let{scrollTop:n,scrollHeight:r,clientHeight:i}=e,a=r-n-i;n<I.current&&a>1?F.current=!1:a<=v&&(F.current=!0),I.current=n,B(!F.current),F.current&&H(!1),L.current=b(e,t,W.current)};u(()=>{let e=N.current,t=P.current;if(!e||!t)return;let n=()=>{let n=t.lastElementChild;if(!F.current&&n!==R.current&&H(!0),R.current=n,F.current)e.scrollTop=e.scrollHeight;else{let t=L.current;t?.element.isConnected&&(e.scrollTop+=t.element.getBoundingClientRect().top-e.getBoundingClientRect().top-t.top)}I.current=e.scrollTop,L.current=b(e,t,W.current)};n();let r=new ResizeObserver(n);return r.observe(e),r.observe(t),()=>r.disconnect()},[W]);let K=c(()=>{let e=N.current;e&&(F.current=!0,B(!1),H(!1),e.scrollTo({top:e.scrollHeight,behavior:x()?`auto`:`smooth`}))},[]);return l(T,()=>({scrollToBottom:K}),[K]),g(`div`,{...j,className:p(s.chatMessageList,D),style:O,children:[h(`div`,{ref:N,role:`log`,"aria-label":M.messages,"aria-busy":U||void 0,tabIndex:0,className:p(s.chatMessageListViewport,k?.viewport),style:A?.viewport,onScroll:G,children:g(`div`,{ref:P,className:p(s.chatMessageListContent,k?.content),style:A?.content,children:[w&&h(`div`,{ref:W,className:s.chatMessageListLoader,children:U&&h(r,{size:`1.5em`})}),C]})}),z&&h(a,{icon:m,styleType:V?i.Filled:i.Plain,color:V?n.Primary:n.Neutral,"aria-label":M.scrollToBottom,title:M.scrollToBottom,className:p(s.chatMessageListScrollButton,!V&&s.chatMessageListScrollButtonSurface,k?.scrollButton),style:A?.scrollButton,onClick:K})]})}export{S as default};
|
|
2
|
+
//# sourceMappingURL=chatMessageList.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatMessageList.mjs","names":[],"sources":["../../../../package/common/chatMessageList/chatMessageList.tsx"],"sourcesContent":["import { useCallback, useImperativeHandle, useLayoutEffect, useRef, useState, type ReactElement } from 'react';\nimport cx from 'classnames';\nimport { faArrowDown } from '@fortawesome/free-solid-svg-icons';\n\nimport ActionIcon from '../actionIcon';\nimport { ButtonStyleType } from '../button/button.types';\nimport Loader from '../loader';\nimport { useComponentDefaults, useLabels } from '../configProvider';\nimport { useInfiniteScroll } from '../infiniteBlock/useInfiniteScroll';\nimport { ThemeColor } from '../themeProvider/color.types';\nimport type { ChatMessageListLabels, ChatMessageListProps } from './chatMessageList.types';\nimport styles from './chatMessageList.module.pcss';\n\nconst DEFAULT_LABELS: Required<ChatMessageListLabels> = {\n messages: 'Messages',\n scrollToBottom: 'Jump to latest',\n};\n\n/** How close to the bottom a user scrolling down gets before new messages are followed again. */\nconst STICK_DISTANCE = 32;\n\nconst NOTHING_TO_LOAD = () => Promise.resolve();\n\ninterface Anchor {\n element: Element;\n /** Distance from the viewport's top edge. */\n top: number;\n}\n\n/**\n * The first message still showing at the top of the viewport, found by bisection because a long\n * conversation has thousands of children and this runs on every scroll event.\n */\nfunction findAnchor(viewport: HTMLElement, content: HTMLElement, skip: Element | null): Anchor | null {\n const viewportTop = viewport.getBoundingClientRect().top;\n const items = content.children;\n let low = items[0] === skip ? 1 : 0;\n let high = items.length - 1;\n let found: Element | null = null;\n while (low <= high) {\n const middle = (low + high) >> 1;\n if (items[middle].getBoundingClientRect().bottom > viewportTop) {\n found = items[middle];\n high = middle - 1;\n } else {\n low = middle + 1;\n }\n }\n return found && { element: found, top: found.getBoundingClientRect().top - viewportTop };\n}\n\nfunction prefersReducedMotion(): boolean {\n return typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\nfunction ChatMessageList(props: ChatMessageListProps): ReactElement {\n const {\n children,\n onLoadMore,\n ref,\n labels,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('ChatMessageList', props);\n const text = useLabels('ChatMessageList', labels, DEFAULT_LABELS);\n const viewportRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const isStuckRef = useRef(true);\n const lastScrollTopRef = useRef(0);\n const anchorRef = useRef<Anchor | null>(null);\n const lastChildRef = useRef<Element | null>(null);\n const [isAway, setIsAway] = useState(false);\n const [hasNew, setHasNew] = useState(false);\n const { isLoading, sentinelRef } = useInfiniteScroll(viewportRef, onLoadMore ?? NOTHING_TO_LOAD, !onLoadMore);\n\n const handleScroll = () => {\n const viewport = viewportRef.current;\n const content = contentRef.current;\n if (!viewport || !content) return;\n const { scrollTop, scrollHeight, clientHeight } = viewport;\n const distance = scrollHeight - scrollTop - clientHeight;\n // Any move up lets go, however small: a slow drag would otherwise be pulled back down by each\n // line of a streaming answer. The browser clamping scrollTop as content shrinks also moves it\n // up, but lands on the bottom, which is why the distance has to be checked too.\n if (scrollTop < lastScrollTopRef.current && distance > 1) isStuckRef.current = false;\n else if (distance <= STICK_DISTANCE) isStuckRef.current = true;\n lastScrollTopRef.current = scrollTop;\n setIsAway(!isStuckRef.current);\n if (isStuckRef.current) setHasNew(false);\n anchorRef.current = findAnchor(viewport, content, sentinelRef.current);\n };\n\n // Scroll anchoring by hand: Safari has no `overflow-anchor`, and where it exists it does nothing\n // at scrollTop 0, which is exactly where older messages get prepended. Runs in a ResizeObserver\n // callback, after layout and before paint, so neither the jump nor its correction is ever seen.\n useLayoutEffect(() => {\n const viewport = viewportRef.current;\n const content = contentRef.current;\n if (!viewport || !content) return undefined;\n\n const follow = () => {\n // A new last child is a message arriving; one prepended at the top or growing as it streams\n // leaves it the same.\n const last = content.lastElementChild;\n if (!isStuckRef.current && last !== lastChildRef.current) setHasNew(true);\n lastChildRef.current = last;\n if (isStuckRef.current) {\n viewport.scrollTop = viewport.scrollHeight;\n } else {\n const anchor = anchorRef.current;\n if (anchor?.element.isConnected) {\n viewport.scrollTop +=\n anchor.element.getBoundingClientRect().top - viewport.getBoundingClientRect().top - anchor.top;\n }\n }\n // So the scroll event this causes is not taken for the user scrolling up.\n lastScrollTopRef.current = viewport.scrollTop;\n anchorRef.current = findAnchor(viewport, content, sentinelRef.current);\n };\n\n follow();\n // The viewport too: a composer growing or a phone keyboard opening shrinks it from below.\n const observer = new ResizeObserver(follow);\n observer.observe(viewport);\n observer.observe(content);\n return () => observer.disconnect();\n }, [sentinelRef]);\n\n const scrollToBottom = useCallback(() => {\n const viewport = viewportRef.current;\n if (!viewport) return;\n isStuckRef.current = true;\n setIsAway(false);\n setHasNew(false);\n viewport.scrollTo({ top: viewport.scrollHeight, behavior: prefersReducedMotion() ? 'auto' : 'smooth' });\n }, []);\n\n useImperativeHandle(ref, () => ({ scrollToBottom }), [scrollToBottom]);\n\n return (\n <div {...rest} className={cx(styles.chatMessageList, className)} style={style}>\n <div\n ref={viewportRef}\n role='log'\n aria-label={text.messages}\n aria-busy={isLoading || undefined}\n // A stop of its own, so the history can be scrolled from the keyboard between messages\n // that hold nothing focusable.\n tabIndex={0}\n className={cx(styles.chatMessageListViewport, classNames?.viewport)}\n style={slotStyles?.viewport}\n onScroll={handleScroll}\n >\n <div\n ref={contentRef}\n className={cx(styles.chatMessageListContent, classNames?.content)}\n style={slotStyles?.content}\n >\n {onLoadMore && (\n <div ref={sentinelRef} className={styles.chatMessageListLoader}>\n {isLoading && <Loader size='1.5em' />}\n </div>\n )}\n {children}\n </div>\n </div>\n {isAway && (\n <ActionIcon\n icon={faArrowDown}\n styleType={hasNew ? ButtonStyleType.Filled : ButtonStyleType.Plain}\n color={hasNew ? ThemeColor.Primary : ThemeColor.Neutral}\n aria-label={text.scrollToBottom}\n title={text.scrollToBottom}\n className={cx(\n styles.chatMessageListScrollButton,\n !hasNew && styles.chatMessageListScrollButtonSurface,\n classNames?.scrollButton,\n )}\n style={slotStyles?.scrollButton}\n onClick={scrollToBottom}\n />\n )}\n </div>\n );\n}\n\nexport default ChatMessageList;\n"],"mappings":"qtBAaA,IAAM,EAAkD,CACtD,SAAU,WACV,eAAgB,gBAClB,EAGM,EAAiB,GAEjB,MAAwB,QAAQ,QAAQ,EAY9C,SAAS,EAAW,EAAuB,EAAsB,EAAqC,CACpG,IAAM,EAAc,EAAS,sBAAsB,CAAC,CAAC,IAC/C,EAAQ,EAAQ,SAClB,EAAM,IAAM,KAAO,GACnB,EAAO,EAAM,OAAS,EACtB,EAAwB,KAC5B,KAAO,GAAO,GAAM,CAClB,IAAM,EAAU,EAAM,GAAS,EAC3B,EAAM,EAAO,CAAC,sBAAsB,CAAC,CAAC,OAAS,GACjD,EAAQ,EAAM,GACd,EAAO,EAAS,GAEhB,EAAM,EAAS,CAEnB,CACA,OAAO,GAAS,CAAE,QAAS,EAAO,IAAK,EAAM,sBAAsB,CAAC,CAAC,IAAM,CAAY,CACzF,CAEA,SAAS,GAAgC,CACvC,OAAO,OAAO,OAAO,YAAe,YAAc,OAAO,WAAW,kCAAkC,CAAC,CAAC,OAC1G,CAEA,SAAS,EAAgB,EAA2C,CAClE,GAAM,CACJ,WACA,aACA,MACA,SACA,YACA,QACA,aACA,OAAQ,EACR,GAAG,GACD,EAAqB,kBAAmB,CAAK,EAC3C,EAAO,EAAU,kBAAmB,EAAQ,CAAc,EAC1D,EAAc,EAAuB,IAAI,EACzC,EAAa,EAAuB,IAAI,EACxC,EAAa,EAAO,EAAI,EACxB,EAAmB,EAAO,CAAC,EAC3B,EAAY,EAAsB,IAAI,EACtC,EAAe,EAAuB,IAAI,EAC1C,CAAC,EAAQ,GAAa,EAAS,EAAK,EACpC,CAAC,EAAQ,GAAa,EAAS,EAAK,EACpC,CAAE,YAAW,eAAgB,EAAkB,EAAa,GAAc,EAAiB,CAAC,CAAU,EAEtG,MAAqB,CACzB,IAAM,EAAW,EAAY,QACvB,EAAU,EAAW,QAC3B,GAAI,CAAC,GAAY,CAAC,EAAS,OAC3B,GAAM,CAAE,YAAW,eAAc,gBAAiB,EAC5C,EAAW,EAAe,EAAY,EAIxC,EAAY,EAAiB,SAAW,EAAW,EAAG,EAAW,QAAU,GACtE,GAAY,IAAgB,EAAW,QAAU,IAC1D,EAAiB,QAAU,EAC3B,EAAU,CAAC,EAAW,OAAO,EACzB,EAAW,SAAS,EAAU,EAAK,EACvC,EAAU,QAAU,EAAW,EAAU,EAAS,EAAY,OAAO,CACvE,EAKA,MAAsB,CACpB,IAAM,EAAW,EAAY,QACvB,EAAU,EAAW,QAC3B,GAAI,CAAC,GAAY,CAAC,EAAS,OAE3B,IAAM,MAAe,CAGnB,IAAM,EAAO,EAAQ,iBAGrB,GAFI,CAAC,EAAW,SAAW,IAAS,EAAa,SAAS,EAAU,EAAI,EACxE,EAAa,QAAU,EACnB,EAAW,QACb,EAAS,UAAY,EAAS,iBACzB,CACL,IAAM,EAAS,EAAU,QACrB,GAAQ,QAAQ,cAClB,EAAS,WACP,EAAO,QAAQ,sBAAsB,CAAC,CAAC,IAAM,EAAS,sBAAsB,CAAC,CAAC,IAAM,EAAO,IAEjG,CAEA,EAAiB,QAAU,EAAS,UACpC,EAAU,QAAU,EAAW,EAAU,EAAS,EAAY,OAAO,CACvE,EAEA,EAAO,EAEP,IAAM,EAAW,IAAI,eAAe,CAAM,EAG1C,OAFA,EAAS,QAAQ,CAAQ,EACzB,EAAS,QAAQ,CAAO,MACX,EAAS,WAAW,CACnC,EAAG,CAAC,CAAW,CAAC,EAEhB,IAAM,EAAiB,MAAkB,CACvC,IAAM,EAAW,EAAY,QACxB,IACL,EAAW,QAAU,GACrB,EAAU,EAAK,EACf,EAAU,EAAK,EACf,EAAS,SAAS,CAAE,IAAK,EAAS,aAAc,SAAU,EAAqB,EAAI,OAAS,QAAS,CAAC,EACxG,EAAG,CAAC,CAAC,EAIL,OAFA,EAAoB,OAAY,CAAE,gBAAe,GAAI,CAAC,CAAc,CAAC,EAGnE,EAAC,MAAD,CAAK,GAAI,EAAM,UAAW,EAAG,EAAO,gBAAiB,CAAS,EAAU,QAAxE,SAAA,CACE,EAAC,MAAD,CACE,IAAK,EACL,KAAK,MACL,aAAY,EAAK,SACjB,YAAW,GAAa,IAAA,GAGxB,SAAU,EACV,UAAW,EAAG,EAAO,wBAAyB,GAAY,QAAQ,EAClE,MAAO,GAAY,SACnB,SAAU,EAEV,SAAA,EAAC,MAAD,CACE,IAAK,EACL,UAAW,EAAG,EAAO,uBAAwB,GAAY,OAAO,EAChE,MAAO,GAAY,QAHrB,SAAA,CAKG,GACC,EAAC,MAAD,CAAK,IAAK,EAAa,UAAW,EAAO,sBACtC,SAAA,GAAa,EAAC,EAAD,CAAQ,KAAK,OAAS,CAAA,CACjC,CAAA,EAEN,CACE,GACF,CAAA,EACJ,GACC,EAAC,EAAD,CACE,KAAM,EACN,UAAW,EAAS,EAAgB,OAAS,EAAgB,MAC7D,MAAO,EAAS,EAAW,QAAU,EAAW,QAChD,aAAY,EAAK,eACjB,MAAO,EAAK,eACZ,UAAW,EACT,EAAO,4BACP,CAAC,GAAU,EAAO,mCAClB,GAAY,YACd,EACA,MAAO,GAAY,aACnB,QAAS,CACV,CAAA,CAEA,GAET"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";var e=`chatMessageList_u0P5F`,t=`chatMessageList-viewport_tgg1O`,n=`chatMessageList-content_ZAQDK`,r=`chatMessageList-loader_fWDBv`,i=`chatMessageList-scrollButton_jxrC8`,a=`chatMessageList-scrollButton-surface_28YFu`,o={chatMessageList:e,"chatMessageList-viewport":`chatMessageList-viewport_tgg1O`,chatMessageListViewport:t,"chatMessageList-content":`chatMessageList-content_ZAQDK`,chatMessageListContent:n,"chatMessageList-loader":`chatMessageList-loader_fWDBv`,chatMessageListLoader:r,"chatMessageList-scrollButton":`chatMessageList-scrollButton_jxrC8`,chatMessageListScrollButton:i,"chatMessageList-scrollButton-surface":`chatMessageList-scrollButton-surface_28YFu`,chatMessageListScrollButtonSurface:a};export{e as chatMessageList,n as chatMessageListContent,r as chatMessageListLoader,i as chatMessageListScrollButton,a as chatMessageListScrollButtonSurface,t as chatMessageListViewport,o as default};
|
|
2
|
+
//# sourceMappingURL=chatMessageList.module.mjs.map
|