@konce-pt/react 0.9.0 → 0.10.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/dist/accordion/accordion.js +7 -1
- package/dist/accordion/llms.txt +8 -4
- package/dist/alert/alert.d.ts +10 -4
- package/dist/alert/alert.js +13 -6
- package/dist/alert/llms.txt +8 -6
- package/dist/app-shell/app-shell.js +28 -2
- package/dist/app-shell/llms.txt +3 -0
- package/dist/autocomplete/autocomplete.d.ts +7 -2
- package/dist/autocomplete/autocomplete.js +46 -9
- package/dist/autocomplete/llms.txt +12 -0
- package/dist/avatar/avatar.d.ts +4 -2
- package/dist/avatar/avatar.js +8 -3
- package/dist/avatar/llms.txt +9 -5
- package/dist/avatar-group/avatar-group.d.ts +5 -2
- package/dist/avatar-group/avatar-group.js +10 -2
- package/dist/avatar-group/llms.txt +7 -4
- package/dist/badge/badge.d.ts +8 -1
- package/dist/badge/badge.js +3 -2
- package/dist/badge/llms.txt +11 -5
- package/dist/bottom-sheet/bottom-sheet.d.ts +1 -1
- package/dist/bottom-sheet/bottom-sheet.js +8 -2
- package/dist/bottom-sheet/llms.txt +8 -2
- package/dist/breadcrumb/breadcrumb.js +4 -4
- package/dist/breadcrumb/llms.txt +12 -5
- package/dist/card/card.d.ts +9 -1
- package/dist/card/card.js +2 -2
- package/dist/card/llms.txt +6 -2
- package/dist/carousel/carousel.d.ts +8 -1
- package/dist/carousel/carousel.js +9 -2
- package/dist/carousel/llms.txt +24 -10
- package/dist/checkbox/checkbox.d.ts +6 -1
- package/dist/checkbox/checkbox.js +11 -3
- package/dist/checkbox/llms.txt +11 -0
- package/dist/chip/chip.js +10 -1
- package/dist/chip/llms.txt +7 -4
- package/dist/chips-input/chips-input.d.ts +7 -1
- package/dist/chips-input/chips-input.js +21 -5
- package/dist/chips-input/llms.txt +12 -0
- package/dist/color-picker/color-picker.d.ts +18 -5
- package/dist/color-picker/color-picker.js +48 -14
- package/dist/color-picker/llms.txt +14 -0
- package/dist/confirm/confirm-container.d.ts +5 -1
- package/dist/confirm/confirm-container.js +15 -1
- package/dist/confirm/llms.txt +4 -2
- package/dist/context-menu/context-menu.d.ts +5 -0
- package/dist/context-menu/context-menu.js +48 -4
- package/dist/context-menu/llms.txt +11 -4
- package/dist/date-range/date-range.d.ts +14 -1
- package/dist/date-range/date-range.js +133 -17
- package/dist/date-range/llms.txt +26 -3
- package/dist/datepicker/datepicker.d.ts +10 -1
- package/dist/datepicker/datepicker.js +147 -14
- package/dist/datepicker/llms.txt +27 -3
- package/dist/dialog/dialog.d.ts +4 -0
- package/dist/dialog/dialog.js +7 -1
- package/dist/dialog/llms.txt +2 -0
- package/dist/drawer/drawer.js +7 -2
- package/dist/drawer/llms.txt +10 -5
- package/dist/empty/empty.d.ts +6 -1
- package/dist/empty/empty.js +2 -2
- package/dist/empty/llms.txt +4 -3
- package/dist/fab/llms.txt +2 -1
- package/dist/file-upload/file-upload.d.ts +5 -0
- package/dist/file-upload/file-upload.js +24 -3
- package/dist/file-upload/llms.txt +15 -4
- package/dist/form-field/form-field-context.d.ts +30 -1
- package/dist/form-field/form-field-context.js +27 -0
- package/dist/form-field/form-field.js +16 -2
- package/dist/form-field/llms.txt +10 -1
- package/dist/galleria/galleria.d.ts +6 -1
- package/dist/galleria/galleria.js +40 -1
- package/dist/galleria/llms.txt +17 -8
- package/dist/icon/icon.d.ts +7 -1
- package/dist/icon/icon.js +18 -4
- package/dist/icon/llms.txt +5 -2
- package/dist/image/image.d.ts +5 -0
- package/dist/image/image.js +38 -2
- package/dist/image/llms.txt +24 -17
- package/dist/input/input.d.ts +1 -1
- package/dist/input/input.js +4 -6
- package/dist/input/llms.txt +5 -0
- package/dist/input-mask/input-mask.d.ts +1 -1
- package/dist/input-mask/input-mask.js +8 -6
- package/dist/input-mask/llms.txt +11 -0
- package/dist/input-number/input-number.d.ts +13 -1
- package/dist/input-number/input-number.js +49 -9
- package/dist/input-number/llms.txt +21 -3
- package/dist/input-otp/input-otp.d.ts +7 -2
- package/dist/input-otp/input-otp.js +36 -7
- package/dist/input-otp/llms.txt +14 -0
- package/dist/internal/calendar-focus.d.ts +8 -0
- package/dist/internal/calendar-focus.js +38 -0
- package/dist/internal/option-nav.d.ts +24 -0
- package/dist/internal/option-nav.js +80 -0
- package/dist/internal/roving.d.ts +9 -0
- package/dist/internal/roving.js +43 -0
- package/dist/internal/use-modal-focus.d.ts +10 -0
- package/dist/internal/use-modal-focus.js +57 -0
- package/dist/knob/knob.d.ts +5 -1
- package/dist/knob/knob.js +4 -2
- package/dist/knob/llms.txt +8 -0
- package/dist/listbox/listbox.d.ts +13 -1
- package/dist/listbox/listbox.js +89 -6
- package/dist/listbox/llms.txt +21 -5
- package/dist/megamenu/llms.txt +28 -10
- package/dist/megamenu/megamenu.d.ts +10 -1
- package/dist/megamenu/megamenu.js +116 -5
- package/dist/menu/llms.txt +12 -1
- package/dist/menu/menu-keys.d.ts +15 -0
- package/dist/menu/menu-keys.js +53 -0
- package/dist/menu/menu-rows.js +1 -1
- package/dist/menu/menu.js +52 -4
- package/dist/menubar/llms.txt +24 -7
- package/dist/menubar/menubar.d.ts +8 -1
- package/dist/menubar/menubar.js +92 -6
- package/dist/meter-group/llms.txt +10 -3
- package/dist/meter-group/meter-group.d.ts +9 -2
- package/dist/meter-group/meter-group.js +8 -2
- package/dist/order-list/llms.txt +11 -3
- package/dist/order-list/order-list.d.ts +5 -1
- package/dist/order-list/order-list.js +46 -8
- package/dist/panel/llms.txt +9 -6
- package/dist/panel/panel.js +3 -1
- package/dist/password/llms.txt +8 -0
- package/dist/password/password.d.ts +6 -1
- package/dist/password/password.js +6 -5
- package/dist/pick-list/llms.txt +12 -3
- package/dist/pick-list/pick-list.d.ts +5 -1
- package/dist/pick-list/pick-list.js +52 -5
- package/dist/popover/llms.txt +9 -2
- package/dist/popover/popover.d.ts +9 -3
- package/dist/popover/popover.js +41 -6
- package/dist/public-api.d.ts +6 -6
- package/dist/public-api.js +1 -1
- package/dist/radio-group/llms.txt +11 -0
- package/dist/radio-group/radio-group.d.ts +9 -1
- package/dist/radio-group/radio-group.js +5 -2
- package/dist/rating/llms.txt +18 -5
- package/dist/rating/rating.d.ts +21 -3
- package/dist/rating/rating.js +87 -12
- package/dist/rich-text/llms.txt +20 -0
- package/dist/rich-text/rich-text.d.ts +7 -1
- package/dist/rich-text/rich-text.js +155 -28
- package/dist/scroll-top/llms.txt +5 -1
- package/dist/scroll-top/scroll-top.js +3 -1
- package/dist/select/llms.txt +21 -7
- package/dist/select/select.d.ts +12 -1
- package/dist/select/select.js +110 -38
- package/dist/sidenav/llms.txt +6 -0
- package/dist/skeleton/llms.txt +3 -2
- package/dist/skeleton/skeleton.d.ts +1 -1
- package/dist/skeleton/skeleton.js +2 -2
- package/dist/slider/llms.txt +14 -1
- package/dist/slider/slider.d.ts +11 -2
- package/dist/slider/slider.js +15 -2
- package/dist/speed-dial/llms.txt +6 -2
- package/dist/speed-dial/speed-dial.d.ts +1 -1
- package/dist/speed-dial/speed-dial.js +14 -3
- package/dist/spinner/llms.txt +12 -8
- package/dist/spinner/spinner.d.ts +5 -1
- package/dist/spinner/spinner.js +12 -2
- package/dist/split-button/llms.txt +11 -3
- package/dist/split-button/split-button.d.ts +3 -1
- package/dist/split-button/split-button.js +43 -3
- package/dist/splitter/llms.txt +14 -4
- package/dist/splitter/splitter.d.ts +9 -2
- package/dist/splitter/splitter.js +15 -2
- package/dist/stepper/llms.txt +8 -4
- package/dist/stepper/stepper.d.ts +3 -2
- package/dist/stepper/stepper.js +7 -2
- package/dist/switch/llms.txt +10 -0
- package/dist/switch/switch.d.ts +1 -1
- package/dist/switch/switch.js +4 -2
- package/dist/switch-group/llms.txt +11 -0
- package/dist/switch-group/switch-group.d.ts +5 -1
- package/dist/switch-group/switch-group.js +7 -2
- package/dist/tabs/llms.txt +10 -4
- package/dist/tabs/tabs.d.ts +4 -0
- package/dist/tabs/tabs.js +21 -2
- package/dist/textarea/llms.txt +10 -0
- package/dist/textarea/textarea.d.ts +3 -1
- package/dist/textarea/textarea.js +13 -8
- package/dist/toast/llms.txt +21 -7
- package/dist/toast/toast-container.d.ts +6 -1
- package/dist/toast/toast-container.js +52 -1
- package/dist/toast/toast-store.d.ts +12 -0
- package/dist/toast/toast-store.js +46 -7
- package/dist/tooltip/llms.txt +10 -3
- package/dist/tooltip/tooltip.d.ts +5 -0
- package/dist/tooltip/tooltip.js +80 -7
- package/dist/tree/llms.txt +20 -8
- package/dist/tree/tree-keys.d.ts +27 -0
- package/dist/tree/tree-keys.js +39 -0
- package/dist/tree/tree.d.ts +12 -5
- package/dist/tree/tree.js +75 -29
- package/package.json +12 -12
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Children, cloneElement, createContext, isValidElement, useContext, useState, } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
4
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
|
+
import { useKptId } from "../internal/use-id.js";
|
|
5
6
|
import { KptIcon } from "../icon/icon.js";
|
|
6
7
|
const AccordionContext = createContext(null);
|
|
7
8
|
/**
|
|
@@ -46,6 +47,9 @@ export function KptAccordion({ multi = false, animated = true, animationDuration
|
|
|
46
47
|
/** Panel akordeonu. Umieszczaj w `<KptAccordion>` albo używaj samodzielnie. */
|
|
47
48
|
export function KptAccordionPanel({ title, disabled = false, expanded, defaultExpanded = false, onExpandedChange, index, className, style, children, ...rest }) {
|
|
48
49
|
const accordion = useContext(AccordionContext);
|
|
50
|
+
const uid = useKptId(undefined, 'kpt-accordion');
|
|
51
|
+
const headerId = `${uid}-header`;
|
|
52
|
+
const bodyId = `${uid}-body`;
|
|
49
53
|
const [standalone, setStandalone] = useControllableState({
|
|
50
54
|
value: expanded,
|
|
51
55
|
defaultValue: defaultExpanded,
|
|
@@ -63,7 +67,9 @@ export function KptAccordionPanel({ title, disabled = false, expanded, defaultEx
|
|
|
63
67
|
setStandalone(willOpen);
|
|
64
68
|
onExpandedChange?.(willOpen);
|
|
65
69
|
};
|
|
66
|
-
return (_jsx("div", { className: cn('kpt-accordion-panel-host', className), style: { ['--kpt-anim-duration']: accordion?.animMs ?? '500ms', ...style }, ...rest, children: _jsxs("div", { className: cn('kpt-accordion-panel', isOpen && 'is-open'), children: [_jsxs("button", { type: "button", className: "kpt-accordion-panel__header", "aria-expanded": isOpen, disabled: disabled, onClick: toggle, children: [_jsx("span", { className: "kpt-accordion-panel__title", children: title }), _jsx(KptIcon, { name: "chevron-down", className: cn('kpt-accordion-panel__chevron', isOpen && 'is-open') })] }), _jsx("div", { className: cn('kpt-accordion-panel__collapse', isOpen && 'is-open'),
|
|
70
|
+
return (_jsx("div", { className: cn('kpt-accordion-panel-host', className), style: { ['--kpt-anim-duration']: accordion?.animMs ?? '500ms', ...style }, ...rest, children: _jsxs("div", { className: cn('kpt-accordion-panel', isOpen && 'is-open'), children: [_jsxs("button", { type: "button", className: "kpt-accordion-panel__header", id: headerId, "aria-expanded": isOpen, "aria-controls": bodyId, disabled: disabled, onClick: toggle, children: [_jsx("span", { className: "kpt-accordion-panel__title", children: title }), _jsx(KptIcon, { name: "chevron-down", className: cn('kpt-accordion-panel__chevron', isOpen && 'is-open') })] }), _jsx("div", { className: cn('kpt-accordion-panel__collapse', isOpen && 'is-open'),
|
|
71
|
+
// Treść to region nazwany nagłówkiem — czytnik ogłasza nie tylko stan, ale i to, czego dotyczy.
|
|
72
|
+
role: "region", id: bodyId, "aria-labelledby": headerId, inert: !isOpen, "aria-hidden": !isOpen, children: _jsx("div", { className: "kpt-accordion-panel__collapse-inner", children: _jsx("div", { className: "kpt-accordion-panel__body", children: children }) }) })] }) }));
|
|
67
73
|
}
|
|
68
74
|
KptAccordionPanel.kptAccordionSlot = 'panel';
|
|
69
75
|
KptAccordion.Panel = KptAccordionPanel;
|
package/dist/accordion/llms.txt
CHANGED
|
@@ -7,11 +7,13 @@ Import: `import { KptAccordion } from '@konce-pt/react';`
|
|
|
7
7
|
<div class="kpt-accordion-host">
|
|
8
8
|
<div class="kpt-accordion-panel-host" style="--kpt-anim-duration: 500ms">
|
|
9
9
|
<div class="kpt-accordion-panel is-open">
|
|
10
|
-
<button class="kpt-accordion-panel__header"
|
|
10
|
+
<button class="kpt-accordion-panel__header" id="kpt-accordion-r1-header"
|
|
11
|
+
aria-expanded="true" aria-controls="kpt-accordion-r1-body">
|
|
11
12
|
<span class="kpt-accordion-panel__title">Section 1</span>
|
|
12
13
|
<span class="kpt-icon-host kpt-accordion-panel__chevron is-open">…</span>
|
|
13
14
|
</button>
|
|
14
|
-
<div class="kpt-accordion-panel__collapse is-open"
|
|
15
|
+
<div class="kpt-accordion-panel__collapse is-open" role="region" id="kpt-accordion-r1-body"
|
|
16
|
+
aria-labelledby="kpt-accordion-r1-header">
|
|
15
17
|
<div class="kpt-accordion-panel__collapse-inner">
|
|
16
18
|
<div class="kpt-accordion-panel__body">…children…</div>
|
|
17
19
|
</div>
|
|
@@ -52,5 +54,7 @@ nothing inside takes focus or reaches a screen reader.
|
|
|
52
54
|
`--kpt-anim-duration` set by the accordion on each panel.
|
|
53
55
|
|
|
54
56
|
## Accessibility
|
|
55
|
-
|
|
56
|
-
`aria-
|
|
57
|
+
Each header is a native button with `aria-expanded` and `aria-controls`; the body is a
|
|
58
|
+
`role="region"` named by its header (`aria-labelledby`), so a screen reader announces both the state
|
|
59
|
+
and what it belongs to. A collapsed body is `inert` and `aria-hidden`, skipped by the keyboard and
|
|
60
|
+
the screen reader alike.
|
package/dist/alert/alert.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ComponentPropsWithRef, ReactNode } from 'react';
|
|
2
2
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
3
|
import type { KptAlertVariant } from '../shared/types.ts';
|
|
4
4
|
export type { KptAlertVariant };
|
|
@@ -9,17 +9,23 @@ export interface KptAlertProps extends Omit<ComponentPropsWithRef<'div'>, 'title
|
|
|
9
9
|
dismissible?: boolean;
|
|
10
10
|
/** Wywoływane po zamknięciu przez użytkownika. */
|
|
11
11
|
onClosed?: () => void;
|
|
12
|
+
/** Widoczność (sterowana); bez niej alert sam trzyma stan. */
|
|
13
|
+
visible?: boolean;
|
|
14
|
+
defaultVisible?: boolean;
|
|
15
|
+
onVisibleChange?: (visible: boolean) => void;
|
|
12
16
|
children?: ReactNode;
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
15
19
|
* Komunikat inline (alert). Warianty info/success/warning/danger, opcjonalny tytuł i zamykanie.
|
|
16
20
|
*
|
|
17
21
|
* Zamknięcie chowa komunikat atrybutem `hidden` (tak samo jak port Angulara), a nie odmontowuje
|
|
18
|
-
* go
|
|
19
|
-
*
|
|
22
|
+
* go. `visible`/`onVisibleChange` pozwalają pokazać go ponownie z zewnątrz.
|
|
23
|
+
*
|
|
24
|
+
* Rola idzie za wagą komunikatu: `info` i `success` to `role="status"` (czytnik ogłosi je uprzejmie,
|
|
25
|
+
* nie przerywając), `warning` i `danger` — `role="alert"` (natychmiast).
|
|
20
26
|
*
|
|
21
27
|
* @example
|
|
22
28
|
* <KptAlert variant="success" title="Zapisano">Zmiany zostały zapisane.</KptAlert>
|
|
23
29
|
* <KptAlert variant="warning" dismissible onClosed={onClosed}>Uwaga…</KptAlert>
|
|
24
30
|
*/
|
|
25
|
-
export declare function KptAlert({ variant, title, dismissible, onClosed, locale, dictionary, className, children, ...rest }: Readonly<KptAlertProps>): import("react").JSX.Element;
|
|
31
|
+
export declare function KptAlert({ variant, title, dismissible, onClosed, visible, defaultVisible, onVisibleChange, locale, dictionary, className, children, ...rest }: Readonly<KptAlertProps>): import("react").JSX.Element;
|
package/dist/alert/alert.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
3
2
|
import { cn } from "../internal/cn.js";
|
|
3
|
+
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
4
4
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
5
5
|
import { KptIcon } from "../icon/icon.js";
|
|
6
6
|
const ICONS = {
|
|
@@ -13,19 +13,26 @@ const ICONS = {
|
|
|
13
13
|
* Komunikat inline (alert). Warianty info/success/warning/danger, opcjonalny tytuł i zamykanie.
|
|
14
14
|
*
|
|
15
15
|
* Zamknięcie chowa komunikat atrybutem `hidden` (tak samo jak port Angulara), a nie odmontowuje
|
|
16
|
-
* go
|
|
17
|
-
*
|
|
16
|
+
* go. `visible`/`onVisibleChange` pozwalają pokazać go ponownie z zewnątrz.
|
|
17
|
+
*
|
|
18
|
+
* Rola idzie za wagą komunikatu: `info` i `success` to `role="status"` (czytnik ogłosi je uprzejmie,
|
|
19
|
+
* nie przerywając), `warning` i `danger` — `role="alert"` (natychmiast).
|
|
18
20
|
*
|
|
19
21
|
* @example
|
|
20
22
|
* <KptAlert variant="success" title="Zapisano">Zmiany zostały zapisane.</KptAlert>
|
|
21
23
|
* <KptAlert variant="warning" dismissible onClosed={onClosed}>Uwaga…</KptAlert>
|
|
22
24
|
*/
|
|
23
|
-
export function KptAlert({ variant = 'info', title = '', dismissible = false, onClosed, locale, dictionary, className, children, ...rest }) {
|
|
25
|
+
export function KptAlert({ variant = 'info', title = '', dismissible = false, onClosed, visible, defaultVisible = true, onVisibleChange, locale, dictionary, className, children, ...rest }) {
|
|
24
26
|
const { t } = useKptMessages({ locale, dictionary });
|
|
25
|
-
const [
|
|
27
|
+
const [isVisible, setVisible] = useControllableState({
|
|
28
|
+
value: visible,
|
|
29
|
+
defaultValue: defaultVisible,
|
|
30
|
+
onChange: onVisibleChange,
|
|
31
|
+
});
|
|
32
|
+
const role = variant === 'warning' || variant === 'danger' ? 'alert' : 'status';
|
|
26
33
|
const dismiss = () => {
|
|
27
34
|
setVisible(false);
|
|
28
35
|
onClosed?.();
|
|
29
36
|
};
|
|
30
|
-
return (_jsx("div", { className: cn('kpt-alert-host', className), hidden: !
|
|
37
|
+
return (_jsx("div", { className: cn('kpt-alert-host', className), hidden: !isVisible, ...rest, children: _jsxs("div", { className: "kpt-alert", "data-variant": variant, role: role, children: [_jsx(KptIcon, { className: "kpt-alert__icon", name: ICONS[variant] }), _jsxs("div", { className: "kpt-alert__content", children: [title ? _jsx("div", { className: "kpt-alert__title", children: title }) : null, _jsx("div", { className: "kpt-alert__message", children: children })] }), dismissible ? (_jsx("button", { className: "kpt-alert__close", type: "button", onClick: dismiss, "aria-label": t('common.close'), children: _jsx(KptIcon, { name: "x" }) })) : null] }) }));
|
|
31
38
|
}
|
package/dist/alert/llms.txt
CHANGED
|
@@ -5,7 +5,7 @@ Import: `import { KptAlert } from '@konce-pt/react';`
|
|
|
5
5
|
|
|
6
6
|
## DOM
|
|
7
7
|
<div class="kpt-alert-host">
|
|
8
|
-
<div class="kpt-alert" data-variant="success" role="
|
|
8
|
+
<div class="kpt-alert" data-variant="success" role="status">
|
|
9
9
|
<span class="kpt-icon-host kpt-alert__icon">…</span>
|
|
10
10
|
<div class="kpt-alert__content">
|
|
11
11
|
<div class="kpt-alert__title">Saved</div>
|
|
@@ -19,14 +19,14 @@ Import: `import { KptAlert } from '@konce-pt/react';`
|
|
|
19
19
|
- `variant`: 'info' | 'success' | 'warning' | 'danger' (default 'info')
|
|
20
20
|
- `title`: string
|
|
21
21
|
- `dismissible`: boolean — adds the close button
|
|
22
|
+
- `visible` / `defaultVisible` / `onVisibleChange` — controlled or uncontrolled visibility
|
|
22
23
|
- `onClosed()` — fires after the user dismisses it
|
|
23
24
|
- `locale`, `dictionary` — i18n overrides for this component only
|
|
24
25
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
25
26
|
|
|
26
27
|
## Dismissal hides, it does not unmount
|
|
27
|
-
Closing sets `hidden` on the host, the same as the Angular port
|
|
28
|
-
|
|
29
|
-
the component — the internal state is a convenience, not the only way.
|
|
28
|
+
Closing sets `hidden` on the host, the same as the Angular port. `visible` / `onVisibleChange`
|
|
29
|
+
make it controllable, so the same alert can be shown again from outside.
|
|
30
30
|
|
|
31
31
|
## The icon follows the variant
|
|
32
32
|
Each variant picks its own icon from the built-in set (`info`, `circle-check`, `alert-triangle`,
|
|
@@ -41,5 +41,7 @@ The semantic colour pairs of each variant (`--kpt-color-info` / `-success` / `-w
|
|
|
41
41
|
`-danger` with their `-subtle` counterparts), `--kpt-radius-md`.
|
|
42
42
|
|
|
43
43
|
## Accessibility
|
|
44
|
-
The
|
|
45
|
-
|
|
44
|
+
The role follows the weight of the message: `info` and `success` are `role="status"` (read
|
|
45
|
+
politely, without interrupting), `warning` and `danger` are `role="alert"` (read at once). The icon
|
|
46
|
+
is decorative; the × button is labelled from `common.close`. After closing, focus falls back to the
|
|
47
|
+
page — move it yourself in the close handler when the alert was reached with the keyboard.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useCallback, useContext, useEffect, useMemo, } from 'react';
|
|
2
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
4
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
5
5
|
import { useKptId } from "../internal/use-id.js";
|
|
6
6
|
import { useKptBreakpointUp } from "../internal/use-breakpoint.js";
|
|
7
7
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
8
|
+
/** Pierwszy element panelu, który przyjmie fokus po otwarciu nakładki. */
|
|
9
|
+
const FOCUSABLE = 'a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex="-1"])';
|
|
8
10
|
const KptAppShellContext = createContext(null);
|
|
9
11
|
/**
|
|
10
12
|
* Szkielet aplikacji: pasek u góry, panel boczny i treść.
|
|
@@ -73,7 +75,31 @@ export function KptAppShell({ toolbar, sidenav, children, sidenavOpen, defaultSi
|
|
|
73
75
|
}), [isSidenavOpen, isOverlay, sidenavId, setSidenavOpen, closeSidenav]);
|
|
74
76
|
// Scrim blokuje wskaźnik, ale nie klawiaturę — treść pod nakładką nie może łapać fokusu.
|
|
75
77
|
const contentInert = isOverlay && isSidenavOpen;
|
|
76
|
-
|
|
78
|
+
// Otwarta nakładka robi treść `inert`, więc fokus zostawiony w niej (albo na przycisku menu)
|
|
79
|
+
// nie miałby dokąd pójść. Po otwarciu przenosimy go do panelu, po zamknięciu oddajemy tam,
|
|
80
|
+
// skąd przyszedł — chyba że użytkownik zdążył go przenieść gdzie indziej. Stan `null` przed
|
|
81
|
+
// pierwszym renderem: nakładka otwarta już przy starcie nie zabiera fokusu.
|
|
82
|
+
const asideRef = useRef(null);
|
|
83
|
+
const overlayWasOpen = useRef(null);
|
|
84
|
+
const returnFocusTo = useRef(null);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
const was = overlayWasOpen.current;
|
|
87
|
+
overlayWasOpen.current = contentInert;
|
|
88
|
+
const panel = asideRef.current;
|
|
89
|
+
if (was === null || was === contentInert || !panel)
|
|
90
|
+
return;
|
|
91
|
+
const active = document.activeElement;
|
|
92
|
+
if (contentInert) {
|
|
93
|
+
returnFocusTo.current = active instanceof HTMLElement && !panel.contains(active) ? active : null;
|
|
94
|
+
(panel.querySelector(FOCUSABLE) ?? panel).focus();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const focusLost = !active || active === document.body || panel.contains(active);
|
|
98
|
+
if (focusLost && returnFocusTo.current?.isConnected)
|
|
99
|
+
returnFocusTo.current.focus();
|
|
100
|
+
returnFocusTo.current = null;
|
|
101
|
+
}, [contentInert]);
|
|
102
|
+
return (_jsx(KptAppShellContext.Provider, { value: context, children: _jsxs("div", { className: cn('kpt-app-shell', dockedOpen && 'kpt-app-shell--nav-open', compactOpen && 'kpt-app-shell--nav-compact-open', className), "data-sidenav-mode": sidenavMode, "data-sidenav-breakpoint": sidenavBreakpoint, "data-sidenav-rail": sidenavRailBreakpoint ?? undefined, ...rest, children: [toolbar, _jsxs("div", { className: "kpt-app-shell__body", children: [_jsx("aside", { ref: asideRef, className: "kpt-app-shell__sidenav", role: "navigation", tabIndex: -1, id: sidenavId, "aria-label": sidenavLabel || t('appShell.navigation'), children: sidenav }), _jsx("div", { className: "kpt-app-shell__scrim", "aria-hidden": "true", onClick: closeSidenav }), _jsx("main", { className: "kpt-app-shell__content", inert: contentInert, children: children })] })] }) }));
|
|
77
103
|
}
|
|
78
104
|
/** Dostęp do stanu shella. Poza `KptAppShell` rzuca — to zawsze błąd użycia. */
|
|
79
105
|
export function useKptAppShell() {
|
package/dist/app-shell/llms.txt
CHANGED
|
@@ -57,3 +57,6 @@ decides WHICH state the button flips. The scrim is always in the DOM; CSS contro
|
|
|
57
57
|
- The panel is an `<aside role="navigation">` with a label and an `id` for `aria-controls`.
|
|
58
58
|
- A closed docked panel leaves the tab order through `visibility`, correct on the first frame.
|
|
59
59
|
- While the overlay is open the content gets `inert`, and Escape closes the panel.
|
|
60
|
+
- Opening the overlay moves focus into the panel (its first focusable element, or the `<aside>`
|
|
61
|
+
itself); closing it returns focus to where it came from — usually the menu button — unless the user
|
|
62
|
+
has already moved it elsewhere. An overlay that is open when the page loads does not take focus.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ComponentPropsWithRef } from 'react';
|
|
2
|
-
|
|
2
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
|
+
export interface KptAutocompleteProps extends Omit<ComponentPropsWithRef<'input'>, 'value' | 'defaultValue' | 'onChange'>, KptI18nOverrides {
|
|
3
4
|
options?: readonly string[];
|
|
4
5
|
value?: string;
|
|
5
6
|
defaultValue?: string;
|
|
@@ -12,7 +13,11 @@ export interface KptAutocompleteProps extends Omit<ComponentPropsWithRef<'input'
|
|
|
12
13
|
* Pole z podpowiedziami. Wartością jest wpisany tekst, nie wybrana opcja — podpowiedzi
|
|
13
14
|
* skracają pisanie, ale nie ograniczają wpisu, więc pole nadaje się też do wartości spoza listy.
|
|
14
15
|
*
|
|
16
|
+
* Wzorzec WAI-ARIA „combobox z listą”: fokus zostaje w polu, aktywną podpowiedź ogłasza
|
|
17
|
+
* `aria-activedescendant`, a liczbę podpowiedzi (albo ich brak) — region `polite`. Dopasowanie
|
|
18
|
+
* pomija wielkość liter i diakrytyki („lodz” znajduje „Łódź”).
|
|
19
|
+
*
|
|
15
20
|
* @example
|
|
16
21
|
* <KptAutocomplete options={cities} value={city} onValueChange={setCity} placeholder="Miasto…" />
|
|
17
22
|
*/
|
|
18
|
-
export declare function KptAutocomplete({ options, value, defaultValue, onValueChange, invalid, touched, onTouch, disabled, className, onBlur, onFocus, onKeyDown, ref, ...rest }: Readonly<KptAutocompleteProps>): import("react").JSX.Element;
|
|
23
|
+
export declare function KptAutocomplete({ options, value, defaultValue, onValueChange, invalid, touched, onTouch, disabled, className, onBlur, onFocus, onKeyDown, ref, id, 'aria-describedby': ariaDescribedBy, locale, dictionary, ...rest }: Readonly<KptAutocompleteProps>): import("react").JSX.Element;
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useMemo, useRef, useState, } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
4
|
import { composeRefs } from "../internal/compose-refs.js";
|
|
5
5
|
import { KptOverlay } from "../internal/overlay.js";
|
|
6
6
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
7
|
+
import { foldMentionText } from '@konce-pt/mention';
|
|
8
|
+
import { joinIds, useKptFieldA11y } from "../form-field/form-field-context.js";
|
|
9
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
7
10
|
/**
|
|
8
11
|
* Pole z podpowiedziami. Wartością jest wpisany tekst, nie wybrana opcja — podpowiedzi
|
|
9
12
|
* skracają pisanie, ale nie ograniczają wpisu, więc pole nadaje się też do wartości spoza listy.
|
|
10
13
|
*
|
|
14
|
+
* Wzorzec WAI-ARIA „combobox z listą”: fokus zostaje w polu, aktywną podpowiedź ogłasza
|
|
15
|
+
* `aria-activedescendant`, a liczbę podpowiedzi (albo ich brak) — region `polite`. Dopasowanie
|
|
16
|
+
* pomija wielkość liter i diakrytyki („lodz” znajduje „Łódź”).
|
|
17
|
+
*
|
|
11
18
|
* @example
|
|
12
19
|
* <KptAutocomplete options={cities} value={city} onValueChange={setCity} placeholder="Miasto…" />
|
|
13
20
|
*/
|
|
14
|
-
export function KptAutocomplete({ options = [], value, defaultValue = '', onValueChange, invalid = false, touched, onTouch, disabled = false, className, onBlur, onFocus, onKeyDown, ref, ...rest }) {
|
|
21
|
+
export function KptAutocomplete({ options = [], value, defaultValue = '', onValueChange, invalid = false, touched, onTouch, disabled = false, className, onBlur, onFocus, onKeyDown, ref, id, 'aria-describedby': ariaDescribedBy, locale, dictionary, ...rest }) {
|
|
22
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
23
|
+
const field = useKptFieldA11y(id, 'kpt-autocomplete');
|
|
15
24
|
const inputRef = useRef(null);
|
|
16
25
|
const [current, setCurrent] = useControllableState({
|
|
17
26
|
value,
|
|
@@ -20,11 +29,26 @@ export function KptAutocomplete({ options = [], value, defaultValue = '', onValu
|
|
|
20
29
|
});
|
|
21
30
|
const [open, setOpen] = useState(false);
|
|
22
31
|
const [activeIndex, setActiveIndex] = useState(-1);
|
|
32
|
+
/** Wpisano coś od ostatniego otwarcia — dopiero wtedy ogłaszamy liczbę podpowiedzi. */
|
|
33
|
+
const [typed, setTyped] = useState(false);
|
|
23
34
|
const showInvalid = invalid && (touched ?? true);
|
|
24
35
|
const filtered = useMemo(() => {
|
|
25
|
-
const q = current.trim()
|
|
26
|
-
return q ? options.filter((option) => option
|
|
36
|
+
const q = foldMentionText(current.trim());
|
|
37
|
+
return q ? options.filter((option) => foldMentionText(option).includes(q)) : options;
|
|
27
38
|
}, [options, current]);
|
|
39
|
+
const panelOpen = open && filtered.length > 0;
|
|
40
|
+
const listId = `${field.id}-list`;
|
|
41
|
+
const optionId = (index) => `${field.id}-opt-${index}`;
|
|
42
|
+
const activeOptionId = panelOpen && activeIndex >= 0 && activeIndex < filtered.length ? optionId(activeIndex) : undefined;
|
|
43
|
+
const announcement = open && typed && current.trim()
|
|
44
|
+
? filtered.length
|
|
45
|
+
? t('autocomplete.results', { count: filtered.length })
|
|
46
|
+
: t('autocomplete.noResults')
|
|
47
|
+
: '';
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (activeOptionId)
|
|
50
|
+
document.getElementById(activeOptionId)?.scrollIntoView({ block: 'nearest' });
|
|
51
|
+
}, [activeOptionId]);
|
|
28
52
|
const pick = useCallback((option) => {
|
|
29
53
|
setCurrent(option);
|
|
30
54
|
setOpen(false);
|
|
@@ -38,7 +62,9 @@ export function KptAutocomplete({ options = [], value, defaultValue = '', onValu
|
|
|
38
62
|
}
|
|
39
63
|
else if (event.key === 'ArrowUp') {
|
|
40
64
|
event.preventDefault();
|
|
41
|
-
|
|
65
|
+
setOpen(true);
|
|
66
|
+
// Z pola (bez aktywnej) `↑` idzie na ostatnią podpowiedź.
|
|
67
|
+
setActiveIndex((index) => (index <= 0 ? filtered.length - 1 : index - 1));
|
|
42
68
|
}
|
|
43
69
|
else if (event.key === 'Enter') {
|
|
44
70
|
const option = filtered[activeIndex];
|
|
@@ -48,12 +74,22 @@ export function KptAutocomplete({ options = [], value, defaultValue = '', onValu
|
|
|
48
74
|
}
|
|
49
75
|
}
|
|
50
76
|
else if (event.key === 'Escape') {
|
|
51
|
-
|
|
77
|
+
// Pierwszy `Escape` zamyka listę, drugi (przy zamkniętej) czyści pole.
|
|
78
|
+
if (panelOpen) {
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
setOpen(false);
|
|
81
|
+
setActiveIndex(-1);
|
|
82
|
+
}
|
|
83
|
+
else if (current) {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
setCurrent('');
|
|
86
|
+
}
|
|
52
87
|
}
|
|
53
88
|
};
|
|
54
|
-
return (_jsxs("span", { className: "kpt-autocomplete-host", children: [_jsx("input", { ref: composeRefs(inputRef, ref), className: cn('kpt-autocomplete__input', className), type: "text", role: "combobox", "aria-expanded":
|
|
89
|
+
return (_jsxs("span", { className: "kpt-autocomplete-host", children: [_jsx("input", { ref: composeRefs(inputRef, ref), className: cn('kpt-autocomplete__input', className), type: "text", id: field.id, "aria-describedby": joinIds(ariaDescribedBy, field.describedBy), role: "combobox", "aria-autocomplete": "list", autoComplete: "off", "aria-expanded": panelOpen, "aria-controls": panelOpen ? listId : undefined, "aria-activedescendant": activeOptionId, "aria-invalid": showInvalid || undefined, disabled: disabled, value: current, onChange: (event) => {
|
|
55
90
|
setCurrent(event.target.value);
|
|
56
91
|
setOpen(true);
|
|
92
|
+
setTyped(true);
|
|
57
93
|
setActiveIndex(-1);
|
|
58
94
|
}, onFocus: (event) => {
|
|
59
95
|
onFocus?.(event);
|
|
@@ -61,12 +97,13 @@ export function KptAutocomplete({ options = [], value, defaultValue = '', onValu
|
|
|
61
97
|
}, onBlur: (event) => {
|
|
62
98
|
onBlur?.(event);
|
|
63
99
|
setOpen(false);
|
|
100
|
+
setTyped(false);
|
|
64
101
|
onTouch?.();
|
|
65
|
-
}, onKeyDown: handleKeyDown, ...rest }), _jsx(KptOverlay, { open:
|
|
102
|
+
}, onKeyDown: handleKeyDown, ...rest }), _jsx(KptOverlay, { open: panelOpen, anchor: inputRef.current, onClose: () => setOpen(false), matchAnchorWidth: true, withBackdrop: false, children: _jsx("ul", { className: "kpt-autocomplete__panel", role: "listbox", id: listId, "aria-label": rest['aria-label'], children: filtered.map((option, index) => (_jsx("li", { id: optionId(index), className: cn('kpt-autocomplete__option', index === activeIndex && 'is-active'), role: "option", "aria-selected": index === activeIndex,
|
|
66
103
|
// `mousedown` z zablokowanym domyślnym zachowaniem: klik w opcję nie może
|
|
67
104
|
// zabrać fokusu polu, bo `blur` zamknąłby panel przed wyborem.
|
|
68
105
|
onMouseDown: (event) => {
|
|
69
106
|
event.preventDefault();
|
|
70
107
|
pick(option);
|
|
71
|
-
}, onMouseEnter: () => setActiveIndex(index), children: option }, option))) }) })] }));
|
|
108
|
+
}, onMouseEnter: () => setActiveIndex(index), children: option }, option))) }) }), _jsx("span", { className: "kpt-autocomplete__live", "aria-live": "polite", children: announcement })] }));
|
|
72
109
|
}
|
|
@@ -35,6 +35,18 @@ one, Escape closes.
|
|
|
35
35
|
## Examples
|
|
36
36
|
<KptAutocomplete options={cities} value={city} onValueChange={setCity} placeholder="City…" />
|
|
37
37
|
|
|
38
|
+
## Screen reader and keyboard
|
|
39
|
+
The WAI-ARIA combobox-with-list pattern: focus stays in the field, `aria-autocomplete="list"`,
|
|
40
|
+
`aria-controls` and `aria-activedescendant` point at the active suggestion, and a polite live region
|
|
41
|
+
says how many suggestions there are (`autocomplete.results`) or that there are none
|
|
42
|
+
(`autocomplete.noResults`). Matching ignores case and diacritics ("lodz" finds "Łódź"). `↑` from the
|
|
43
|
+
field goes to the last suggestion; the first `Escape` closes the list, the second clears the field.
|
|
44
|
+
|
|
45
|
+
## Label and description
|
|
46
|
+
`id` goes to the native field, not the host; without one it comes from `KptFormField` or is
|
|
47
|
+
generated. Inside `KptFormField` the wrapper's `label[for]` points at it and the hint or error is
|
|
48
|
+
added to `aria-describedby` (merged with your own). Without a wrapper, name it with `aria-label`.
|
|
49
|
+
|
|
38
50
|
## Tokens
|
|
39
51
|
`--kpt-form-field-{bg,text,placeholder,border,border-focus,radius}`,
|
|
40
52
|
`--kpt-color-surface-raised` and `--kpt-elevation-3` for the panel.
|
package/dist/avatar/avatar.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ComponentPropsWithRef } from 'react';
|
|
2
2
|
import type { KptHostProps } from '../internal/types.ts';
|
|
3
3
|
export type KptAvatarSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
4
4
|
export type KptAvatarShape = 'circle' | 'square';
|
|
@@ -11,6 +11,8 @@ export interface KptAvatarProps extends Omit<ComponentPropsWithRef<'span'>, 'chi
|
|
|
11
11
|
icon?: string | null;
|
|
12
12
|
size?: KptAvatarSize;
|
|
13
13
|
shape?: KptAvatarShape;
|
|
14
|
+
/** Ukrywa awatar przed czytnikiem ekranu, gdy imię jest już napisane obok. */
|
|
15
|
+
decorative?: boolean;
|
|
14
16
|
/**
|
|
15
17
|
* Propsy wrappera hosta (`.kpt-avatar-host`). To on jest elementem układu, więc klasy
|
|
16
18
|
* pozycjonujące awatar w rodzicu — jak `kpt-card__avatar` — idą właśnie tutaj.
|
|
@@ -30,4 +32,4 @@ export interface KptAvatarProps extends Omit<ComponentPropsWithRef<'span'>, 'chi
|
|
|
30
32
|
* <KptAvatar name="Anna Kowalska" size="lg" />
|
|
31
33
|
* <KptAvatar icon="user" shape="square" />
|
|
32
34
|
*/
|
|
33
|
-
export declare function KptAvatar({ src, name, icon, size, shape, hostProps, className, ...rest }: Readonly<KptAvatarProps>): import("react").JSX.Element;
|
|
35
|
+
export declare function KptAvatar({ src, name, icon, size, shape, decorative, hostProps, className, ...rest }: Readonly<KptAvatarProps>): import("react").JSX.Element;
|
package/dist/avatar/avatar.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
2
3
|
import { cn } from "../internal/cn.js";
|
|
3
4
|
import { KptIcon } from "../icon/icon.js";
|
|
4
5
|
/**
|
|
@@ -14,10 +15,14 @@ import { KptIcon } from "../icon/icon.js";
|
|
|
14
15
|
* <KptAvatar name="Anna Kowalska" size="lg" />
|
|
15
16
|
* <KptAvatar icon="user" shape="square" />
|
|
16
17
|
*/
|
|
17
|
-
export function KptAvatar({ src = null, name = '', icon = null, size = 'md', shape = 'circle', hostProps, className, ...rest }) {
|
|
18
|
+
export function KptAvatar({ src = null, name = '', icon = null, size = 'md', shape = 'circle', decorative = false, hostProps, className, ...rest }) {
|
|
18
19
|
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
// Zdjęcie, które się nie wczytało — awatar wraca wtedy do inicjałów (albo ikony). Pamiętamy,
|
|
21
|
+
// którego `src` dotyczy błąd, więc nowy adres próbuje się wczytać od nowa.
|
|
22
|
+
const [brokenSrc, setBrokenSrc] = useState(null);
|
|
23
|
+
const showImage = !!src && brokenSrc !== src;
|
|
24
|
+
const labelled = !decorative && !showImage && !!name;
|
|
25
|
+
return (_jsx("span", { className: cn('kpt-avatar-host', hostClassName), ...hostRest, children: _jsx("span", { className: cn('kpt-avatar', className), ...rest, "data-size": size, "data-shape": shape, title: decorative ? undefined : name || undefined, role: labelled ? 'img' : undefined, "aria-label": labelled ? name : undefined, "aria-hidden": decorative || undefined, children: showImage ? (_jsx("img", { className: "kpt-avatar__img", src: src, alt: decorative ? '' : name, onError: () => setBrokenSrc(src) })) : name ? (_jsx("span", { className: "kpt-avatar__initials", "aria-hidden": "true", children: initialsOf(name) })) : icon ? (_jsx(KptIcon, { name: icon })) : null }) }));
|
|
21
26
|
}
|
|
22
27
|
/** Do dwóch pierwszych liter z kolejnych członów nazwy. */
|
|
23
28
|
function initialsOf(name) {
|
package/dist/avatar/llms.txt
CHANGED
|
@@ -16,12 +16,14 @@ Import: `import { KptAvatar } from '@konce-pt/react';`
|
|
|
16
16
|
- `icon`: string | null — a registry icon name, used when there is neither photo nor name
|
|
17
17
|
- `size`: 'sm' | 'md' | 'lg' | 'xl' (default 'md')
|
|
18
18
|
- `shape`: 'circle' | 'square' (default 'circle')
|
|
19
|
+
- `decorative`: boolean — hides the avatar from screen readers (the name is written next to it)
|
|
19
20
|
- `hostProps` — props for the `.kpt-avatar-host` wrapper
|
|
20
21
|
- `className`, `ref` and every other `<span>` prop go to the inner `.kpt-avatar`
|
|
21
22
|
|
|
22
23
|
## Fallback order
|
|
23
|
-
`src` → `name` (up to two initials, uppercased) → `icon`.
|
|
24
|
-
|
|
24
|
+
`src` → `name` (up to two initials, uppercased) → `icon`. A photo that fails to load drops to the
|
|
25
|
+
next step by itself, and a new `src` is tried again. With none of them the avatar renders as an
|
|
26
|
+
empty shape, which is a valid placeholder.
|
|
25
27
|
|
|
26
28
|
## Positioning inside a parent
|
|
27
29
|
The host is the layout element, so classes that place the avatar in its parent go to `hostProps` —
|
|
@@ -37,6 +39,8 @@ the overlapping avatar on a card is `hostProps={{ className: 'kpt-card__avatar'
|
|
|
37
39
|
`--kpt-color-muted`, `--kpt-color-on-muted`, `--kpt-radius-full`, `--kpt-radius-md` (square).
|
|
38
40
|
|
|
39
41
|
## Accessibility
|
|
40
|
-
`name`
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
`name` goes into the native `title` (initials alone say nothing). With a photo the name is the
|
|
43
|
+
image's `alt`; without one the avatar is `role="img"` with `aria-label`, and the initials are
|
|
44
|
+
`aria-hidden`. A photo that fails to load falls back to the initials (or the icon); a new `src` is
|
|
45
|
+
tried again. `decorative` removes the avatar from the accessibility tree — use it when the name is
|
|
46
|
+
already written next to it.
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
2
3
|
import { type KptAvatarSize } from '../avatar/avatar.tsx';
|
|
3
4
|
export interface KptAvatarItem {
|
|
4
5
|
name?: string;
|
|
5
6
|
src?: string;
|
|
6
7
|
icon?: string;
|
|
7
8
|
}
|
|
8
|
-
export interface KptAvatarGroupProps extends ComponentPropsWithRef<'div'
|
|
9
|
+
export interface KptAvatarGroupProps extends ComponentPropsWithRef<'div'>, KptI18nOverrides {
|
|
9
10
|
avatars?: readonly KptAvatarItem[];
|
|
10
11
|
/** Ile awatarów pokazać, zanim pojawi się „+N". */
|
|
11
12
|
max?: number;
|
|
12
13
|
size?: KptAvatarSize;
|
|
14
|
+
/** Nazwa grupy dla czytników ekranu, np. „Członkowie projektu”; bez niej grupa nie ma roli. */
|
|
15
|
+
label?: string;
|
|
13
16
|
}
|
|
14
17
|
/**
|
|
15
18
|
* Grupa nakładających się awatarów z licznikiem „+N". Buduje na `KptAvatar`, więc każdy element
|
|
@@ -18,4 +21,4 @@ export interface KptAvatarGroupProps extends ComponentPropsWithRef<'div'> {
|
|
|
18
21
|
* @example
|
|
19
22
|
* <KptAvatarGroup avatars={team} max={4} />
|
|
20
23
|
*/
|
|
21
|
-
export declare function KptAvatarGroup({ avatars, max, size, className, ...rest }: Readonly<KptAvatarGroupProps>): import("react").JSX.Element;
|
|
24
|
+
export declare function KptAvatarGroup({ avatars, max, size, label, locale, dictionary, className, ...rest }: Readonly<KptAvatarGroupProps>): import("react").JSX.Element;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../internal/cn.js";
|
|
3
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
3
4
|
import { KptAvatar } from "../avatar/avatar.js";
|
|
4
5
|
/**
|
|
5
6
|
* Grupa nakładających się awatarów z licznikiem „+N". Buduje na `KptAvatar`, więc każdy element
|
|
@@ -8,8 +9,15 @@ import { KptAvatar } from "../avatar/avatar.js";
|
|
|
8
9
|
* @example
|
|
9
10
|
* <KptAvatarGroup avatars={team} max={4} />
|
|
10
11
|
*/
|
|
11
|
-
export function KptAvatarGroup({ avatars = [], max = 4, size = 'md', className, ...rest }) {
|
|
12
|
+
export function KptAvatarGroup({ avatars = [], max = 4, size = 'md', label = '', locale, dictionary, className, ...rest }) {
|
|
13
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
12
14
|
const shown = avatars.slice(0, max);
|
|
13
15
|
const overflow = Math.max(0, avatars.length - max);
|
|
14
|
-
|
|
16
|
+
// Sam „+2” nic czytnikowi nie mówi — licznik ma nazwę „i 2 więcej” i podpowiedź z imionami.
|
|
17
|
+
const hiddenNames = avatars
|
|
18
|
+
.slice(max)
|
|
19
|
+
.map((avatar) => avatar.name)
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.join(', ');
|
|
22
|
+
return (_jsx("div", { className: cn('kpt-avatar-group-host', className), ...rest, children: _jsxs("div", { className: "kpt-avatar-group", "data-size": size, role: label ? 'group' : undefined, "aria-label": label || undefined, children: [shown.map((avatar, index) => (_jsx("span", { className: "kpt-avatar-group__item", children: _jsx(KptAvatar, { name: avatar.name || '', src: avatar.src || null, icon: avatar.icon || null, size: size }) }, index))), overflow > 0 ? (_jsx("span", { className: "kpt-avatar-group__item kpt-avatar-group__more", role: "img", "data-size": size, "aria-label": t('avatarGroup.more', { count: overflow }), title: hiddenNames || undefined, children: _jsxs("span", { "aria-hidden": "true", children: ["+", overflow] }) })) : null] }) }));
|
|
15
23
|
}
|
|
@@ -5,9 +5,10 @@ Import: `import { KptAvatarGroup } from '@konce-pt/react';`
|
|
|
5
5
|
|
|
6
6
|
## DOM
|
|
7
7
|
<div class="kpt-avatar-group-host">
|
|
8
|
-
<div class="kpt-avatar-group" data-size="lg">
|
|
8
|
+
<div class="kpt-avatar-group" data-size="lg" role="group" aria-label="Project members">
|
|
9
9
|
<span class="kpt-avatar-group__item">…KptAvatar…</span>…
|
|
10
|
-
<span class="kpt-avatar-group__item kpt-avatar-group__more" data-size="lg"
|
|
10
|
+
<span class="kpt-avatar-group__item kpt-avatar-group__more" role="img" data-size="lg"
|
|
11
|
+
aria-label="2 more" title="Marta, Tomasz"><span aria-hidden="true">+2</span></span>
|
|
11
12
|
</div>
|
|
12
13
|
</div>
|
|
13
14
|
|
|
@@ -15,6 +16,7 @@ Import: `import { KptAvatarGroup } from '@konce-pt/react';`
|
|
|
15
16
|
- `avatars`: `KptAvatarItem[]` — `{ name?, src?, icon? }`
|
|
16
17
|
- `max`: number (default 4) — how many to show before "+N"
|
|
17
18
|
- `size`: `'sm' | 'md' | 'lg' | 'xl'`
|
|
19
|
+
- `label`: string — the group's name; with it the row is `role="group"`
|
|
18
20
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
19
21
|
|
|
20
22
|
## One list, three fallbacks
|
|
@@ -34,5 +36,6 @@ Every avatar keeps the `title` from `KptAvatar`, so hovering tells you who it is
|
|
|
34
36
|
The overlap ring uses `--kpt-color-surface`, the "+N" chip `--kpt-color-muted`.
|
|
35
37
|
|
|
36
38
|
## Accessibility
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
Every avatar keeps its own name. With `label` the row is `role="group"` with that name. The “+N”
|
|
40
|
+
counter is `role="img"` named from `avatarGroup.more` (`{count}` — “2 more”), with the hidden names
|
|
41
|
+
in its `title`. The overlap uses `margin-inline-start`, so it mirrors in RTL.
|
package/dist/badge/badge.d.ts
CHANGED
|
@@ -12,6 +12,13 @@ export interface KptBadgeProps extends ComponentPropsWithRef<'span'> {
|
|
|
12
12
|
overlay?: boolean;
|
|
13
13
|
/** Powyżej tej wartości licznik pokazuje `99+`. */
|
|
14
14
|
max?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Znaczenie plakietki dla czytnika ekranu, np. „3 nieprzeczytane”. Sama liczba („3”) albo kropka
|
|
17
|
+
* nic nie mówią; z `label` widoczny tekst jest ukryty przed czytnikiem, a odczytywana jest etykieta.
|
|
18
|
+
*/
|
|
19
|
+
label?: string;
|
|
20
|
+
/** Chowa plakietkę, gdy `value` to `0` albo `null` (nie dotyczy `dot`). */
|
|
21
|
+
hideZero?: boolean;
|
|
15
22
|
children?: ReactNode;
|
|
16
23
|
}
|
|
17
24
|
/**
|
|
@@ -22,4 +29,4 @@ export interface KptBadgeProps extends ComponentPropsWithRef<'span'> {
|
|
|
22
29
|
* <KptBadge value={128} overlay><KptIcon name="menu" /></KptBadge>
|
|
23
30
|
* <KptBadge dot variant="success" overlay><KptAvatar name="AK" /></KptBadge>
|
|
24
31
|
*/
|
|
25
|
-
export declare function KptBadge({ value, variant, size, dot, overlay, max, className, children, ...rest }: Readonly<KptBadgeProps>): import("react").JSX.Element;
|
|
32
|
+
export declare function KptBadge({ value, variant, size, dot, overlay, max, label, hideZero, className, children, ...rest }: Readonly<KptBadgeProps>): import("react").JSX.Element;
|
package/dist/badge/badge.js
CHANGED
|
@@ -8,7 +8,8 @@ import { cn } from "../internal/cn.js";
|
|
|
8
8
|
* <KptBadge value={128} overlay><KptIcon name="menu" /></KptBadge>
|
|
9
9
|
* <KptBadge dot variant="success" overlay><KptAvatar name="AK" /></KptBadge>
|
|
10
10
|
*/
|
|
11
|
-
export function KptBadge({ value = null, variant = 'primary', size = 'md', dot = false, overlay = false, max = 99, className, children, ...rest }) {
|
|
11
|
+
export function KptBadge({ value = null, variant = 'primary', size = 'md', dot = false, overlay = false, max = 99, label = '', hideZero = false, className, children, ...rest }) {
|
|
12
12
|
const display = typeof value === 'number' && value > max ? `${max}+` : (value ?? '');
|
|
13
|
-
|
|
13
|
+
const hiddenZero = hideZero && !dot && (value === 0 || value === null || value === '');
|
|
14
|
+
return (_jsxs("span", { className: cn('kpt-badge-host', className), ...rest, children: [children, hiddenZero ? null : (_jsxs("span", { className: cn('kpt-badge', overlay && 'kpt-badge--overlay', dot && 'kpt-badge--dot'), "data-variant": variant, "data-size": size, children: [_jsx("span", { "aria-hidden": label ? true : undefined, children: dot ? '' : display }), label ? _jsx("span", { className: "kpt-badge__label", children: label }) : null] }))] }));
|
|
14
15
|
}
|