@konce-pt/react 0.11.2 → 0.14.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/README.md +128 -64
- package/dist/accordion/accordion.d.ts +14 -6
- package/dist/accordion/accordion.js +25 -17
- package/dist/accordion/llms.txt +22 -10
- package/dist/alert/alert.d.ts +2 -2
- package/dist/alert/alert.js +3 -2
- package/dist/alert/llms.txt +10 -9
- package/dist/app-shell/app-shell.d.ts +14 -14
- package/dist/app-shell/app-shell.js +22 -15
- package/dist/app-shell/llms.txt +16 -15
- package/dist/auth/auth-types.d.ts +6 -6
- package/dist/auth/auth.d.ts +2 -2
- package/dist/auth/auth.js +24 -15
- package/dist/auth/llms.txt +12 -11
- package/dist/autocomplete/autocomplete.d.ts +19 -6
- package/dist/autocomplete/autocomplete.js +32 -11
- package/dist/autocomplete/llms.txt +19 -5
- package/dist/avatar/avatar.d.ts +6 -6
- package/dist/avatar/avatar.js +6 -5
- package/dist/avatar/llms.txt +10 -10
- package/dist/avatar-group/avatar-group.d.ts +4 -4
- package/dist/avatar-group/avatar-group.js +4 -3
- package/dist/avatar-group/llms.txt +8 -8
- package/dist/backdrop/backdrop.d.ts +7 -26
- package/dist/backdrop/backdrop.js +32 -27
- package/dist/backdrop/llms.txt +27 -27
- package/dist/badge/badge.d.ts +3 -3
- package/dist/badge/badge.js +1 -1
- package/dist/badge/llms.txt +11 -11
- package/dist/bottom-sheet/bottom-sheet.d.ts +8 -3
- package/dist/bottom-sheet/bottom-sheet.js +5 -4
- package/dist/bottom-sheet/llms.txt +11 -8
- package/dist/breadcrumb/breadcrumb.d.ts +8 -2
- package/dist/breadcrumb/breadcrumb.js +11 -3
- package/dist/breadcrumb/llms.txt +10 -2
- package/dist/button/button.d.ts +3 -3
- package/dist/button/button.js +10 -9
- package/dist/button/llms.txt +27 -19
- package/dist/button-group/button-group.d.ts +18 -5
- package/dist/button-group/button-group.js +54 -6
- package/dist/button-group/llms.txt +27 -18
- package/dist/card/card.d.ts +8 -8
- package/dist/card/card.js +10 -9
- package/dist/card/llms.txt +20 -20
- package/dist/carousel/carousel.d.ts +6 -6
- package/dist/carousel/carousel.js +8 -5
- package/dist/carousel/llms.txt +10 -10
- package/dist/charts/chart-legend.d.ts +3 -3
- package/dist/charts/chart-legend.js +2 -1
- package/dist/charts/chart-tooltip.d.ts +3 -3
- package/dist/charts/chart-tooltip.js +1 -1
- package/dist/charts/chart.d.ts +4 -3
- package/dist/charts/chart.js +18 -10
- package/dist/charts/llms.txt +10 -7
- package/dist/charts-entry/public-api.js +2 -2
- package/dist/chat/chat.d.ts +17 -10
- package/dist/chat/chat.js +77 -23
- package/dist/chat/llms.txt +39 -15
- package/dist/chat-composer/chat-composer.d.ts +20 -7
- package/dist/chat-composer/chat-composer.js +29 -7
- package/dist/chat-composer/llms.txt +20 -11
- package/dist/chat-dock/chat-dock.d.ts +11 -4
- package/dist/chat-dock/chat-dock.js +10 -6
- package/dist/chat-dock/llms.txt +15 -14
- package/dist/chat-entry/public-api.js +1 -1
- package/dist/chat-message/chat-message.d.ts +12 -5
- package/dist/chat-message/chat-message.js +19 -8
- package/dist/chat-message/llms.txt +16 -15
- package/dist/chat-typing/chat-typing.d.ts +2 -2
- package/dist/chat-typing/chat-typing.js +4 -3
- package/dist/chat-typing/llms.txt +6 -6
- package/dist/checkbox/checkbox.d.ts +13 -6
- package/dist/checkbox/checkbox.js +31 -5
- package/dist/checkbox/llms.txt +26 -11
- package/dist/chip/chip.d.ts +4 -4
- package/dist/chip/chip.js +3 -2
- package/dist/chip/llms.txt +11 -11
- package/dist/chips-input/chips-input.d.ts +6 -4
- package/dist/chips-input/chips-input.js +47 -12
- package/dist/chips-input/llms.txt +18 -8
- package/dist/clock/clock.d.ts +11 -6
- package/dist/clock/clock.js +38 -8
- package/dist/clock/llms.txt +12 -8
- package/dist/color-picker/color-picker.d.ts +9 -5
- package/dist/color-picker/color-picker.js +37 -18
- package/dist/color-picker/llms.txt +8 -5
- package/dist/confirm/confirm-container.d.ts +3 -3
- package/dist/confirm/confirm-container.js +6 -5
- package/dist/confirm/confirm-store.d.ts +4 -4
- package/dist/confirm/confirm-store.js +11 -5
- package/dist/confirm/llms.txt +8 -7
- package/dist/context-menu/context-menu.d.ts +1 -1
- package/dist/context-menu/context-menu.js +37 -14
- package/dist/context-menu/llms.txt +10 -7
- package/dist/data-table/data-table-types.d.ts +5 -5
- package/dist/data-table/data-table.d.ts +37 -5
- package/dist/data-table/data-table.js +426 -119
- package/dist/data-table/export-csv.d.ts +3 -3
- package/dist/data-table/export-csv.js +8 -10
- package/dist/data-table/llms.txt +127 -79
- package/dist/data-view/data-view.d.ts +13 -5
- package/dist/data-view/data-view.js +68 -6
- package/dist/data-view/llms.txt +29 -10
- package/dist/date-range/date-range.d.ts +20 -8
- package/dist/date-range/date-range.js +86 -63
- package/dist/date-range/llms.txt +32 -18
- package/dist/datepicker/datepicker.d.ts +22 -8
- package/dist/datepicker/datepicker.js +103 -71
- package/dist/datepicker/llms.txt +36 -22
- package/dist/dialog/dialog.d.ts +4 -4
- package/dist/dialog/dialog.js +32 -11
- package/dist/dialog/llms.txt +13 -10
- package/dist/divider/divider.d.ts +7 -1
- package/dist/divider/divider.js +3 -2
- package/dist/divider/llms.txt +4 -3
- package/dist/drawer/drawer.d.ts +9 -4
- package/dist/drawer/drawer.js +5 -4
- package/dist/drawer/llms.txt +13 -10
- package/dist/empty/empty.d.ts +2 -2
- package/dist/empty/empty.js +1 -1
- package/dist/empty/llms.txt +9 -9
- package/dist/fab/fab.d.ts +1 -1
- package/dist/fab/fab.js +1 -1
- package/dist/fab/llms.txt +10 -6
- package/dist/fieldset/fieldset.d.ts +18 -4
- package/dist/fieldset/fieldset.js +12 -6
- package/dist/fieldset/llms.txt +26 -9
- package/dist/file-upload/file-upload.d.ts +8 -2
- package/dist/file-upload/file-upload.js +22 -11
- package/dist/file-upload/llms.txt +12 -9
- package/dist/form-field/form-field-context.d.ts +4 -4
- package/dist/form-field/form-field.d.ts +12 -4
- package/dist/form-field/form-field.js +27 -6
- package/dist/form-field/llms.txt +25 -11
- package/dist/galleria/galleria.d.ts +3 -3
- package/dist/galleria/galleria.js +9 -6
- package/dist/galleria/llms.txt +7 -7
- package/dist/grid/col.d.ts +4 -4
- package/dist/grid/col.js +2 -2
- package/dist/grid/flex.d.ts +3 -3
- package/dist/grid/flex.js +1 -1
- package/dist/grid/grid.d.ts +7 -7
- package/dist/grid/grid.js +3 -3
- package/dist/grid/layout.d.ts +2 -2
- package/dist/grid/layout.js +2 -2
- package/dist/grid/llms.txt +15 -15
- package/dist/grid-entry/public-api.js +1 -1
- package/dist/i18n/i18n-context.d.ts +6 -6
- package/dist/i18n/i18n-context.js +27 -5
- package/dist/icon/icon-registry.d.ts +3 -3
- package/dist/icon/icon-registry.js +3 -3
- package/dist/icon/icon.d.ts +4 -4
- package/dist/icon/icon.js +10 -7
- package/dist/icon/llms.txt +13 -13
- package/dist/icon-button/icon-button.d.ts +2 -2
- package/dist/icon-button/icon-button.js +1 -1
- package/dist/icon-button/llms.txt +9 -5
- package/dist/icons-entry/public-api.d.ts +2 -2
- package/dist/icons-entry/public-api.js +3 -3
- package/dist/image/image.js +5 -5
- package/dist/image/llms.txt +13 -13
- package/dist/input/input.d.ts +18 -5
- package/dist/input/input.js +22 -6
- package/dist/input/llms.txt +25 -11
- package/dist/input-mask/input-mask.d.ts +7 -2
- package/dist/input-mask/input-mask.js +17 -43
- package/dist/input-mask/llms.txt +8 -5
- package/dist/input-number/input-number.d.ts +11 -9
- package/dist/input-number/input-number.js +25 -21
- package/dist/input-number/llms.txt +17 -14
- package/dist/input-otp/input-otp.d.ts +18 -5
- package/dist/input-otp/input-otp.js +48 -24
- package/dist/input-otp/llms.txt +18 -7
- package/dist/internal/calendar-focus.d.ts +1 -1
- package/dist/internal/calendar-focus.js +3 -3
- package/dist/internal/cn.d.ts +1 -1
- package/dist/internal/mask.d.ts +14 -0
- package/dist/internal/mask.js +62 -0
- package/dist/internal/number-group.d.ts +22 -0
- package/dist/internal/number-group.js +53 -0
- package/dist/internal/option-key.d.ts +6 -0
- package/dist/internal/option-key.js +8 -0
- package/dist/internal/option-nav.d.ts +4 -4
- package/dist/internal/option-nav.js +5 -5
- package/dist/internal/overlay-container.js +1 -1
- package/dist/internal/overlay.d.ts +3 -3
- package/dist/internal/overlay.js +40 -10
- package/dist/internal/password-strength.d.ts +6 -0
- package/dist/internal/password-strength.js +74 -0
- package/dist/internal/roving.d.ts +2 -2
- package/dist/internal/roving.js +3 -3
- package/dist/internal/scroll-lock.d.ts +6 -0
- package/dist/internal/scroll-lock.js +35 -0
- package/dist/internal/slot-children.d.ts +13 -0
- package/dist/internal/slot-children.js +31 -0
- package/dist/internal/slot.d.ts +3 -3
- package/dist/internal/slot.js +2 -2
- package/dist/internal/step.d.ts +12 -0
- package/dist/internal/step.js +31 -0
- package/dist/internal/tween.d.ts +5 -5
- package/dist/internal/tween.js +7 -8
- package/dist/internal/types.d.ts +2 -2
- package/dist/internal/use-breakpoint.d.ts +2 -2
- package/dist/internal/use-breakpoint.js +4 -4
- package/dist/internal/use-controllable-state.d.ts +5 -0
- package/dist/internal/use-controllable-state.js +25 -4
- package/dist/internal/use-id.d.ts +2 -2
- package/dist/internal/use-id.js +2 -2
- package/dist/internal/use-modal-focus.d.ts +2 -1
- package/dist/internal/use-modal-focus.js +14 -3
- package/dist/knob/knob.d.ts +7 -4
- package/dist/knob/knob.js +27 -22
- package/dist/knob/llms.txt +18 -11
- package/dist/listbox/listbox.d.ts +8 -6
- package/dist/listbox/listbox.js +37 -17
- package/dist/listbox/llms.txt +12 -9
- package/dist/loader/llms.txt +56 -0
- package/dist/loader/loader.d.ts +49 -0
- package/dist/loader/loader.js +68 -0
- package/dist/map/llms.txt +30 -23
- package/dist/map/map-create-dialog.d.ts +4 -4
- package/dist/map/map-create-dialog.js +36 -28
- package/dist/map/map-hours-field.d.ts +4 -4
- package/dist/map/map-hours-field.js +8 -7
- package/dist/map/map-image-field.d.ts +2 -2
- package/dist/map/map-image-field.js +6 -5
- package/dist/map/map-legend.d.ts +4 -4
- package/dist/map/map-legend.js +3 -2
- package/dist/map/map-poi-card.d.ts +3 -3
- package/dist/map/map-poi-card.js +10 -9
- package/dist/map/map-search.d.ts +17 -6
- package/dist/map/map-search.js +70 -43
- package/dist/map/map.d.ts +25 -13
- package/dist/map/map.js +86 -57
- package/dist/map/ports.d.ts +9 -9
- package/dist/map/ports.js +6 -6
- package/dist/map-entry/public-api.js +2 -2
- package/dist/megamenu/llms.txt +18 -7
- package/dist/megamenu/megamenu.d.ts +12 -6
- package/dist/megamenu/megamenu.js +23 -14
- package/dist/mention/llms.txt +15 -15
- package/dist/mention/mention.d.ts +5 -5
- package/dist/mention/mention.js +2 -2
- package/dist/mention/mentions.d.ts +1 -1
- package/dist/mention/mentions.js +1 -1
- package/dist/menu/llms.txt +12 -12
- package/dist/menu/menu-item.d.ts +4 -4
- package/dist/menu/menu-keys.d.ts +2 -2
- package/dist/menu/menu-keys.js +5 -5
- package/dist/menu/menu-rows.js +1 -0
- package/dist/menu/menu.d.ts +2 -2
- package/dist/menu/menu.js +15 -8
- package/dist/menubar/llms.txt +19 -8
- package/dist/menubar/menubar.d.ts +11 -5
- package/dist/menubar/menubar.js +23 -11
- package/dist/meter-group/llms.txt +12 -12
- package/dist/meter-group/meter-group.d.ts +2 -2
- package/dist/meter-group/meter-group.js +2 -1
- package/dist/motion/motion-context.d.ts +22 -0
- package/dist/motion/motion-context.js +28 -0
- package/dist/order-list/llms.txt +24 -9
- package/dist/order-list/order-list.d.ts +12 -4
- package/dist/order-list/order-list.js +123 -25
- package/dist/paginator/llms.txt +8 -8
- package/dist/paginator/paginator.js +7 -4
- package/dist/panel/llms.txt +25 -13
- package/dist/panel/panel.d.ts +11 -3
- package/dist/panel/panel.js +12 -7
- package/dist/password/llms.txt +19 -7
- package/dist/password/password.d.ts +2 -2
- package/dist/password/password.js +27 -16
- package/dist/peek/llms.txt +11 -11
- package/dist/peek/peek.d.ts +6 -6
- package/dist/peek/peek.js +41 -19
- package/dist/pick-list/llms.txt +35 -12
- package/dist/pick-list/pick-list.d.ts +12 -4
- package/dist/pick-list/pick-list.js +128 -24
- package/dist/popover/llms.txt +13 -10
- package/dist/popover/popover.d.ts +6 -4
- package/dist/popover/popover.js +14 -7
- package/dist/progress/llms.txt +17 -17
- package/dist/progress/progress.d.ts +7 -7
- package/dist/progress/progress.js +7 -6
- package/dist/public-api.d.ts +16 -4
- package/dist/public-api.js +13 -7
- package/dist/radio-group/llms.txt +16 -6
- package/dist/radio-group/radio-group.d.ts +12 -3
- package/dist/radio-group/radio-group.js +23 -4
- package/dist/rating/llms.txt +20 -13
- package/dist/rating/rating-pointer.d.ts +16 -0
- package/dist/rating/rating-pointer.js +26 -0
- package/dist/rating/rating.d.ts +6 -6
- package/dist/rating/rating.js +77 -12
- package/dist/rich-text/lists.d.ts +2 -2
- package/dist/rich-text/lists.js +7 -7
- package/dist/rich-text/llms.txt +35 -12
- package/dist/rich-text/rich-text.d.ts +13 -14
- package/dist/rich-text/rich-text.js +311 -142
- package/dist/rich-text/sanitize.d.ts +11 -2
- package/dist/rich-text/sanitize.js +80 -32
- package/dist/rich-text/tables.d.ts +3 -3
- package/dist/rich-text/tables.js +3 -3
- package/dist/roadmap/llms.txt +23 -17
- package/dist/roadmap/roadmap.d.ts +6 -6
- package/dist/roadmap/roadmap.js +102 -25
- package/dist/roadmap-entry/public-api.js +2 -2
- package/dist/scroll-top/llms.txt +6 -6
- package/dist/scroll-top/scroll-top.d.ts +1 -1
- package/dist/scroll-top/scroll-top.js +2 -1
- package/dist/select/llms.txt +22 -12
- package/dist/select/select.d.ts +10 -8
- package/dist/select/select.js +87 -47
- package/dist/shared/types.d.ts +22 -1
- package/dist/shared/types.js +9 -1
- package/dist/sidenav/llms.txt +9 -9
- package/dist/sidenav/sidenav.d.ts +2 -3
- package/dist/sidenav/sidenav.js +5 -4
- package/dist/skeleton/llms.txt +3 -3
- package/dist/skeleton/skeleton.d.ts +1 -1
- package/dist/skeleton/skeleton.js +1 -1
- package/dist/slider/llms.txt +34 -12
- package/dist/slider/slider-drag.d.ts +26 -0
- package/dist/slider/slider-drag.js +79 -0
- package/dist/slider/slider.d.ts +8 -5
- package/dist/slider/slider.js +38 -8
- package/dist/speech/llms.txt +97 -0
- package/dist/speech/speech-button.d.ts +44 -0
- package/dist/speech/speech-button.js +86 -0
- package/dist/speech/speech-wave.d.ts +19 -0
- package/dist/speech/speech-wave.js +16 -0
- package/dist/speech/use-speech-recognition.d.ts +81 -0
- package/dist/speech/use-speech-recognition.js +179 -0
- package/dist/speed-dial/llms.txt +14 -8
- package/dist/speed-dial/speed-dial.d.ts +1 -1
- package/dist/speed-dial/speed-dial.js +3 -2
- package/dist/spinner/llms.txt +7 -7
- package/dist/spinner/spinner.d.ts +1 -1
- package/dist/spinner/spinner.js +2 -1
- package/dist/split-button/llms.txt +21 -15
- package/dist/split-button/split-button.d.ts +2 -2
- package/dist/split-button/split-button.js +13 -6
- package/dist/splitter/llms.txt +11 -11
- package/dist/splitter/splitter.d.ts +3 -3
- package/dist/splitter/splitter.js +25 -10
- package/dist/stepper/llms.txt +35 -13
- package/dist/stepper/stepper.d.ts +6 -6
- package/dist/stepper/stepper.js +60 -13
- package/dist/switch/llms.txt +15 -5
- package/dist/switch/switch.d.ts +10 -3
- package/dist/switch/switch.js +18 -4
- package/dist/switch-group/llms.txt +11 -8
- package/dist/switch-group/switch-group.d.ts +6 -4
- package/dist/switch-group/switch-group.js +9 -7
- package/dist/tabs/llms.txt +12 -9
- package/dist/tabs/tabs.d.ts +5 -5
- package/dist/tabs/tabs.js +36 -20
- package/dist/textarea/llms.txt +23 -12
- package/dist/textarea/textarea.d.ts +19 -6
- package/dist/textarea/textarea.js +22 -7
- package/dist/timeline/llms.txt +4 -4
- package/dist/timeline/timeline.d.ts +1 -1
- package/dist/timeline/timeline.js +1 -1
- package/dist/toast/llms.txt +50 -17
- package/dist/toast/toast-container.d.ts +9 -3
- package/dist/toast/toast-container.js +18 -9
- package/dist/toast/toast-store.d.ts +42 -2
- package/dist/toast/toast-store.js +36 -1
- package/dist/toolbar/llms.txt +7 -7
- package/dist/toolbar/toolbar.d.ts +3 -3
- package/dist/toolbar/toolbar.js +2 -2
- package/dist/tooltip/llms.txt +14 -12
- package/dist/tooltip/tooltip.d.ts +5 -4
- package/dist/tooltip/tooltip.js +100 -41
- package/dist/tree/llms.txt +25 -11
- package/dist/tree/tree-keys.d.ts +5 -5
- package/dist/tree/tree-keys.js +7 -7
- package/dist/tree/tree.d.ts +17 -5
- package/dist/tree/tree.js +62 -20
- package/dist/update/llms.txt +54 -0
- package/dist/update/update.d.ts +49 -0
- package/dist/update/update.js +82 -0
- package/package.json +18 -15
|
@@ -1,21 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
3
|
import { useRef, useState, } from 'react';
|
|
4
|
+
import { KPT_CHAT_TYPING_THROTTLE_MS } from '@konce-pt/chat';
|
|
3
5
|
import { cn } from "../internal/cn.js";
|
|
4
6
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
7
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
6
8
|
import { KptIcon } from "../icon/icon.js";
|
|
7
9
|
import { KptIconButton } from "../icon-button/icon-button.js";
|
|
10
|
+
import { KptSpeechButton } from "../speech/speech-button.js";
|
|
11
|
+
import { useKptSpeechRecognition } from "../speech/use-speech-recognition.js";
|
|
8
12
|
/**
|
|
9
|
-
* Pole pisania wiadomości
|
|
13
|
+
* Pole pisania wiadomości - autorosnące, z załącznikami i paskiem cytatu.
|
|
10
14
|
*
|
|
11
15
|
* `Enter` wysyła, `Shift+Enter` łamie linię (`enterToSend={false}` odwraca układ).
|
|
12
|
-
* Przy `offline` przycisk zmienia etykietę na kolejkowanie
|
|
16
|
+
* Przy `offline` przycisk zmienia etykietę na kolejkowanie - wiadomość dalej da się napisać
|
|
13
17
|
* i oddać aplikacji, bo to ona decyduje, co zrobić z kolejką.
|
|
14
18
|
*
|
|
15
19
|
* @example
|
|
16
20
|
* <KptChatComposer allowAttachments onSend={(payload) => send(payload)} />
|
|
17
21
|
*/
|
|
18
|
-
export function KptChatComposer({ value, defaultValue = '', onValueChange, placeholder = '', disabled = false, offline = false, allowAttachments = false, maxRows = 6, enterToSend = true, replyTo, defaultReplyTo = null, onReplyToChange, onSend, onTyping, locale, dictionary, className, ...rest }) {
|
|
22
|
+
export function KptChatComposer({ value, defaultValue = '', onValueChange, placeholder = '', disabled = false, offline = false, allowAttachments = false, maxRows = 6, enterToSend = true, speech = false, speechLang, speechColor, replyTo, defaultReplyTo = null, onReplyToChange, onSend, onTyping, locale, dictionary, className, ...rest }) {
|
|
19
23
|
const { t } = useKptMessages({ locale, dictionary });
|
|
20
24
|
const [text, setText] = useControllableState({
|
|
21
25
|
value,
|
|
@@ -30,7 +34,13 @@ export function KptChatComposer({ value, defaultValue = '', onValueChange, place
|
|
|
30
34
|
const [files, setFiles] = useState([]);
|
|
31
35
|
const fieldRef = useRef(null);
|
|
32
36
|
const pickerRef = useRef(null);
|
|
33
|
-
|
|
37
|
+
// Dyktowanie: tekst zatwierdzony wchodzi przez `onChange` pola - rośnie ono i zgłasza "piszę"
|
|
38
|
+
// jak przy klawiaturze. Bez `speech` kontroler nie tworzy sesji.
|
|
39
|
+
const mic = useKptSpeechRecognition({ enabled: speech, target: fieldRef, lang: speechLang, disabled });
|
|
40
|
+
/** Kiedy ostatnio zgłoszono "piszę" - dławienie `onTyping`. */
|
|
41
|
+
const lastTyping = useRef(0);
|
|
42
|
+
// W trakcie dyktowania słowa mogą być jeszcze tylko tymczasowe - wysyłka i tak je zatwierdzi.
|
|
43
|
+
const canSend = !disabled && (text.trim().length > 0 || files.length > 0 || mic.listening);
|
|
34
44
|
const sendLabel = offline ? t('chat.queue') : t('chat.send');
|
|
35
45
|
/**
|
|
36
46
|
* Wysokość liczona od treści. Zerowanie przed odczytem `scrollHeight` jest konieczne:
|
|
@@ -48,7 +58,14 @@ export function KptChatComposer({ value, defaultValue = '', onValueChange, place
|
|
|
48
58
|
const submit = () => {
|
|
49
59
|
if (!canSend)
|
|
50
60
|
return;
|
|
51
|
-
|
|
61
|
+
// Tekst tymczasowy dyktowania widać w polu, ale nie ma go w stanie - `flush()` zatwierdza go
|
|
62
|
+
// synchronicznie przez zdarzenie `input`, więc DOM ma go od razu, a stan dopiero po renderze.
|
|
63
|
+
// Dlatego treść do wysyłki czytamy z pola: wysyła się dokładnie to, co widać.
|
|
64
|
+
mic.flush();
|
|
65
|
+
const current = (fieldRef.current?.value ?? text).trim();
|
|
66
|
+
if (!current && !files.length)
|
|
67
|
+
return;
|
|
68
|
+
onSend?.({ text: current, files, replyTo: quote });
|
|
52
69
|
setText('');
|
|
53
70
|
setFiles([]);
|
|
54
71
|
setQuote(null);
|
|
@@ -76,6 +93,11 @@ export function KptChatComposer({ value, defaultValue = '', onValueChange, place
|
|
|
76
93
|
} }), _jsx(KptIconButton, { size: "sm", disabled: disabled, "aria-label": t('chat.attach'), onClick: () => pickerRef.current?.click(), children: _jsx(KptIcon, { name: "paperclip" }) })] })) : null, _jsx("textarea", { ref: fieldRef, className: "kpt-chat-composer__field", rows: 1, style: { '--_max-rows': maxRows }, value: text, disabled: disabled, placeholder: placeholder || t('chat.placeholder'), "aria-label": placeholder || t('chat.placeholder'), onChange: (event) => {
|
|
77
94
|
setText(event.currentTarget.value);
|
|
78
95
|
grow(event.currentTarget);
|
|
79
|
-
|
|
80
|
-
|
|
96
|
+
// "Piszę" przy pierwszym znaku i potem najwyżej raz na KPT_CHAT_TYPING_THROTTLE_MS - nie przy każdym.
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
if (now - lastTyping.current >= KPT_CHAT_TYPING_THROTTLE_MS) {
|
|
99
|
+
lastTyping.current = now;
|
|
100
|
+
onTyping?.();
|
|
101
|
+
}
|
|
102
|
+
}, onKeyDown: onKeyDown }), speech ? (_jsx(KptSpeechButton, { listeningColor: speechColor, speech: mic, size: "sm", unsupported: "hide", disabled: disabled, hostProps: { className: 'kpt-chat-composer__speech' } })) : null, _jsx(KptIconButton, { variant: "filled", disabled: !canSend, "aria-label": sendLabel, onClick: submit, children: _jsx(KptIcon, { name: offline ? 'clock' : 'send' }) })] })] }));
|
|
81
103
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# KptChatComposer (@konce-pt/react/chat)
|
|
2
2
|
|
|
3
|
-
The message field
|
|
3
|
+
The message field - auto-growing, with attachments and a quote strip.
|
|
4
4
|
Import: `import { KptChatComposer } from '@konce-pt/react/chat';`
|
|
5
5
|
|
|
6
6
|
## DOM
|
|
7
7
|
<div class="kpt-chat-composer-host">
|
|
8
|
-
<div class="kpt-chat-composer__reply"
|
|
9
|
-
<ul class="kpt-chat-composer__files"
|
|
8
|
+
<div class="kpt-chat-composer__reply">...the quoted message...</div>
|
|
9
|
+
<ul class="kpt-chat-composer__files">...picked files...</ul>
|
|
10
10
|
<div class="kpt-chat-composer">
|
|
11
11
|
<input type="file" class="kpt-chat-composer__picker" hidden />
|
|
12
12
|
<!-- KptIconButton: attach -->
|
|
@@ -16,26 +16,26 @@ Import: `import { KptChatComposer } from '@konce-pt/react/chat';`
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
18
|
## Props
|
|
19
|
-
- `value` / `defaultValue` / `onValueChange`
|
|
20
|
-
- `replyTo` / `defaultReplyTo` / `onReplyToChange`
|
|
19
|
+
- `value` / `defaultValue` / `onValueChange` - the text
|
|
20
|
+
- `replyTo` / `defaultReplyTo` / `onReplyToChange` - `KptChatReplyRef | null`
|
|
21
21
|
- `placeholder`, `disabled`, `offline`, `allowAttachments`
|
|
22
22
|
- `maxRows`: number (default 6), `enterToSend`: boolean (default true)
|
|
23
|
-
- `onSend(payload)`
|
|
24
|
-
- `onTyping()`
|
|
23
|
+
- `onSend(payload)` - `{ text, files, replyTo }`; the text is trimmed
|
|
24
|
+
- `onTyping()` - every keystroke, for the "is typing" signal
|
|
25
25
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
26
26
|
|
|
27
27
|
## Enter sends, Shift+Enter breaks the line
|
|
28
|
-
`enterToSend={false}` swaps the two. A keystroke arriving mid-composition (an IME) never sends
|
|
28
|
+
`enterToSend={false}` swaps the two. A keystroke arriving mid-composition (an IME) never sends -
|
|
29
29
|
`isComposing` is checked, otherwise picking a candidate would fire the message.
|
|
30
30
|
|
|
31
31
|
## The height comes from the content
|
|
32
32
|
The field grows with the text and then scrolls. The script sets `height` to `scrollHeight`, having
|
|
33
|
-
zeroed it first
|
|
33
|
+
zeroed it first - without that reset the field can only grow, because `scrollHeight` never drops
|
|
34
34
|
below the height already set. The ceiling is CSS: `--_max-rows`.
|
|
35
35
|
|
|
36
36
|
## Offline does not lock the field
|
|
37
37
|
The button changes its label to queueing and the payload still reaches `onSend`. What to do with a
|
|
38
|
-
queue is the application's decision
|
|
38
|
+
queue is the application's decision - the component has no transport to hold it back with.
|
|
39
39
|
|
|
40
40
|
## Examples
|
|
41
41
|
<KptChatComposer allowAttachments onSend={send} />
|
|
@@ -51,6 +51,15 @@ The field `--kpt-color-surface-variant` + `--kpt-radius-lg`, the top edge `--kpt
|
|
|
51
51
|
the quote strip `--kpt-color-primary`.
|
|
52
52
|
|
|
53
53
|
## Accessibility
|
|
54
|
-
The textarea carries an `aria-label` equal to the placeholder
|
|
54
|
+
The textarea carries an `aria-label` equal to the placeholder - a field without a label is
|
|
55
55
|
invisible to a screen reader. Both icon buttons are labelled from the dictionary. It is not a
|
|
56
56
|
form control: no `touched`/`invalid` state, and the value disappears on send.
|
|
57
|
+
|
|
58
|
+
## Dictation
|
|
59
|
+
`speech`: boolean = false puts a microphone between the field and the send button (`KptSpeechButton`);
|
|
60
|
+
`speechLang` sets the language and `speechColor` the listening colour (theme colour by default). Words appear in the field live. Enter or "Send" during dictation
|
|
61
|
+
commits what is visible (`flush()`) and stops listening, so exactly the visible text is sent - the send
|
|
62
|
+
button is enabled while listening even if only interim words exist. Final text comes through the
|
|
63
|
+
field's `onChange`: the field grows and `onTyping` fires as with a keyboard.
|
|
64
|
+
|
|
65
|
+
<KptChatComposer speech onSend={({ text }) => askAgent(text)} />
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
2
2
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
|
+
import { type KptEnterMotion } from '@konce-pt/motion';
|
|
3
4
|
export type KptChatDockPosition = 'bottom-end' | 'bottom-start';
|
|
4
5
|
export interface KptChatDockProps extends Omit<ComponentPropsWithRef<'div'>, 'children'>, KptI18nOverrides {
|
|
5
6
|
open?: boolean;
|
|
@@ -15,17 +16,23 @@ export interface KptChatDockProps extends Omit<ComponentPropsWithRef<'div'>, 'ch
|
|
|
15
16
|
width?: string;
|
|
16
17
|
/**
|
|
17
18
|
* Odsunięcie od rogu ekranu (dowolna długość CSS), wspólne dla obu osi.
|
|
18
|
-
* Domyślnie `--kpt-space-6`. Przydaje się, gdy w tym samym rogu stoi już inny widget
|
|
19
|
+
* Domyślnie `--kpt-space-6`. Przydaje się, gdy w tym samym rogu stoi już inny widget -
|
|
19
20
|
* `KptScrollTop` przykleja się dokładnie tam samo i bez tego oba się nakładają.
|
|
20
21
|
*/
|
|
21
22
|
offset?: string;
|
|
22
23
|
children?: ReactNode;
|
|
24
|
+
/**
|
|
25
|
+
* Ruch otwarcia: `spring` - okno wyrasta z przycisku (kapsuła rośnie w panel na sprężynie),
|
|
26
|
+
* `fade` - panel wjeżdża i się przenika, `none` - bez ruchu. Bez wartości bierze `enter`
|
|
27
|
+
* z `KptMotionProvider` (domyślnie `spring`); `enabled={false}` i systemowe "ogranicz ruch" wyłączają go zawsze.
|
|
28
|
+
*/
|
|
29
|
+
enterMotion?: KptEnterMotion;
|
|
23
30
|
}
|
|
24
31
|
/**
|
|
25
|
-
* Pływający czat w rogu ekranu
|
|
32
|
+
* Pływający czat w rogu ekranu - FAB z licznikiem nieprzeczytanych i panel z rozmową.
|
|
26
33
|
*
|
|
27
34
|
* Zawartość panelu jest dzieckiem, więc dock nie wie nic o wiadomościach: wkłada się do niego
|
|
28
|
-
* `KptChat` i `KptChatComposer` (albo cokolwiek innego). Sam FAB to `KptFab`
|
|
35
|
+
* `KptChat` i `KptChatComposer` (albo cokolwiek innego). Sam FAB to `KptFab` - dock go używa,
|
|
29
36
|
* nie zastępuje.
|
|
30
37
|
*
|
|
31
38
|
* Poniżej `--kpt-breakpoint-sm` panel wchodzi na pełny ekran: 22,5 rem szerokości na telefonie
|
|
@@ -37,4 +44,4 @@ export interface KptChatDockProps extends Omit<ComponentPropsWithRef<'div'>, 'ch
|
|
|
37
44
|
* <KptChatComposer onSend={send} />
|
|
38
45
|
* </KptChatDock>
|
|
39
46
|
*/
|
|
40
|
-
export declare function KptChatDock({ open, defaultOpen, onOpenChange, position, unread, header, icon, width, offset, locale, dictionary, className, children, ...rest }: Readonly<KptChatDockProps>): import("react").JSX.Element;
|
|
47
|
+
export declare function KptChatDock({ open, defaultOpen, onOpenChange, position, unread, header, icon, width, offset, enterMotion, locale, dictionary, className, children, ...rest }: Readonly<KptChatDockProps>): import("react").JSX.Element;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { useEffect, useRef, } from 'react';
|
|
3
4
|
import { cn } from "../internal/cn.js";
|
|
@@ -7,11 +8,13 @@ import { KptBadge } from "../badge/badge.js";
|
|
|
7
8
|
import { KptFab } from "../fab/fab.js";
|
|
8
9
|
import { KptIcon } from "../icon/icon.js";
|
|
9
10
|
import { KptIconButton } from "../icon-button/icon-button.js";
|
|
11
|
+
import { resolveEnterMotion } from '@konce-pt/motion';
|
|
12
|
+
import { useKptMotion } from "../motion/motion-context.js";
|
|
10
13
|
/**
|
|
11
|
-
* Pływający czat w rogu ekranu
|
|
14
|
+
* Pływający czat w rogu ekranu - FAB z licznikiem nieprzeczytanych i panel z rozmową.
|
|
12
15
|
*
|
|
13
16
|
* Zawartość panelu jest dzieckiem, więc dock nie wie nic o wiadomościach: wkłada się do niego
|
|
14
|
-
* `KptChat` i `KptChatComposer` (albo cokolwiek innego). Sam FAB to `KptFab`
|
|
17
|
+
* `KptChat` i `KptChatComposer` (albo cokolwiek innego). Sam FAB to `KptFab` - dock go używa,
|
|
15
18
|
* nie zastępuje.
|
|
16
19
|
*
|
|
17
20
|
* Poniżej `--kpt-breakpoint-sm` panel wchodzi na pełny ekran: 22,5 rem szerokości na telefonie
|
|
@@ -23,7 +26,8 @@ import { KptIconButton } from "../icon-button/icon-button.js";
|
|
|
23
26
|
* <KptChatComposer onSend={send} />
|
|
24
27
|
* </KptChatDock>
|
|
25
28
|
*/
|
|
26
|
-
export function KptChatDock({ open, defaultOpen = false, onOpenChange, position = 'bottom-end', unread = 0, header = '', icon = 'message-circle', width = '22.5rem', offset = '', locale, dictionary, className, children, ...rest }) {
|
|
29
|
+
export function KptChatDock({ open, defaultOpen = false, onOpenChange, position = 'bottom-end', unread = 0, header = '', icon = 'message-circle', width = '22.5rem', offset = '', enterMotion, locale, dictionary, className, children, ...rest }) {
|
|
30
|
+
const motion = resolveEnterMotion(enterMotion, useKptMotion());
|
|
27
31
|
const { t } = useKptMessages({ locale, dictionary });
|
|
28
32
|
const [isOpen, setOpen] = useControllableState({
|
|
29
33
|
value: open,
|
|
@@ -33,7 +37,7 @@ export function KptChatDock({ open, defaultOpen = false, onOpenChange, position
|
|
|
33
37
|
const panelRef = useRef(null);
|
|
34
38
|
const triggerRef = useRef(null);
|
|
35
39
|
/*
|
|
36
|
-
* Fokus ląduje na polu pisania, jeśli w panelu jest
|
|
40
|
+
* Fokus ląduje na polu pisania, jeśli w panelu jest - po to się czat otwiera.
|
|
37
41
|
* Bez pola bierze pierwszy element interaktywny, a w ostateczności sam panel.
|
|
38
42
|
*/
|
|
39
43
|
useEffect(() => {
|
|
@@ -54,7 +58,7 @@ export function KptChatDock({ open, defaultOpen = false, onOpenChange, position
|
|
|
54
58
|
if (!isOpen)
|
|
55
59
|
return;
|
|
56
60
|
setOpen(false);
|
|
57
|
-
// Fokus wraca na FAB
|
|
61
|
+
// Fokus wraca na FAB - po zamknięciu panelu nie może zostać na elemencie,
|
|
58
62
|
// który przed chwilą zniknął z drzewa dostępności.
|
|
59
63
|
triggerRef.current?.querySelector('button')?.focus();
|
|
60
64
|
};
|
|
@@ -63,5 +67,5 @@ export function KptChatDock({ open, defaultOpen = false, onOpenChange, position
|
|
|
63
67
|
close();
|
|
64
68
|
};
|
|
65
69
|
const fab = (_jsx(KptFab, { "aria-label": isOpen ? t('chat.close') : t('chat.open'), onClick: () => setOpen(!isOpen), children: _jsx(KptIcon, { name: isOpen ? 'x' : icon }) }));
|
|
66
|
-
return (_jsxs("div", { className: cn('kpt-chat-dock-host', className), onKeyDown: onKeyDown, ...rest, children: [_jsxs("div", { ref: panelRef, className: cn('kpt-chat-dock__panel', isOpen && 'is-open'), "data-position": position, role: "dialog", tabIndex: -1, style: { '--_width': width, ...(offset ? { '--_offset': offset } : {}) }, "aria-label": header || t('chat.open'), "aria-hidden": isOpen ? undefined : true, inert: !isOpen, children: [_jsxs("header", { className: "kpt-chat-dock__header", children: [_jsx("span", { className: "kpt-chat-dock__title", children: header }), _jsx(KptIconButton, { size: "sm", "aria-label": t('chat.close'), onClick: close, children: _jsx(KptIcon, { name: "x" }) })] }), _jsx("div", { className: "kpt-chat-dock__content", children: children })] }), _jsx("span", { ref: triggerRef, className: "kpt-chat-dock__trigger", "data-position": position, style: offset ? { '--_offset': offset } : undefined, children: unread > 0 ? (_jsx(KptBadge, { value: unread, variant: "danger", size: "sm", overlay: true, "aria-label": t('chat.unreadBadge', { count: unread }), children: fab })) : (fab) })] }));
|
|
70
|
+
return (_jsxs("div", { className: cn('kpt-chat-dock-host', className), onKeyDown: onKeyDown, ...rest, children: [_jsxs("div", { ref: panelRef, className: cn('kpt-chat-dock__panel', isOpen && 'is-open'), "data-position": position, "data-motion": motion, role: "dialog", tabIndex: -1, style: { '--_width': width, ...(offset ? { '--_offset': offset } : {}) }, "aria-label": header || t('chat.open'), "aria-hidden": isOpen ? undefined : true, inert: !isOpen, children: [_jsxs("header", { className: "kpt-chat-dock__header", children: [_jsx("span", { className: "kpt-chat-dock__title", children: header }), _jsx(KptIconButton, { size: "sm", "aria-label": t('chat.close'), onClick: close, children: _jsx(KptIcon, { name: "x" }) })] }), _jsx("div", { className: "kpt-chat-dock__content", children: children })] }), _jsx("span", { ref: triggerRef, className: "kpt-chat-dock__trigger", "data-position": position, style: offset ? { '--_offset': offset } : undefined, children: unread > 0 ? (_jsx(KptBadge, { value: unread, variant: "danger", size: "sm", overlay: true, "aria-label": t('chat.unreadBadge', { count: unread }), children: fab })) : (fab) })] }));
|
|
67
71
|
}
|
package/dist/chat-dock/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# KptChatDock (@konce-pt/react/chat)
|
|
2
2
|
|
|
3
|
-
A floating chat in the corner of the screen
|
|
3
|
+
A floating chat in the corner of the screen - a FAB with an unread badge plus a panel holding the
|
|
4
4
|
conversation.
|
|
5
5
|
Import: `import { KptChatDock } from '@konce-pt/react/chat';`
|
|
6
6
|
|
|
@@ -11,31 +11,32 @@ Import: `import { KptChatDock } from '@konce-pt/react/chat';`
|
|
|
11
11
|
<span class="kpt-chat-dock__title">Support</span>
|
|
12
12
|
<!-- KptIconButton: close -->
|
|
13
13
|
</header>
|
|
14
|
-
<div class="kpt-chat-dock__content"
|
|
14
|
+
<div class="kpt-chat-dock__content">...children...</div>
|
|
15
15
|
</div>
|
|
16
16
|
<span class="kpt-chat-dock__trigger" data-position="bottom-end">
|
|
17
|
-
<!-- KptBadge overlay
|
|
17
|
+
<!-- KptBadge overlay -> KptFab -->
|
|
18
18
|
</span>
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
21
|
## Props
|
|
22
|
-
- `open` / `defaultOpen` / `onOpenChange`
|
|
22
|
+
- `open` / `defaultOpen` / `onOpenChange` - works controlled and uncontrolled
|
|
23
23
|
- `position`: `'bottom-end' | 'bottom-start'` (logical, so RTL flips the corner on its own)
|
|
24
|
-
- `unread`: number
|
|
25
|
-
- `icon`: string (default `message-circle`)
|
|
26
|
-
- `width`: string (default `22.5rem`)
|
|
27
|
-
- `offset`: string
|
|
28
|
-
- `
|
|
24
|
+
- `unread`: number - 0 hides the badge; `header`: string
|
|
25
|
+
- `icon`: string (default `message-circle`) - the closed FAB; open always shows a cross
|
|
26
|
+
- `width`: string (default `22.5rem`) - above `--kpt-breakpoint-sm`
|
|
27
|
+
- `offset`: string - distance from the corner, both axes (default `--kpt-space-6`)
|
|
28
|
+
- `enterMotion`: `'spring' | 'fade' | 'none'` - `spring` grows the window out of the button, `fade` slides it in (default from `KptMotionProvider`, else `spring`)
|
|
29
|
+
- `children` - the panel content
|
|
29
30
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
30
31
|
|
|
31
32
|
## The FAB is KptFab, not a copy of it
|
|
32
|
-
The dock composes the existing components
|
|
33
|
+
The dock composes the existing components - `KptFab`, `KptBadge`, `KptIconButton` - so a change to
|
|
33
34
|
the FAB reaches the chat as well. The content is `children`, which means the dock knows nothing
|
|
34
35
|
about messages: put `KptChat` and `KptChatComposer` inside, or anything else.
|
|
35
36
|
|
|
36
37
|
## Sharing the corner with KptScrollTop
|
|
37
38
|
`KptScrollTop` pins itself to the same spot with the same `--kpt-z-overlay`, and it would swallow the
|
|
38
|
-
chat FAB whole
|
|
39
|
+
chat FAB whole - only the unread badge would stick out. The stylesheet resolves it by itself: when a
|
|
39
40
|
`bottom-end` dock is on the page, the scroll-top button steps **aside**, not up, because the
|
|
40
41
|
conversation panel stands above the FAB. It applies to `bottom-end` only and measures from the
|
|
41
42
|
default offset, so an application that sets `offset` positions the scroll-top button itself.
|
|
@@ -45,7 +46,7 @@ default offset, so an application that sets `offset` positions the scroll-top bu
|
|
|
45
46
|
Dropped in the middle of an application's layout the dock takes no space in the flow.
|
|
46
47
|
|
|
47
48
|
## Closed is not removed
|
|
48
|
-
The panel stays in the DOM with `visibility: hidden` and a delayed transition
|
|
49
|
+
The panel stays in the DOM with `visibility: hidden` and a delayed transition - a transition
|
|
49
50
|
cannot animate out of `display: none`. It also carries `inert` and `aria-hidden` while closed, so
|
|
50
51
|
a hidden panel never catches the tab order.
|
|
51
52
|
|
|
@@ -55,7 +56,7 @@ a hidden panel never catches the tab order.
|
|
|
55
56
|
<KptChatComposer onSend={send} />
|
|
56
57
|
</KptChatDock>
|
|
57
58
|
|
|
58
|
-
<KptChatDock open={open} onOpenChange={setOpen} position="bottom-start" width="26rem"
|
|
59
|
+
<KptChatDock open={open} onOpenChange={setOpen} position="bottom-start" width="26rem">...</KptChatDock>
|
|
59
60
|
|
|
60
61
|
## i18n
|
|
61
62
|
`open`, `close`, `unreadBadge` (`{count}`) from the `chat` namespace.
|
|
@@ -65,7 +66,7 @@ The panel `--kpt-radius-lg` + `--kpt-elevation-3`, the corner offsets `--kpt-spa
|
|
|
65
66
|
the stack `--kpt-z-overlay`.
|
|
66
67
|
|
|
67
68
|
## Accessibility
|
|
68
|
-
Opening moves focus to the composer inside the panel
|
|
69
|
+
Opening moves focus to the composer inside the panel - that is what the chat is opened for; with no
|
|
69
70
|
field it takes the first interactive element. `Escape` closes and returns focus to the FAB, because
|
|
70
71
|
focus must not stay on an element that just left the accessibility tree. Below 640px the panel goes
|
|
71
72
|
full-screen.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* @konce-pt/react/chat
|
|
2
|
+
* @konce-pt/react/chat - rozmowa. Osobny subpath, bo dock i logika listy są ciężkie
|
|
3
3
|
* względem rdzenia biblioteki i nie każda aplikacja ich potrzebuje.
|
|
4
4
|
*/
|
|
5
5
|
export { KptChat } from "../chat/chat.js";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
2
|
+
import { type KptEnterMotion } from '@konce-pt/motion';
|
|
2
3
|
import type { KptChatAttachment, KptChatAuthor, KptChatGroupPosition, KptChatMessage as KptChatMessageModel, KptChatReaction, KptChatReactionOption, KptChatReplyRef, KptChatStatus } from '@konce-pt/chat';
|
|
3
4
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
4
|
-
/** Wygląd bąbla. `system` nie rysuje bąbla w ogóle
|
|
5
|
+
/** Wygląd bąbla. `system` nie rysuje bąbla w ogóle - to komunikat, nie wypowiedź. */
|
|
5
6
|
export type KptChatMessageVariant = 'neutral' | 'primary' | 'solid' | 'ghost' | 'system';
|
|
6
7
|
export interface KptChatMessageProps extends Omit<ComponentPropsWithRef<'div'>, 'children'>, KptI18nOverrides {
|
|
7
8
|
/** Cała wiadomość naraz. Pojedyncze propsy poniżej mają nad nią priorytet. */
|
|
@@ -29,15 +30,21 @@ export interface KptChatMessageProps extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
29
30
|
reactionOptions?: readonly KptChatReactionOption[];
|
|
30
31
|
onRetry?: () => void;
|
|
31
32
|
onReply?: () => void;
|
|
32
|
-
/** Wybrana reakcja (emoji). Biblioteka niczego nie dopisuje do modelu
|
|
33
|
+
/** Wybrana reakcja (emoji). Biblioteka niczego nie dopisuje do modelu - robi to aplikacja. */
|
|
33
34
|
onReact?: (emoji: string) => void;
|
|
34
|
-
/** Kliknięcie w cytat
|
|
35
|
+
/** Kliknięcie w cytat - aplikacja może przewinąć listę do oryginału. */
|
|
35
36
|
onQuoted?: (quote: KptChatReplyRef) => void;
|
|
36
37
|
/** Treść bąbla. Wygrywa z `text`, więc bąbel przyjmuje cokolwiek. */
|
|
37
38
|
children?: ReactNode;
|
|
39
|
+
/**
|
|
40
|
+
* Ruch pojawiania się (reakcja, zmiana statusu): `spring` - wyrasta z miejsca, z którego przychodzi, na sprężynie;
|
|
41
|
+
* `fade` - tylko się przenika; `none` - bez ruchu. Bez wartości bierze `enter` z `KptMotionProvider`
|
|
42
|
+
* (domyślnie `spring`); `enabled={false}` i systemowe "ogranicz ruch" wyłączają go zawsze.
|
|
43
|
+
*/
|
|
44
|
+
enterMotion?: KptEnterMotion;
|
|
38
45
|
}
|
|
39
46
|
/**
|
|
40
|
-
* Pojedyncza wiadomość czatu
|
|
47
|
+
* Pojedyncza wiadomość czatu - bąbel z treścią, godziną, statusem wysyłki i opcjonalnym
|
|
41
48
|
* awatarem, cytatem, załącznikami i reakcjami.
|
|
42
49
|
*
|
|
43
50
|
* Działa na dwa sposoby: dostaje gotowy obiekt przez `message` (tak używa go `KptChat`)
|
|
@@ -50,4 +57,4 @@ export interface KptChatMessageProps extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
50
57
|
* <KptChatMessage message={msg} align="end" position="last" />
|
|
51
58
|
* <KptChatMessage align="end" status="failed" onRetry={resend}>Nie poszło</KptChatMessage>
|
|
52
59
|
*/
|
|
53
|
-
export declare function KptChatMessage({ message, author, text, time, attachments, reactions, replyTo, status, align, variant, position, system, showAuthor, showAvatar, showMeta, tail, actions, reactionOptions, onRetry, onReply, onReact, onQuoted, children, locale, dictionary, className, ...rest }: Readonly<KptChatMessageProps>): import("react").JSX.Element;
|
|
60
|
+
export declare function KptChatMessage({ message, author, text, time, attachments, reactions, replyTo, status, align, variant, position, system, showAuthor, showAvatar, showMeta, tail, actions, reactionOptions, enterMotion, ref, onRetry, onReply, onReact, onQuoted, children, locale, dictionary, className, ...rest }: Readonly<KptChatMessageProps>): import("react").JSX.Element;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { resolveEnterMotion } from '@konce-pt/motion';
|
|
3
5
|
import { KPT_CHAT_REACTIONS, canRetry, toDate } from '@konce-pt/chat';
|
|
4
6
|
import { cn } from "../internal/cn.js";
|
|
5
7
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
6
8
|
import { KptAvatar } from "../avatar/avatar.js";
|
|
7
9
|
import { KptIcon } from "../icon/icon.js";
|
|
8
10
|
import { KptIconButton } from "../icon-button/icon-button.js";
|
|
11
|
+
import { useKptMotion } from "../motion/motion-context.js";
|
|
12
|
+
import { composeRefs } from "../internal/compose-refs.js";
|
|
9
13
|
/** Ikona statusu wysyłki. `read` używa tej samej ikony co `delivered`, różni je kolor. */
|
|
10
14
|
const STATUS_ICONS = {
|
|
11
15
|
sending: 'clock',
|
|
@@ -15,7 +19,7 @@ const STATUS_ICONS = {
|
|
|
15
19
|
failed: 'alert-triangle',
|
|
16
20
|
};
|
|
17
21
|
/**
|
|
18
|
-
* Pojedyncza wiadomość czatu
|
|
22
|
+
* Pojedyncza wiadomość czatu - bąbel z treścią, godziną, statusem wysyłki i opcjonalnym
|
|
19
23
|
* awatarem, cytatem, załącznikami i reakcjami.
|
|
20
24
|
*
|
|
21
25
|
* Działa na dwa sposoby: dostaje gotowy obiekt przez `message` (tak używa go `KptChat`)
|
|
@@ -28,14 +32,21 @@ const STATUS_ICONS = {
|
|
|
28
32
|
* <KptChatMessage message={msg} align="end" position="last" />
|
|
29
33
|
* <KptChatMessage align="end" status="failed" onRetry={resend}>Nie poszło</KptChatMessage>
|
|
30
34
|
*/
|
|
31
|
-
export function KptChatMessage({ message = null, author = null, text = null, time = null, attachments = null, reactions = null, replyTo = null, status = null, align = 'start', variant = null, position = 'single', system = false, showAuthor = false, showAvatar = false, showMeta = true, tail = true, actions = false, reactionOptions = KPT_CHAT_REACTIONS, onRetry, onReply, onReact, onQuoted, children, locale, dictionary, className, ...rest }) {
|
|
35
|
+
export function KptChatMessage({ message = null, author = null, text = null, time = null, attachments = null, reactions = null, replyTo = null, status = null, align = 'start', variant = null, position = 'single', system = false, showAuthor = false, showAvatar = false, showMeta = true, tail = true, actions = false, reactionOptions = KPT_CHAT_REACTIONS, enterMotion, ref, onRetry, onReply, onReact, onQuoted, children, locale, dictionary, className, ...rest }) {
|
|
32
36
|
const { t, locale: activeLocale } = useKptMessages({ locale, dictionary });
|
|
33
37
|
const rowRef = useRef(null);
|
|
34
38
|
/** Czy wybierak reakcji jest otwarty. */
|
|
35
39
|
const [pickerOpen, setPickerOpen] = useState(false);
|
|
40
|
+
const motion = resolveEnterMotion(enterMotion, useKptMotion());
|
|
41
|
+
const hostRef = useRef(null);
|
|
42
|
+
// Reakcje i status wskakują dopiero po pierwszym renderze - wczytana historia się nie rusza.
|
|
43
|
+
// Klasa idzie wprost na element: osobny stan dałby drugi przebieg renderu tylko po to.
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
hostRef.current?.classList.add('is-live');
|
|
46
|
+
}, []);
|
|
36
47
|
// Kliknięcie obok zamyka wybierak. Sam `onBlur` nie wystarcza: w Safari kliknięcie w przycisk
|
|
37
48
|
// nie przenosi na niego fokusu, więc wybierak zostawałby otwarty na zawsze. Faza przechwytywania
|
|
38
|
-
// i `pointerdown` (nie `click`)
|
|
49
|
+
// i `pointerdown` (nie `click`) - własne `onClick` opcji zdąży się wykonać przed zamknięciem.
|
|
39
50
|
useEffect(() => {
|
|
40
51
|
if (!pickerOpen)
|
|
41
52
|
return;
|
|
@@ -75,7 +86,7 @@ export function KptChatMessage({ message = null, author = null, text = null, tim
|
|
|
75
86
|
const statusIcon = resolvedStatus ? STATUS_ICONS[resolvedStatus] : null;
|
|
76
87
|
const retryable = canRetry(resolvedStatus);
|
|
77
88
|
/*
|
|
78
|
-
* Etykieta dla czytnika ekranu. Bez niej z bąbla płynie sama treść
|
|
89
|
+
* Etykieta dla czytnika ekranu. Bez niej z bąbla płynie sama treść - bez nadawcy,
|
|
79
90
|
* godziny i informacji, że wiadomość się nie wysłała.
|
|
80
91
|
*/
|
|
81
92
|
const ariaLabel = isSystem
|
|
@@ -84,9 +95,9 @@ export function KptChatMessage({ message = null, author = null, text = null, tim
|
|
|
84
95
|
.filter(Boolean)
|
|
85
96
|
.join(', ');
|
|
86
97
|
if (isSystem) {
|
|
87
|
-
return (_jsx("div", { className: cn('kpt-chat-message-host', className), role: "note", "aria-label": ariaLabel, ...rest, children: _jsx("p", { className: "kpt-chat-message__system", children: resolvedText }) }));
|
|
98
|
+
return (_jsx("div", { ref: composeRefs(hostRef, ref), className: cn('kpt-chat-message-host', className), role: "note", "aria-label": ariaLabel, "data-motion": motion, ...rest, children: _jsx("p", { className: "kpt-chat-message__system", children: resolvedText }) }));
|
|
88
99
|
}
|
|
89
|
-
return (_jsx("div", { className: cn('kpt-chat-message-host', className), role: "article", "aria-label": ariaLabel, ...rest, children: _jsxs("div", { ref: rowRef, className: "kpt-chat-message__row", "data-align": align, children: [align === 'start' ? (_jsx("span", { className: "kpt-chat-message__avatar", "aria-hidden": "true", children: showAvatar ? (_jsx(KptAvatar, { size: "sm", src: resolvedAuthor?.avatar ?? null, name: resolvedAuthor?.name ?? '' })) : null })) : null, _jsxs("div", { className: "kpt-chat-message__stack", children: [showAuthor && resolvedAuthor ? (_jsx("span", { className: "kpt-chat-message__author", children: resolvedAuthor.name })) : null, _jsxs("div", { className: "kpt-chat-message__bubble", "data-variant": resolvedVariant, "data-align": align, "data-position": position, "data-status": resolvedStatus ?? undefined, "data-tail": hasTail ? '' : undefined, children: [resolvedReplyTo ? (_jsxs("button", { type: "button", className: "kpt-chat-message__quote", onClick: () => onQuoted?.(resolvedReplyTo), children: [_jsx("span", { className: "kpt-chat-message__quote-author", children: resolvedReplyTo.author }), _jsx("span", { className: "kpt-chat-message__quote-text", children: resolvedReplyTo.excerpt })] })) : null, resolvedText ? _jsx("p", { className: "kpt-chat-message__text", children: resolvedText }) : null, children, resolvedAttachments.length ? (_jsx("ul", { className: "kpt-chat-message__files", children: resolvedAttachments.map((file) => (_jsxs("li", { className: "kpt-chat-message__file", children: [file.thumbnail ? (_jsx("img", { className: "kpt-chat-message__file-thumb", src: file.thumbnail, alt: file.name })) : (_jsx(KptIcon, { name: "paperclip" })), _jsx("a", { className: "kpt-chat-message__file-name", href: file.url, download: file.url ? file.name : undefined, children: file.name }), file.size ? (_jsx("span", { className: "kpt-chat-message__file-size", children: fileSize(file.size) })) : null] }, file.id))) })) : null, showMeta ? (_jsxs("span", { className: "kpt-chat-message__meta", children: [timeLabel ? (_jsx("time", { className: "kpt-chat-message__time", dateTime: sentAt?.toISOString(), children: timeLabel })) : null, statusIcon ? (_jsx(KptIcon, { className: "kpt-chat-message__status", name: statusIcon })) : null] })) : null, resolvedReactions.length ? (_jsx("ul", { className: "kpt-chat-message__reactions", "data-align": align, children: resolvedReactions.map((reaction) => (_jsxs("li", { className: "kpt-chat-message__reaction", "data-reacted": reaction.reacted ? '' : undefined, children: [_jsx("span", { "aria-hidden": "true", children: reaction.emoji }), _jsx("span", { className: "kpt-chat-message__reaction-count", children: reaction.count })] }, reaction.emoji))) })) : null] })] }), _jsx("span", { className: "kpt-chat-message__actions", "data-open": pickerOpen ? '' : undefined, children: retryable ? (_jsx(KptIconButton, { hostProps: { className: 'kpt-chat-message__retry' }, size: "sm", "aria-label": t('chat.retry'), onClick: () => onRetry?.(), children: _jsx(KptIcon, { name: "refresh" }) })) : actions ? (_jsxs(_Fragment, { children: [_jsx(KptIconButton, { size: "sm", "aria-label": t('chat.replyTo', { author: resolvedAuthor?.name ?? '' }), onClick: () => onReply?.(), children: _jsx(KptIcon, { name: "corner-up-left" }) }), _jsxs("span", { className: "kpt-chat-message__react", onKeyDown: (event) => {
|
|
100
|
+
return (_jsx("div", { ref: composeRefs(hostRef, ref), className: cn('kpt-chat-message-host', className), role: "article", "aria-label": ariaLabel, "data-motion": motion, ...rest, children: _jsxs("div", { ref: rowRef, className: "kpt-chat-message__row", "data-align": align, children: [align === 'start' ? (_jsx("span", { className: "kpt-chat-message__avatar", "aria-hidden": "true", children: showAvatar ? (_jsx(KptAvatar, { size: "sm", src: resolvedAuthor?.avatar ?? null, name: resolvedAuthor?.name ?? '' })) : null })) : null, _jsxs("div", { className: "kpt-chat-message__stack", children: [showAuthor && resolvedAuthor ? (_jsx("span", { className: "kpt-chat-message__author", children: resolvedAuthor.name })) : null, _jsxs("div", { className: "kpt-chat-message__bubble", "data-variant": resolvedVariant, "data-align": align, "data-position": position, "data-status": resolvedStatus ?? undefined, "data-tail": hasTail ? '' : undefined, children: [resolvedReplyTo ? (_jsxs("button", { type: "button", className: "kpt-chat-message__quote", onClick: () => onQuoted?.(resolvedReplyTo), children: [_jsx("span", { className: "kpt-chat-message__quote-author", children: resolvedReplyTo.author }), _jsx("span", { className: "kpt-chat-message__quote-text", children: resolvedReplyTo.excerpt })] })) : null, resolvedText ? _jsx("p", { className: "kpt-chat-message__text", children: resolvedText }) : null, children, resolvedAttachments.length ? (_jsx("ul", { className: "kpt-chat-message__files", children: resolvedAttachments.map((file) => (_jsxs("li", { className: "kpt-chat-message__file", children: [file.thumbnail ? (_jsx("img", { className: "kpt-chat-message__file-thumb", src: file.thumbnail, alt: file.name })) : (_jsx(KptIcon, { name: "paperclip" })), _jsx("a", { className: "kpt-chat-message__file-name", href: file.url, download: file.url ? file.name : undefined, children: file.name }), file.size ? (_jsx("span", { className: "kpt-chat-message__file-size", children: fileSize(file.size) })) : null] }, file.id))) })) : null, showMeta ? (_jsxs("span", { className: "kpt-chat-message__meta", children: [timeLabel ? (_jsx("time", { className: "kpt-chat-message__time", dateTime: sentAt?.toISOString(), children: timeLabel })) : null, statusIcon ? (_jsx(KptIcon, { className: "kpt-chat-message__status", name: statusIcon }, statusIcon)) : null] })) : null, resolvedReactions.length ? (_jsx("ul", { className: "kpt-chat-message__reactions", "data-align": align, children: resolvedReactions.map((reaction) => (_jsxs("li", { className: "kpt-chat-message__reaction", "data-reacted": reaction.reacted ? '' : undefined, children: [_jsx("span", { "aria-hidden": "true", children: reaction.emoji }), _jsx("span", { className: "kpt-chat-message__reaction-count", children: _jsx("span", { className: "kpt-chat-message__reaction-digit", children: reaction.count }, reaction.count) })] }, reaction.emoji))) })) : null] })] }), _jsx("span", { className: "kpt-chat-message__actions", "data-open": pickerOpen ? '' : undefined, children: retryable ? (_jsx(KptIconButton, { hostProps: { className: 'kpt-chat-message__retry' }, size: "sm", "aria-label": t('chat.retry'), onClick: () => onRetry?.(), children: _jsx(KptIcon, { name: "refresh" }) })) : actions ? (_jsxs(_Fragment, { children: [_jsx(KptIconButton, { size: "sm", "aria-label": t('chat.replyTo', { author: resolvedAuthor?.name ?? '' }), onClick: () => onReply?.(), children: _jsx(KptIcon, { name: "corner-up-left" }) }), _jsxs("span", { className: "kpt-chat-message__react", onKeyDown: (event) => {
|
|
90
101
|
if (event.key === 'Escape')
|
|
91
102
|
setPickerOpen(false);
|
|
92
103
|
}, onBlur: (event) => {
|
|
@@ -94,7 +105,7 @@ export function KptChatMessage({ message = null, author = null, text = null, tim
|
|
|
94
105
|
if (!event.currentTarget.contains(event.relatedTarget))
|
|
95
106
|
setPickerOpen(false);
|
|
96
107
|
}, children: [_jsx(KptIconButton, { size: "sm", "aria-label": t('chat.react'), "aria-expanded": pickerOpen, onClick: () => setPickerOpen((open) => !open), children: _jsx(KptIcon, { name: "mood-smile" }) }), pickerOpen ? (_jsx("span", { className: "kpt-chat-message__picker", role: "group", "aria-label": t('chat.react'), children: reactionOptions.map((option) => {
|
|
97
|
-
const label = t('chat.react' + option.kind[0].toUpperCase() + option.kind.slice(1));
|
|
108
|
+
const label = option.label ?? t('chat.react' + option.kind[0].toUpperCase() + option.kind.slice(1));
|
|
98
109
|
const reacted = resolvedReactions.some((item) => item.emoji === option.emoji && item.reacted);
|
|
99
110
|
return (_jsx("button", { type: "button", className: "kpt-chat-message__pick", "aria-label": label, title: label, "aria-pressed": reacted, onClick: () => {
|
|
100
111
|
onReact?.(option.emoji);
|
|
@@ -102,7 +113,7 @@ export function KptChatMessage({ message = null, author = null, text = null, tim
|
|
|
102
113
|
}, children: _jsx("span", { "aria-hidden": "true", children: option.emoji }) }, option.kind));
|
|
103
114
|
}) })) : null] })] })) : null })] }) }));
|
|
104
115
|
}
|
|
105
|
-
/** Rozmiar załącznika w kB/MB
|
|
116
|
+
/** Rozmiar załącznika w kB/MB - surowe bajty nic nie mówią przy pliku obok nazwy. */
|
|
106
117
|
function fileSize(bytes) {
|
|
107
118
|
if (bytes < 1024)
|
|
108
119
|
return bytes + ' B';
|
|
@@ -7,41 +7,42 @@ Import: `import { KptChatMessage } from '@konce-pt/react/chat';`
|
|
|
7
7
|
## DOM
|
|
8
8
|
<div class="kpt-chat-message-host" role="article" aria-label="Anna, 09:22, Read">
|
|
9
9
|
<div class="kpt-chat-message__row" data-align="start">
|
|
10
|
-
<span class="kpt-chat-message__avatar"
|
|
10
|
+
<span class="kpt-chat-message__avatar">...KptAvatar...</span>
|
|
11
11
|
<div class="kpt-chat-message__stack">
|
|
12
12
|
<span class="kpt-chat-message__author">Anna Kowalska</span>
|
|
13
13
|
<div class="kpt-chat-message__bubble"
|
|
14
14
|
data-variant="neutral" data-align="start" data-position="last" data-tail>
|
|
15
|
-
<button class="kpt-chat-message__quote"
|
|
16
|
-
<p class="kpt-chat-message__text"
|
|
17
|
-
<ul class="kpt-chat-message__files"
|
|
18
|
-
<span class="kpt-chat-message__meta"><time>09:22</time
|
|
19
|
-
<ul class="kpt-chat-message__reactions"
|
|
15
|
+
<button class="kpt-chat-message__quote">...</button>
|
|
16
|
+
<p class="kpt-chat-message__text">...</p>
|
|
17
|
+
<ul class="kpt-chat-message__files">...</ul>
|
|
18
|
+
<span class="kpt-chat-message__meta"><time>09:22</time>...</span>
|
|
19
|
+
<ul class="kpt-chat-message__reactions">...</ul>
|
|
20
20
|
</div>
|
|
21
21
|
</div>
|
|
22
|
-
<span class="kpt-chat-message__actions"
|
|
22
|
+
<span class="kpt-chat-message__actions">...KptIconButton, .kpt-chat-message__picker...</span>
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
25
25
|
|
|
26
26
|
## Props
|
|
27
|
-
- `message`: `KptChatMessage`
|
|
27
|
+
- `message`: `KptChatMessage` - the whole object; the single props below win over it
|
|
28
28
|
- `author`, `text`, `time`, `status`, `attachments`, `reactions`, `replyTo`
|
|
29
29
|
- `align`: `'start' | 'end'`; `variant`: `'neutral' | 'primary' | 'solid' | 'ghost' | 'system'`
|
|
30
30
|
- `position`: `'single' | 'first' | 'middle' | 'last'`; `system`: boolean
|
|
31
31
|
- `showAuthor`, `showAvatar`, `showMeta` (default true), `tail` (default true), `actions`
|
|
32
|
-
- `reactionOptions`: `readonly KptChatReactionOption[]` (default `KPT_CHAT_REACTIONS`)
|
|
32
|
+
- `reactionOptions`: `readonly KptChatReactionOption[]` (default `KPT_CHAT_REACTIONS`) - the picker set
|
|
33
|
+
- `enterMotion`: `'spring' | 'fade' | 'none'` - how a new reaction and a status change appear (`KptChat` passes its own; see its doc)
|
|
33
34
|
- `onRetry`, `onReply`: `() => void`; `onReact`: `(emoji: string) => void`; `onQuoted`: `(quote) => void`
|
|
34
|
-
- `children`
|
|
35
|
+
- `children` - bubble content; it renders next to `text`, so the bubble takes anything
|
|
35
36
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
36
37
|
|
|
37
38
|
## The default variant follows the alignment
|
|
38
39
|
Your own message gets the full `solid`, someone else's gets `neutral`. The theme is monochrome, so
|
|
39
|
-
the difference between bubbles is lightness, not hue
|
|
40
|
+
the difference between bubbles is lightness, not hue - `--kpt-color-primary-subtle` has exactly the
|
|
40
41
|
lightness of `--kpt-color-muted`, which would make the two sides indistinguishable. For a quieter
|
|
41
42
|
list set `variant="primary"` by hand.
|
|
42
43
|
|
|
43
44
|
## Grouping is what the shape is made of
|
|
44
|
-
`position` decides which corner collapses and which bubble wears the tail
|
|
45
|
+
`position` decides which corner collapses and which bubble wears the tail - inside a group the
|
|
45
46
|
corner on the sender's side drops to `--kpt-radius-sm`. `KptChat` gets all of it from
|
|
46
47
|
`buildChatRows()`; set it by hand only when you build the list yourself. The tail is skipped for
|
|
47
48
|
`ghost` and `system`, which have no background to cut it out of.
|
|
@@ -49,7 +50,7 @@ corner on the sender's side drops to `--kpt-radius-sm`. `KptChat` gets all of it
|
|
|
49
50
|
## An unsent message must be able to say so
|
|
50
51
|
`status="failed"` outlines the bubble and puts a retry arrow **outside** it, in the action column
|
|
51
52
|
on the outer side of the list. `KptChat` also keeps the meta row visible on such a bubble even in
|
|
52
|
-
the middle of a group
|
|
53
|
+
the middle of a group - the arrow with nothing to explain it would be a mystery.
|
|
53
54
|
Statuses: `sending` (clock), `sent` (check), `delivered` (double check), `read` (the same icon in
|
|
54
55
|
the accent colour) and `failed`.
|
|
55
56
|
|
|
@@ -64,10 +65,10 @@ is formatted by `Intl` in the active locale.
|
|
|
64
65
|
|
|
65
66
|
## Tokens
|
|
66
67
|
`--kpt-color-muted` (incoming) / `--kpt-color-primary` + `--kpt-color-on-primary` (own),
|
|
67
|
-
failed `--kpt-color-danger-subtle` + `--kpt-color-danger-border`
|
|
68
|
+
failed `--kpt-color-danger-subtle` + `--kpt-color-danger-border` - the failed rule also resets the
|
|
68
69
|
text colour, or a white `solid` label would land on pale pink.
|
|
69
70
|
|
|
70
71
|
## Accessibility
|
|
71
|
-
The host is an `article` labelled with author, time and status
|
|
72
|
+
The host is an `article` labelled with author, time and status - content alone would say nothing
|
|
72
73
|
about who wrote it or whether it was sent. The status icon is `aria-hidden`; the meaning travels in
|
|
73
74
|
the label. A system notice is a `note`, never an `article`.
|
|
@@ -8,13 +8,13 @@ export interface KptChatTypingProps extends Omit<ComponentPropsWithRef<'div'>, '
|
|
|
8
8
|
label?: string;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* Wskaźnik pisania
|
|
11
|
+
* Wskaźnik pisania - trzy kropki i informacja, kto pisze.
|
|
12
12
|
*
|
|
13
13
|
* Pusta lista autorów nie renderuje niczego, więc można go trzymać w drzewie na stałe
|
|
14
14
|
* i sterować wyłącznie danymi.
|
|
15
15
|
*
|
|
16
16
|
* Element jest `role="status"` z `aria-live="polite"`: czytnik ogłasza wejście i zniknięcie
|
|
17
|
-
* wskaźnika, ale nie przerywa czytania wiadomości. Sama animacja kropek nic nie mówi
|
|
17
|
+
* wskaźnika, ale nie przerywa czytania wiadomości. Sama animacja kropek nic nie mówi -
|
|
18
18
|
* informację niesie etykieta.
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { cn } from "../internal/cn.js";
|
|
3
4
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
4
5
|
/**
|
|
5
|
-
* Wskaźnik pisania
|
|
6
|
+
* Wskaźnik pisania - trzy kropki i informacja, kto pisze.
|
|
6
7
|
*
|
|
7
8
|
* Pusta lista autorów nie renderuje niczego, więc można go trzymać w drzewie na stałe
|
|
8
9
|
* i sterować wyłącznie danymi.
|
|
9
10
|
*
|
|
10
11
|
* Element jest `role="status"` z `aria-live="polite"`: czytnik ogłasza wejście i zniknięcie
|
|
11
|
-
* wskaźnika, ale nie przerywa czytania wiadomości. Sama animacja kropek nic nie mówi
|
|
12
|
+
* wskaźnika, ale nie przerywa czytania wiadomości. Sama animacja kropek nic nie mówi -
|
|
12
13
|
* informację niesie etykieta.
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
@@ -19,7 +20,7 @@ export function KptChatTyping({ authors = [], label = '', locale, dictionary, cl
|
|
|
19
20
|
if (authors.length === 0 && !label)
|
|
20
21
|
return null;
|
|
21
22
|
/*
|
|
22
|
-
* Jedna osoba jest wymieniona z imienia, przy większej liczbie liczy się głowy
|
|
23
|
+
* Jedna osoba jest wymieniona z imienia, przy większej liczbie liczy się głowy -
|
|
23
24
|
* lista nazwisk rozjeżdża wiersz i i tak nikt jej nie czyta.
|
|
24
25
|
*/
|
|
25
26
|
const text = label ||
|