@konce-pt/react 0.16.1 → 0.18.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 +23 -19
- package/dist/carousel/carousel.d.ts +10 -2
- package/dist/carousel/carousel.js +21 -5
- package/dist/carousel/llms.txt +7 -1
- package/dist/charts/chart.js +26 -12
- package/dist/charts/llms.txt +11 -1
- package/dist/data-table/data-table.d.ts +8 -1
- package/dist/data-table/data-table.js +30 -11
- package/dist/data-table/llms.txt +6 -1
- package/dist/dialog/dialog.js +7 -0
- package/dist/galleria/galleria.d.ts +6 -0
- package/dist/galleria/galleria.js +70 -4
- package/dist/galleria/llms.txt +29 -2
- package/dist/image/image.d.ts +4 -0
- package/dist/image/image.js +55 -4
- package/dist/image/llms.txt +15 -0
- package/dist/input/input.d.ts +13 -2
- package/dist/input/input.js +49 -3
- package/dist/input/llms.txt +20 -0
- package/dist/popover/llms.txt +3 -0
- package/dist/public-api.d.ts +5 -3
- package/dist/public-api.js +1 -0
- package/dist/select/llms.txt +5 -1
- package/dist/select/select.d.ts +24 -2
- package/dist/select/select.js +0 -19
- package/dist/tabs/llms.txt +40 -8
- package/dist/tabs/tabs.d.ts +32 -3
- package/dist/tabs/tabs.js +39 -7
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -77,14 +77,14 @@ import '@konce-pt/grid'; // optional - only if you use the layout c
|
|
|
77
77
|
bundle, which is why both ports render identically. The Angular package injects those styles at
|
|
78
78
|
runtime; React has no other source for them, so this import is not optional.
|
|
79
79
|
|
|
80
|
-
Dark theme: set `<html data-theme="dark"
|
|
80
|
+
Dark theme: set `<html data-theme="dark">`. It is not switched automatically - toggle the attribute yourself, e.g. from `matchMedia('(prefers-color-scheme: dark)')`.
|
|
81
81
|
|
|
82
82
|
**Next.js App Router / Server Components:** every interactive component module starts with
|
|
83
83
|
`'use client'`, so you can import components directly into a server component - no wrapper file.
|
|
84
84
|
|
|
85
85
|
> **No CDK here.** Components with a floating panel (`KptSelect`, `KptPopover`, `KptMenu`,
|
|
86
|
-
> `KptAutocomplete`, `KptDatepicker`...) portal into `document.body` through
|
|
87
|
-
>
|
|
86
|
+
> `KptAutocomplete`, `KptDatepicker`...) portal into `document.body` through `KptOverlay`
|
|
87
|
+
> (also exported for your own floating panels), so `@angular/cdk/overlay-prebuilt.css` is **not** needed - positioning lives in the
|
|
88
88
|
> component, the look in `@konce-pt/styles/components`. Inside an open `KptDialog` those panels
|
|
89
89
|
> portal into the `<dialog>` instead, because `showModal()` moves it to the browser top layer.
|
|
90
90
|
|
|
@@ -120,6 +120,7 @@ export function Login() {
|
|
|
120
120
|
onValueChange={setEmail}
|
|
121
121
|
onTouch={() => setTouched((t) => ({ ...t, email: true }))}
|
|
122
122
|
invalid={Boolean(emailError)}
|
|
123
|
+
touched={touched.email}
|
|
123
124
|
/>
|
|
124
125
|
</KptFormField>
|
|
125
126
|
|
|
@@ -131,6 +132,7 @@ export function Login() {
|
|
|
131
132
|
onValueChange={setPassword}
|
|
132
133
|
onTouch={() => setTouched((t) => ({ ...t, password: true }))}
|
|
133
134
|
invalid={Boolean(passwordError)}
|
|
135
|
+
touched={touched.password}
|
|
134
136
|
/>
|
|
135
137
|
</KptFormField>
|
|
136
138
|
|
|
@@ -141,7 +143,8 @@ export function Login() {
|
|
|
141
143
|
```
|
|
142
144
|
|
|
143
145
|
`KptFormField` generates the control id and publishes it through context, so `label[for]` and
|
|
144
|
-
`input[id]` match with no configuration.
|
|
146
|
+
`input[id]` match with no configuration. `touched` gates the invalid state (`aria-invalid`, red border)
|
|
147
|
+
until the field is blurred; leave it out and `invalid` shows right away. Drop `value`/`onValueChange` and the field becomes
|
|
145
148
|
uncontrolled - the value lives in the DOM, and a native `<form action={...}>` or React Hook Form's
|
|
146
149
|
`register()` picks it up as-is.
|
|
147
150
|
|
|
@@ -166,13 +169,13 @@ Data table with sorting, filtering, pagination and a custom cell:
|
|
|
166
169
|
|
|
167
170
|
| Category | Components |
|
|
168
171
|
| --- | --- |
|
|
169
|
-
| **Forms** | `KptInput`, `KptTextarea`, `KptSelect`, `KptAutocomplete`, `KptCheckbox`, `KptSwitch`, `KptSwitchGroup`, `KptRadioGroup`, `KptSlider`, `KptRating`, `KptDatepicker`, `KptDateRange`, `KptClock`, `KptColorPicker`, `KptFileUpload`, `KptInputNumber`, `KptPassword`, `KptInputOtp`, `KptChipsInput`, `KptInputMask`, `KptListbox`, `KptKnob`, `KptRichText`, `KptFormField` |
|
|
172
|
+
| **Forms** | `KptInput`, `KptTextarea`, `KptSelect`, `KptAutocomplete`, `KptCheckbox`, `KptSwitch`, `KptSwitchGroup`, `KptRadioGroup`, `KptSlider`, `KptRating`, `KptDatepicker`, `KptDateRange`, `KptClock`, `KptColorPicker`, `KptFileUpload`, `KptInputNumber`, `KptPassword`, `KptInputOtp`, `KptChipsInput`, `KptInputMask`, `KptListbox`, `KptKnob`, `KptRichText`, `KptFormField`, `KptSpeechButton` + `KptSpeechWave` (dictation) |
|
|
170
173
|
| **Buttons & actions** | `KptButton`, `KptIconButton`, `KptButtonGroup`, `KptFab`, `KptSplitButton`, `KptSpeedDial` |
|
|
171
|
-
| **Layout** | `KptAppShell`, `KptToolbar`, `KptSidenav`, `KptCard` (+ `Header`, `Footer`, `Media`, `Title`...), `KptDivider`, `KptPanel`, `KptFieldset`, `KptSplitter`, `KptScrollTop` |
|
|
174
|
+
| **Layout** | `KptAppShell`, `KptToolbar`, `KptSidenav`, `KptCard` (+ `Header`, `Footer`, `Media`, `Title`...), `KptDivider`, `KptPanel`, `KptFieldset`, `KptSplitter`, `KptScrollTop`, `KptBackdrop` |
|
|
172
175
|
| **Navigation** | `KptTabs`, `KptAccordion`, `KptBreadcrumb`, `KptStepper`, `KptMenu`, `KptMenubar`, `KptMegamenu`, `KptContextMenu` |
|
|
173
176
|
| **Data** | `KptDataTable`, `KptPaginator`, `KptTree`, `KptTimeline`, `KptCarousel`, `KptDataView`, `KptPickList`, `KptOrderList`, `KptGalleria`, `KptMeterGroup` |
|
|
174
|
-
| **Feedback & overlay** | `KptAlert`, `KptDialog`, `kptToast` + `KptToastContainer`, `KptTooltip`, `KptPopover`, `KptDrawer`, `KptBottomSheet`, `kptConfirm`, `KptBadge`, `KptChip`, `KptAvatar`, `KptAvatarGroup`, `KptSpinner`, `KptProgress`, `KptSkeleton`, `KptEmpty`, `KptImage`, `KptAuth` |
|
|
175
|
-
| **Subpaths** | `@konce-pt/react/charts` (`KptChart`), `/chat` (`KptChat`, `KptChatMessage`, `KptChatTyping`, `KptChatComposer`, `KptChatDock`), `/roadmap` (`KptRoadmap`), `/grid` (`KptGrid`, `KptCol`, `KptFlex`), `/map` (`KptMap` +
|
|
177
|
+
| **Feedback & overlay** | `KptAlert`, `KptDialog`, `kptToast` + `KptToastContainer`, `KptTooltip`, `KptPopover`, `KptPeek`, `KptDrawer`, `KptBottomSheet`, `kptConfirm`, `KptBadge`, `KptChip`, `KptAvatar`, `KptAvatarGroup`, `KptIcon`, `KptSpinner`, `KptLoader`, `KptUpdate`, `KptProgress`, `KptSkeleton`, `KptEmpty`, `KptImage`, `KptAuth`, `KptMention`, `KptMentions` |
|
|
178
|
+
| **Subpaths** | `@konce-pt/react/charts` (`KptChart`), `/chat` (`KptChat`, `KptChatMessage`, `KptChatTyping`, `KptChatComposer`, `KptChatDock`), `/roadmap` (`KptRoadmap`), `/grid` (`KptGrid`, `KptCol`, `KptFlex`), `/map` (`KptMap` + `KptMapLegend`, `KptMapPoiCard`, `KptMapSearch`, `KptMapCreateDialog`, `KptMapHoursField`, `KptMapImageField`), `/icons` (full Tabler set) |
|
|
176
179
|
|
|
177
180
|
The heavy pieces (charts, chat, roadmap, maps, the full icon set) live in subpaths on purpose, so the core
|
|
178
181
|
entry point stays small. Every component ships an `llms.txt` API sheet next to its source.
|
|
@@ -199,7 +202,7 @@ classes - that is what the SCSS reads.
|
|
|
199
202
|
|
|
200
203
|
## Icons
|
|
201
204
|
|
|
202
|
-
**
|
|
205
|
+
**98 icons are built in and need no configuration** - what the components themselves draw plus the
|
|
203
206
|
staples of an application shell (`sun`, `moon`, `bell`, `settings`, `users`, `logout`,
|
|
204
207
|
`layout-dashboard`, `home`, `activity`, `chart-bar`, `brand-github` ...). A typical admin panel needs
|
|
205
208
|
nothing else.
|
|
@@ -286,12 +289,12 @@ export function App() {
|
|
|
286
289
|
// <KptMotionProvider enabled={false}> - no motion at all (visual tests, kiosk)
|
|
287
290
|
```
|
|
288
291
|
|
|
289
|
-
| Key | Values | Components (per-component
|
|
292
|
+
| Key | Values | Components (per-component prop) |
|
|
290
293
|
| --- | --- | --- |
|
|
291
294
|
| `enabled` | `true` \| `false` | All. `false` also zeroes CSS durations (`data-kpt-motion="off"`, `--kpt-motion-scale: 0` on `<html>`); loading indicators slow down instead of stopping |
|
|
292
295
|
| `layout` | `spring` \| `arc` \| `morph` \| `none` | DataView, PickList, OrderList (`layoutMotion`) |
|
|
293
296
|
| `collapse` | `spring` \| `fade` \| `none` | Accordion, Fieldset, Panel, Tree (`collapseMotion`) |
|
|
294
|
-
| `enter` | `spring` \| `fade` \| `none` | Chat, ChatMessage, ChatDock, FormField (`enterMotion`); Password meter, ChipsInput and Select chips |
|
|
297
|
+
| `enter` | `spring` \| `fade` \| `none` | Chat, ChatMessage, ChatDock, FormField (`enterMotion`); Password meter, ChipsInput and Select chips, Stepper panels, the Image preview, the Galleria slide |
|
|
295
298
|
| `toggle` | `spring` \| `fade` \| `none` | Checkbox, RadioGroup, Switch (`toggleMotion`); Rating stars, ButtonGroup selection |
|
|
296
299
|
|
|
297
300
|
Precedence: `enabled: false` always wins, then the component's own prop, then the provider value,
|
|
@@ -307,7 +310,7 @@ are available for your own views in [`@konce-pt/motion`](https://www.npmjs.com/p
|
|
|
307
310
|
|
|
308
311
|
## Documentation
|
|
309
312
|
|
|
310
|
-
- **Playground** - live demos with copyable code: [ui.konce.pt/react](https://ui.konce.pt/react).
|
|
313
|
+
- **Playground** - live demos with copyable code: [ui.konce.pt/react](https://ui.konce.pt/react/).
|
|
311
314
|
- **`llms.txt`** - LLM-friendly API sheets per component, shipped inside the package; the
|
|
312
315
|
package-wide sheet lives at [ui.konce.pt/llms/react/llms.txt](https://ui.konce.pt/llms/react/llms.txt)
|
|
313
316
|
(PL mirror: [llms-pl.txt](https://ui.konce.pt/llms/react/llms-pl.txt)).
|
|
@@ -371,20 +374,21 @@ import '@konce-pt/grid'; // opcjonalnie - klasy układu
|
|
|
371
374
|
własnego bundla - stąd identyczny wygląd obu portów. Angular wstrzykuje te style w runtime, React
|
|
372
375
|
nie ma dla nich innego źródła, więc ten import nie jest opcjonalny.
|
|
373
376
|
|
|
374
|
-
Motyw ciemny: `<html data-theme="dark">`
|
|
377
|
+
Motyw ciemny: `<html data-theme="dark">` - nie włącza się sam, atrybut przełączasz samodzielnie
|
|
378
|
+
(np. z `matchMedia('(prefers-color-scheme: dark)')`). Nadpisania `--kpt-color-*`
|
|
375
379
|
w arkuszu importowanym **po** `@konce-pt/styles`.
|
|
376
380
|
|
|
377
381
|
**Next.js App Router / Server Components:** każdy moduł interaktywnego komponentu zaczyna się od
|
|
378
382
|
`'use client'`, więc komponenty importujesz wprost do komponentu serwerowego - bez pliku-opakowania.
|
|
379
383
|
|
|
380
384
|
> **CDK nie jest tu potrzebny.** Komponenty z panelem (`KptSelect`, `KptPopover`, `KptMenu`,
|
|
381
|
-
> `KptAutocomplete`, `KptDatepicker`...) renderują go przez
|
|
382
|
-
> `document.body`, więc `@angular/cdk/overlay-prebuilt.css` nie jest wymagany. W otwartym
|
|
385
|
+
> `KptAutocomplete`, `KptDatepicker`...) renderują go przez `KptOverlay` (eksportowany także dla
|
|
386
|
+
> własnych paneli) do portalu na `document.body`, więc `@angular/cdk/overlay-prebuilt.css` nie jest wymagany. W otwartym
|
|
383
387
|
> `KptDialog` panel trafia do `<dialog>`, bo `showModal()` przenosi go do górnej warstwy przeglądarki.
|
|
384
388
|
|
|
385
389
|
### Ikony
|
|
386
390
|
|
|
387
|
-
**
|
|
391
|
+
**98 ikon jest wbudowanych i nie wymaga konfiguracji** - to, co rysują same komponenty, plus staple
|
|
388
392
|
powłoki aplikacji (`sun`, `moon`, `bell`, `settings`, `users`, `logout`, `layout-dashboard`,
|
|
389
393
|
`home`, `activity`, `chart-bar`, `brand-github`...). Typowy panel administracyjny nie potrzebuje nic
|
|
390
394
|
więcej.
|
|
@@ -441,12 +445,12 @@ nakładki w portalach też go słuchają.
|
|
|
441
445
|
<KptMotionProvider enabled={false}>...</KptMotionProvider> {/* bez ruchu (testy wizualne, kiosk) */}
|
|
442
446
|
```
|
|
443
447
|
|
|
444
|
-
| Klucz | Wartości | Komponenty (
|
|
448
|
+
| Klucz | Wartości | Komponenty (prop per komponent) |
|
|
445
449
|
| --- | --- | --- |
|
|
446
450
|
| `enabled` | `true` \| `false` | Wszystkie. `false` zeruje też czasy w CSS (`data-kpt-motion="off"`, `--kpt-motion-scale: 0` na `<html>`); wskaźniki ładowania zwalniają zamiast stanąć |
|
|
447
451
|
| `layout` | `spring` \| `arc` \| `morph` \| `none` | DataView, PickList, OrderList (`layoutMotion`) |
|
|
448
452
|
| `collapse` | `spring` \| `fade` \| `none` | Accordion, Fieldset, Panel, Tree (`collapseMotion`) |
|
|
449
|
-
| `enter` | `spring` \| `fade` \| `none` | Chat, ChatMessage, ChatDock, FormField (`enterMotion`); miernik hasła, chipy ChipsInput i Select |
|
|
453
|
+
| `enter` | `spring` \| `fade` \| `none` | Chat, ChatMessage, ChatDock, FormField (`enterMotion`); miernik hasła, chipy ChipsInput i Select, panele Steppera, podgląd Image, slajd Galleria |
|
|
450
454
|
| `toggle` | `spring` \| `fade` \| `none` | Checkbox, RadioGroup, Switch (`toggleMotion`); gwiazdki Rating, wybór ButtonGroup |
|
|
451
455
|
|
|
452
456
|
Pierwszeństwo: `enabled: false` wygrywa zawsze, potem prop samego komponentu, potem wartość z providera,
|
|
@@ -463,7 +467,7 @@ Każdy komponent ma obok źródła plik `llms.txt` z opisem API.
|
|
|
463
467
|
|
|
464
468
|
### Dokumentacja
|
|
465
469
|
|
|
466
|
-
Playground z demami: [ui.konce.pt/react](https://ui.konce.pt/react). Opisy API dla modeli:
|
|
470
|
+
Playground z demami: [ui.konce.pt/react](https://ui.konce.pt/react/). Opisy API dla modeli:
|
|
467
471
|
[`llms.txt`](https://ui.konce.pt/llms/react/llms.txt) (EN) i
|
|
468
472
|
[`llms-pl.txt`](https://ui.konce.pt/llms/react/llms-pl.txt) (PL). Repozytorium:
|
|
469
473
|
[gitlab.com/konce-pt/koncept-ui](https://gitlab.com/konce-pt/koncept-ui).
|
|
@@ -9,6 +9,12 @@ export interface KptCarouselProps<T> extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
9
9
|
renderSlide: (item: T, index: number) => ReactNode;
|
|
10
10
|
/** Nazwa regionu dla czytników ekranu, np. "Nowości"; domyślnie `carousel.label`. */
|
|
11
11
|
label?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Zawijanie strzałek: za ostatnim slajdem "dalej" wraca do pierwszego, przed pierwszym "wstecz" -
|
|
14
|
+
* do ostatniego. `false` - strzałki stają na końcach (np. kroki, które mają początek i koniec).
|
|
15
|
+
* Domyślnie `true`.
|
|
16
|
+
*/
|
|
17
|
+
loop?: boolean;
|
|
12
18
|
}
|
|
13
19
|
/**
|
|
14
20
|
* Karuzela slajdów. Dane przez `items`, wygląd slajdu przez `renderSlide`.
|
|
@@ -19,9 +25,11 @@ export interface KptCarouselProps<T> extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
19
25
|
* Semantyka według wzorca WAI-ARIA "carousel" (bez autoodtwarzania): region z
|
|
20
26
|
* `aria-roledescription="karuzela"`, każdy slajd to grupa "Slajd 2 z 4", a slajdy poza widokiem są
|
|
21
27
|
* `inert` - `Tab` i czytnik ich nie widzą. Zmiana slajdu jest ogłaszana uprzejmie (`aria-live`
|
|
22
|
-
* na torze).
|
|
28
|
+
* na torze). Domyślnie strzałki zawijają: za ostatnim slajdem "dalej" wraca do pierwszego, przed
|
|
29
|
+
* pierwszym "wstecz" - do ostatniego. Z `loop={false}` strzałki stają na końcach i dostają
|
|
30
|
+
* `aria-disabled` zamiast `disabled`, żeby nie zgubić fokusu.
|
|
23
31
|
*
|
|
24
32
|
* @example
|
|
25
33
|
* <KptCarousel items={photos} renderSlide={(photo) => <img src={photo} style={{ width: '100%' }} />} />
|
|
26
34
|
*/
|
|
27
|
-
export declare function KptCarousel<T>({ items, activeIndex, defaultActiveIndex, onActiveIndexChange, renderSlide, label, locale, dictionary, className, ...rest }: Readonly<KptCarouselProps<T>>): import("react").JSX.Element;
|
|
35
|
+
export declare function KptCarousel<T>({ items, activeIndex, defaultActiveIndex, onActiveIndexChange, renderSlide, label, loop, locale, dictionary, className, ...rest }: Readonly<KptCarouselProps<T>>): import("react").JSX.Element;
|
|
@@ -13,12 +13,14 @@ import { KptIcon } from "../icon/icon.js";
|
|
|
13
13
|
* Semantyka według wzorca WAI-ARIA "carousel" (bez autoodtwarzania): region z
|
|
14
14
|
* `aria-roledescription="karuzela"`, każdy slajd to grupa "Slajd 2 z 4", a slajdy poza widokiem są
|
|
15
15
|
* `inert` - `Tab` i czytnik ich nie widzą. Zmiana slajdu jest ogłaszana uprzejmie (`aria-live`
|
|
16
|
-
* na torze).
|
|
16
|
+
* na torze). Domyślnie strzałki zawijają: za ostatnim slajdem "dalej" wraca do pierwszego, przed
|
|
17
|
+
* pierwszym "wstecz" - do ostatniego. Z `loop={false}` strzałki stają na końcach i dostają
|
|
18
|
+
* `aria-disabled` zamiast `disabled`, żeby nie zgubić fokusu.
|
|
17
19
|
*
|
|
18
20
|
* @example
|
|
19
21
|
* <KptCarousel items={photos} renderSlide={(photo) => <img src={photo} style={{ width: '100%' }} />} />
|
|
20
22
|
*/
|
|
21
|
-
export function KptCarousel({ items = [], activeIndex, defaultActiveIndex = 0, onActiveIndexChange, renderSlide, label, locale, dictionary, className, ...rest }) {
|
|
23
|
+
export function KptCarousel({ items = [], activeIndex, defaultActiveIndex = 0, onActiveIndexChange, renderSlide, label, loop = true, locale, dictionary, className, ...rest }) {
|
|
22
24
|
const { t } = useKptMessages({ locale, dictionary });
|
|
23
25
|
const [requested, setActive] = useControllableState({
|
|
24
26
|
value: activeIndex,
|
|
@@ -28,7 +30,21 @@ export function KptCarousel({ items = [], activeIndex, defaultActiveIndex = 0, o
|
|
|
28
30
|
// Indeks przycięty do listy: po skróceniu `items` (filtr) widok nie zostaje pusty.
|
|
29
31
|
const active = Math.min(Math.max(requested, 0), Math.max(items.length - 1, 0));
|
|
30
32
|
const goTo = (index) => setActive(Math.min(Math.max(index, 0), items.length - 1));
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
33
|
+
// Zawijanie ma sens od dwóch slajdów - przy jednym strzałki zostają wyłączone.
|
|
34
|
+
const wraps = loop && items.length > 1;
|
|
35
|
+
// Krok o jeden slajd; poza końcem - zawinięcie albo zatrzymanie, zależnie od `loop`.
|
|
36
|
+
const step = (delta) => {
|
|
37
|
+
const count = items.length;
|
|
38
|
+
if (count === 0)
|
|
39
|
+
return;
|
|
40
|
+
const target = active + delta;
|
|
41
|
+
if (target >= 0 && target < count)
|
|
42
|
+
setActive(target);
|
|
43
|
+
else if (wraps)
|
|
44
|
+
setActive((target + count) % count);
|
|
45
|
+
};
|
|
46
|
+
// Koniec, na którym strzałka staje - tylko bez zawijania.
|
|
47
|
+
const atStart = !wraps && active <= 0;
|
|
48
|
+
const atEnd = !wraps && active >= items.length - 1;
|
|
49
|
+
return (_jsx("div", { className: cn('kpt-carousel-host', className), ...rest, children: _jsxs("div", { className: "kpt-carousel", role: "region", "aria-roledescription": t('carousel.roleCarousel'), "aria-label": label || t('carousel.label'), children: [_jsx("div", { className: "kpt-carousel__viewport", children: _jsx("div", { className: "kpt-carousel__track", "aria-live": "polite", style: { transform: `translateX(calc(var(--_kpt-carousel-dir, 1) * -${active * 100}%))` }, children: items.map((item, index) => (_jsx("div", { className: "kpt-carousel__slide", role: "group", "aria-roledescription": t('carousel.roleSlide'), "aria-label": t('carousel.slideOf', { index: index + 1, total: items.length }), inert: index !== active, "aria-hidden": index === active ? undefined : true, children: renderSlide(item, index) }, index))) }) }), _jsx("button", { type: "button", className: "kpt-carousel__nav kpt-carousel__nav--prev", "aria-disabled": atStart || undefined, "aria-label": t('common.previous'), onClick: () => step(-1), children: _jsx(KptIcon, { name: "chevron-left" }) }), _jsx("button", { type: "button", className: "kpt-carousel__nav kpt-carousel__nav--next", "aria-disabled": atEnd || undefined, "aria-label": t('common.next'), onClick: () => step(1), children: _jsx(KptIcon, { name: "chevron-right" }) }), _jsx("div", { className: "kpt-carousel__dots", children: items.map((_, index) => (_jsx("button", { type: "button", className: cn('kpt-carousel__dot', index === active && 'is-active'), "aria-current": index === active ? 'true' : undefined, "aria-label": t('carousel.slide', { index: index + 1 }), onClick: () => goTo(index) }, index))) })] }) }));
|
|
34
50
|
}
|
package/dist/carousel/llms.txt
CHANGED
|
@@ -28,6 +28,7 @@ Import: `import { KptCarousel } from '@konce-pt/react';`
|
|
|
28
28
|
- `activeIndex` / `defaultActiveIndex` / `onActiveIndexChange`
|
|
29
29
|
- `renderSlide(item, index)` - required
|
|
30
30
|
- `label`: string - the region name; empty = `carousel.label`
|
|
31
|
+
- `loop`: boolean (default true) - the arrows wrap from the last slide to the first and back
|
|
31
32
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
32
33
|
|
|
33
34
|
## The render prop replaces the template
|
|
@@ -49,13 +50,18 @@ The arrows use `common.previous` / `common.next`, the dots `carousel.slide` (`{i
|
|
|
49
50
|
## Tokens
|
|
50
51
|
The active dot `--kpt-color-primary`; the arrows `--kpt-color-surface` (80% via `color-mix`, solid on hover) with `--kpt-elevation-2`; the slide transition `--kpt-motion-duration-base`.
|
|
51
52
|
|
|
53
|
+
## Looping
|
|
54
|
+
By default the arrows wrap: "next" on the last slide goes to the first one and "previous" on the first
|
|
55
|
+
goes to the last; the track rewinds across the slides in between. `loop={false}` stops the arrows at the
|
|
56
|
+
ends instead - for steps that have a beginning and an end (onboarding). There is no autoplay either way.
|
|
57
|
+
|
|
52
58
|
## Accessibility
|
|
53
59
|
The WAI-ARIA carousel pattern without autoplay. The root is `role="region"` with
|
|
54
60
|
`aria-roledescription` from `carousel.roleCarousel` ("carousel") and a name from `label` or
|
|
55
61
|
`carousel.label`. Each slide is `role="group"` with `aria-roledescription` from `carousel.roleSlide`
|
|
56
62
|
and a name from `carousel.slideOf` ("Slide 2 of 4"); slides outside the view are `inert` and
|
|
57
63
|
`aria-hidden`, so Tab never lands on an invisible link. The track is `aria-live="polite"`, so the
|
|
58
|
-
slide that comes in is announced.
|
|
64
|
+
slide that comes in is announced. With `loop` off the arrows use `aria-disabled` at the ends instead of `disabled`,
|
|
59
65
|
so they keep the focus; the active dot carries `aria-current`, and every dot has a 24 px target
|
|
60
66
|
(WCAG 2.5.8) with an 8 px visible dot. The transition is off under `prefers-reduced-motion`; in RTL
|
|
61
67
|
the track and the chevrons follow the reading direction (`:dir(rtl)`).
|
package/dist/charts/chart.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useId, useRef, useState } from 'react';
|
|
4
|
-
import { KptChartMotion, areaPath, bandScale, barLayout, chartAccessibleName, chartDataTable, dataScale, deltaKind, domainScale,
|
|
4
|
+
import { KptChartMotion, areaPath, axisBase, bandPath, bandScale, barLayout, chartScale, chartAccessibleName, chartDataTable, dataScale, deltaKind, domainScale, formatNumber, linePath, linearScale, pieSlices, plotArea, polarSlices, polarToCartesian, polygonPath, radarGrid, radarPoints, radialArea, sameChartShape, scaleRows, seriesColor, significantDelta, stackSeries, } from '@konce-pt/chart';
|
|
5
5
|
import { cn } from "../internal/cn.js";
|
|
6
6
|
import { useKptMotion } from "../motion/motion-context.js";
|
|
7
7
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
@@ -106,8 +106,11 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
106
106
|
const to = config.series.map((s) => [...s.data]);
|
|
107
107
|
const withAxis = config.type === 'line' || config.type === 'bar';
|
|
108
108
|
const visible = (rows) => rows.filter((_, i) => !hidden.has(`ser:${config.series[i]?.name ?? ''}`));
|
|
109
|
+
const axis = { valueAxis: config.valueAxis, stacked: config.stacked };
|
|
110
|
+
// Skala trzymana na czas ducha obejmuje oba stany - z wierszy już złożonych w stos.
|
|
111
|
+
const held = [...scaleRows(visible(from), config.stacked), ...scaleRows(visible(to), config.stacked)];
|
|
109
112
|
const scales = withAxis
|
|
110
|
-
? { start: dataScale(visible(from)), hold:
|
|
113
|
+
? { start: dataScale(visible(from), axis), hold: chartScale(held, { valueAxis: config.valueAxis }), end: dataScale(visible(to), axis) }
|
|
111
114
|
: null;
|
|
112
115
|
motion.transition(from, to, { delta: withAxis && config.animation?.delta !== false, scales });
|
|
113
116
|
}, [config, hidden, motion, motionConfig]);
|
|
@@ -204,16 +207,19 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
204
207
|
if (view.type !== 'line')
|
|
205
208
|
return null;
|
|
206
209
|
const active = view.series.map((s, i) => ({ s, i })).filter(({ s }) => !hidden.has(`ser:${s.name}`));
|
|
207
|
-
const
|
|
208
|
-
const [mn, mx] = extent(values.length ? values : [0]);
|
|
210
|
+
const rows = active.map(({ s }) => s.data);
|
|
209
211
|
const scale = frame.domain
|
|
210
212
|
? domainScale(frame.domain.target, frame.domain.min, frame.domain.max)
|
|
211
|
-
:
|
|
213
|
+
: chartScale(rows, { valueAxis: view.valueAxis, stacked: view.stacked });
|
|
214
|
+
// Stos: punkty serii na narastającej sumie, pasmo od linii serii pod nią.
|
|
215
|
+
const stack = view.stacked ? stackSeries(rows) : null;
|
|
212
216
|
const plot = plotArea(width, height, VERTICAL_INSETS);
|
|
213
217
|
const yScale = linearScale(scale.min, scale.max, plot.y + plot.height, plot.y);
|
|
214
218
|
const count = view.labels.length;
|
|
215
219
|
const xAt = (i) => count > 1 ? plot.x + (i / (count - 1)) * plot.width : plot.x + plot.width / 2;
|
|
216
|
-
|
|
220
|
+
// Punkt nie wychodzi poza oś - przypięte `valueAxis.min`/`max` mogą uciąć wartość.
|
|
221
|
+
const yAt = (v) => yScale(Math.min(scale.max, Math.max(scale.min, v)));
|
|
222
|
+
const baseline = yScale(axisBase(scale));
|
|
217
223
|
const interpolation = view.interpolation ?? 'linear';
|
|
218
224
|
const showPoints = view.points ?? true;
|
|
219
225
|
const grid = scale.ticks.map((t) => ({
|
|
@@ -234,13 +240,21 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
234
240
|
text: label,
|
|
235
241
|
anchor: 'middle',
|
|
236
242
|
}));
|
|
237
|
-
const series = active.map(({ s, i }) => ({
|
|
243
|
+
const series = active.map(({ s, i }, k) => ({
|
|
238
244
|
key: `ser:${s.name}`,
|
|
239
245
|
name: s.name,
|
|
240
246
|
color: seriesColor(s.color, i),
|
|
241
247
|
i,
|
|
242
|
-
points: s.data.map((v, idx) => ({ x: xAt(idx), y:
|
|
248
|
+
points: s.data.map((v, idx) => ({ x: xAt(idx), y: yAt(stack?.top[k]?.[idx] ?? v), value: v, label: view.labels[idx] ?? '' })),
|
|
249
|
+
floor: stack ? s.data.map((_, idx) => ({ x: xAt(idx), y: yAt(stack.base[k]?.[idx] ?? 0) })) : null,
|
|
243
250
|
}));
|
|
251
|
+
// Duch w stosie stoi na sumach poprzedniego stanu, tak jak bieżące punkty.
|
|
252
|
+
const ghostRows = ghostState
|
|
253
|
+
? (() => {
|
|
254
|
+
const prev = active.map(({ i }) => ghostState.prev[i] ?? []);
|
|
255
|
+
return view.stacked ? stackSeries(prev).top : prev;
|
|
256
|
+
})()
|
|
257
|
+
: [];
|
|
244
258
|
const crosshairCat = config.crosshair && activeIndex !== null ? catLabels[activeIndex] : undefined;
|
|
245
259
|
// Prowadnica i pigułka jadą razem z dymkiem.
|
|
246
260
|
const crossX = crosshairCat ? (frame.tip?.x ?? crosshairCat.x) : 0;
|
|
@@ -268,10 +282,10 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
268
282
|
? series.map((s) => {
|
|
269
283
|
const prev = ghostState.prev[s.i] ?? [];
|
|
270
284
|
const next = ghostState.next[s.i] ?? [];
|
|
271
|
-
const gpts = prev.map((v, idx) => ({ x: xAt(idx), y:
|
|
285
|
+
const gpts = (ghostRows[series.indexOf(s)] ?? prev).map((v, idx) => ({ x: xAt(idx), y: yAt(v) }));
|
|
272
286
|
return (_jsxs("g", { className: "kpt-chart__ghost", "aria-hidden": "true", children: [_jsx("path", { className: "kpt-chart__ghost-line", d: linePath(gpts, interpolation) }), gpts.map((p, idx) => (_jsx("line", { className: "kpt-chart__delta", "data-kind": deltaKind(prev[idx] ?? 0, next[idx] ?? 0), x1: p.x, x2: p.x, y1: p.y, y2: s.points[idx]?.y ?? p.y }, idx)))] }, `${ghostState.id}:${s.key}`));
|
|
273
287
|
})
|
|
274
|
-
: null, series.map((s) => (_jsxs("g", { className: cn('kpt-chart__series', dim(s.key) && 'kpt-chart__series--dim'), children: [view.area ? (_jsx("path", { className: "kpt-chart__area", d: areaPath(s.points, baseline, interpolation), style: { fill: s.color } })) : null, _jsx("path", { className: "kpt-chart__line", d: linePath(s.points, interpolation), style: { stroke: s.color }, pathLength: 1 }), showPoints
|
|
288
|
+
: null, series.map((s) => (_jsxs("g", { className: cn('kpt-chart__series', dim(s.key) && 'kpt-chart__series--dim'), children: [view.area ? (_jsx("path", { className: "kpt-chart__area", d: s.floor ? bandPath(s.points, s.floor, interpolation) : areaPath(s.points, baseline, interpolation), style: { fill: s.color } })) : null, _jsx("path", { className: "kpt-chart__line", d: linePath(s.points, interpolation), style: { stroke: s.color }, pathLength: 1 }), showPoints
|
|
275
289
|
? s.points.map((p, idx) => (_jsx("circle", { className: cn('kpt-chart__point', activeIndex === idx && 'kpt-chart__point--active'), cx: p.x, cy: p.y, r: 4, style: { fill: s.color, ['--kpt-chart-i']: idx } }, idx)))
|
|
276
290
|
: null] }, s.key))), sheen
|
|
277
291
|
? series.map((s) => (_jsx("path", { className: "kpt-chart__sheen-line", d: linePath(s.points, interpolation), stroke: `url(#${sheenId})`, style: { opacity: sheen.opacity }, "aria-hidden": "true" }, s.key)))
|
|
@@ -301,7 +315,7 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
301
315
|
const active = view.series.map((s, i) => ({ s, i })).filter(({ s }) => !hidden.has(`ser:${s.name}`));
|
|
302
316
|
const plot = plotArea(width, height, vertical ? VERTICAL_INSETS : HORIZONTAL_INSETS);
|
|
303
317
|
const radius = view.borderRadius ?? 4;
|
|
304
|
-
const layout = barLayout(active.map(({ s }) => s.data), view.labels.length, plot, orientation, radius, frame.domain ? domainScale(frame.domain.target, frame.domain.min, frame.domain.max) : undefined);
|
|
318
|
+
const layout = barLayout(active.map(({ s }) => s.data), view.labels.length, plot, orientation, radius, frame.domain ? domainScale(frame.domain.target, frame.domain.min, frame.domain.max) : undefined, { stacked: view.stacked, valueAxis: view.valueAxis });
|
|
305
319
|
const colors = active.map(({ s, i }) => seriesColor(s.color, i));
|
|
306
320
|
const names = active.map(({ s }) => s.name);
|
|
307
321
|
const cat = vertical
|
|
@@ -335,7 +349,7 @@ export function KptChart({ config, width = 600, height = 360, locale, dictionary
|
|
|
335
349
|
? (() => {
|
|
336
350
|
const prevRows = active.map(({ i }) => ghostState.prev[i] ?? []);
|
|
337
351
|
const nextRows = active.map(({ i }) => ghostState.next[i] ?? []);
|
|
338
|
-
return barLayout(prevRows, view.labels.length, plot, orientation, radius, layout.scale).bars.map((g, k) => {
|
|
352
|
+
return barLayout(prevRows, view.labels.length, plot, orientation, radius, layout.scale, { stacked: view.stacked }).bars.map((g, k) => {
|
|
339
353
|
const cur = layout.bars[k] ?? g;
|
|
340
354
|
const a = barEnd(g, vertical);
|
|
341
355
|
const z = barEnd(cur, vertical);
|
package/dist/charts/llms.txt
CHANGED
|
@@ -19,9 +19,19 @@ proportionally (`width={300} height={180}`) instead of letting a 600-unit chart
|
|
|
19
19
|
|
|
20
20
|
## The config is the API
|
|
21
21
|
`{ type, labels, series, points?, area?, interpolation?, orientation?, borderRadius?, innerRadius?,
|
|
22
|
-
crosshair?, tooltip?, legend?, title?, subtitle?, animation?, ariaLabel?, description? }` - the same shape the Angular port
|
|
22
|
+
valueAxis?, stacked?, crosshair?, tooltip?, legend?, title?, subtitle?, animation?, ariaLabel?, description? }` - the same shape the Angular port
|
|
23
23
|
takes, because both read it from `@konce-pt/chart`.
|
|
24
24
|
|
|
25
|
+
## Value axis and stacking (line, bar)
|
|
26
|
+
- `valueAxis?: { zero?, min?, max? }` - the value axis starts at zero by default, so a bar and the area under a line
|
|
27
|
+
show the size directly. `zero: false` fits the axis to the data (a weight of 79-90 kg is no longer a flat line under
|
|
28
|
+
the ceiling); `min` / `max` pin an end of the axis exactly and win over `zero` - a value beyond a pinned end is
|
|
29
|
+
cut at the edge. Bars and areas grow from zero, or from the end of the axis nearer to it
|
|
30
|
+
- `stacked?: boolean` - stacked series: bars on top of each other in one band per category (positive values up,
|
|
31
|
+
negative down; only the outer segment is rounded), lines as running totals with a band under each one (with
|
|
32
|
+
`area`). The axis covers the totals; the tooltip shows the values of the series, not the totals
|
|
33
|
+
- The rules live in `@konce-pt/chart`: `axisScale`, `chartScale`, `stackSeries`, `bandPath` (tested)
|
|
34
|
+
|
|
25
35
|
## The geometry lives in a package
|
|
26
36
|
Scales, paths, bar layout and arcs come from `@konce-pt/chart` - pure functions with `node --test`
|
|
27
37
|
coverage, shared with the Angular port. The component only turns them into SVG elements; there is
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ComponentPropsWithRef, type ReactNode } from 'react';
|
|
2
|
+
import { type KptColumnGroup } from '@konce-pt/table';
|
|
2
3
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
4
|
import { type KptSpeechColor } from '../speech/speech-button.tsx';
|
|
4
5
|
import type { KptCellEditEvent, KptCellEditRenderers, KptCellRenderers, KptColumn, KptTableLazyState } from './data-table-types.ts';
|
|
@@ -45,6 +46,12 @@ export interface KptDataTableProps<T> extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
45
46
|
/** Szerokość kolumny bez liczalnego `width` w trybie `virtualScroll` (`px`/`rem`/`em`). */
|
|
46
47
|
columnWidth?: string;
|
|
47
48
|
rowHeight?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Nagłówki grupowane: wiersz nad etykietami, w którym sąsiednie kolumny grupy dzielą komórkę
|
|
51
|
+
* (`[{ header: 'Makroskładniki', fields: ['carbs', 'protein', 'fat'] }]`). Liczony z kolumn
|
|
52
|
+
* widocznych, w bieżącej kolejności - ukryta albo przestawiona kolumna wypada z grupy sama.
|
|
53
|
+
*/
|
|
54
|
+
columnGroups?: readonly KptColumnGroup[];
|
|
48
55
|
reorderable?: boolean;
|
|
49
56
|
/**
|
|
50
57
|
* Przykleja kolumny systemowe (rozwijanie, zaznaczanie) do lewej krawędzi także bez kolumn `frozen`.
|
|
@@ -83,4 +90,4 @@ export interface KptDataTableProps<T> extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
83
90
|
/** Treść po lewej stronie paginatora (odpowiednik slotu `[kptPaginatorActions]`). */
|
|
84
91
|
paginatorActions?: ReactNode;
|
|
85
92
|
}
|
|
86
|
-
export declare function KptDataTable<T = Record<string, unknown>>({ columns, data, filterable, filterSpeech, filterSpeechLang, filterSpeechColor, filterRowPosition, pageSize, pageSizeOptions, selectable, loading, ariaLabel, caption, clickableRows, emptyMessage, rowKey, scrollHeight, virtualScroll, columnWidth, rowHeight, reorderable, frozenSystemColumns, exportable, exportFilename, lazy, totalRecords, expandable, multiExpand, summaryLabel, summaryValues, selection, defaultSelection, onSelectionChange, expandedRows, defaultExpandedRows, onExpandedRowsChange, hiddenFields, defaultHiddenFields, onHiddenFieldsChange, onRowClick, onLazyLoad, onCellEdit, cells, cellEditors, renderRowDetail, paginatorActions, locale, dictionary, className, style, ...rest }: Readonly<KptDataTableProps<T>>): import("react").JSX.Element;
|
|
93
|
+
export declare function KptDataTable<T = Record<string, unknown>>({ columns, data, filterable, filterSpeech, filterSpeechLang, filterSpeechColor, filterRowPosition, pageSize, pageSizeOptions, selectable, loading, ariaLabel, caption, clickableRows, emptyMessage, rowKey, scrollHeight, virtualScroll, columnWidth, rowHeight, reorderable, columnGroups, frozenSystemColumns, exportable, exportFilename, lazy, totalRecords, expandable, multiExpand, summaryLabel, summaryValues, selection, defaultSelection, onSelectionChange, expandedRows, defaultExpandedRows, onExpandedRowsChange, hiddenFields, defaultHiddenFields, onHiddenFieldsChange, onRowClick, onLazyLoad, onCellEdit, cells, cellEditors, renderRowDetail, paginatorActions, locale, dictionary, className, style, ...rest }: Readonly<KptDataTableProps<T>>): import("react").JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
4
|
-
import { aggregate, columnOffsets, createGlobalFilter, cssLengthToPx, isEmptyFilterValue, matchesColumnFilter, sortRows, toText, virtualColumnRange, virtualRange, } from '@konce-pt/table';
|
|
4
|
+
import { aggregate, columnOffsets, createGlobalFilter, cssLengthToPx, groupHeaderCells, isEmptyFilterValue, matchesColumnFilter, sortRows, toText, virtualColumnRange, virtualRange, } from '@konce-pt/table';
|
|
5
5
|
import { cn } from "../internal/cn.js";
|
|
6
6
|
import { useControllableState } from "../internal/use-controllable-state.js";
|
|
7
7
|
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
@@ -82,7 +82,7 @@ const EMPTY_FIELDS = [];
|
|
|
82
82
|
function getValue(row, field) {
|
|
83
83
|
return row[field];
|
|
84
84
|
}
|
|
85
|
-
export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, filterSpeech = false, filterSpeechLang, filterSpeechColor, filterRowPosition = 'bottom', pageSize = 0, pageSizeOptions = [5, 10, 20, 50], selectable = 'none', loading = false, ariaLabel = null, caption = null, clickableRows, emptyMessage, rowKey = null, scrollHeight = null, virtualScroll = false, columnWidth = '10rem', rowHeight = 41, reorderable = false, frozenSystemColumns = false, exportable = false, exportFilename = 'export.csv', lazy = false, totalRecords = null, expandable = false, multiExpand = true, summaryLabel, summaryValues = null, selection, defaultSelection = EMPTY_ROWS, onSelectionChange, expandedRows, defaultExpandedRows = EMPTY_ROWS, onExpandedRowsChange, hiddenFields, defaultHiddenFields = EMPTY_FIELDS, onHiddenFieldsChange, onRowClick, onLazyLoad, onCellEdit, cells, cellEditors, renderRowDetail, paginatorActions, locale, dictionary, className, style, ...rest }) {
|
|
85
|
+
export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, filterSpeech = false, filterSpeechLang, filterSpeechColor, filterRowPosition = 'bottom', pageSize = 0, pageSizeOptions = [5, 10, 20, 50], selectable = 'none', loading = false, ariaLabel = null, caption = null, clickableRows, emptyMessage, rowKey = null, scrollHeight = null, virtualScroll = false, columnWidth = '10rem', rowHeight = 41, reorderable = false, columnGroups, frozenSystemColumns = false, exportable = false, exportFilename = 'export.csv', lazy = false, totalRecords = null, expandable = false, multiExpand = true, summaryLabel, summaryValues = null, selection, defaultSelection = EMPTY_ROWS, onSelectionChange, expandedRows, defaultExpandedRows = EMPTY_ROWS, onExpandedRowsChange, hiddenFields, defaultHiddenFields = EMPTY_FIELDS, onHiddenFieldsChange, onRowClick, onLazyLoad, onCellEdit, cells, cellEditors, renderRowDetail, paginatorActions, locale, dictionary, className, style, ...rest }) {
|
|
86
86
|
const { t, locale: activeLocale } = useKptMessages({ locale, dictionary });
|
|
87
87
|
const uid = useId();
|
|
88
88
|
const rowsClickable = clickableRows ?? onRowClick !== undefined;
|
|
@@ -149,6 +149,8 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
|
|
|
149
149
|
const [viewport, setViewport] = useState({ height: 400, width: 1200 });
|
|
150
150
|
const scrollFrame = useRef(0);
|
|
151
151
|
const [theadOffset, setTheadOffset] = useState(0);
|
|
152
|
+
/** Offset sticky trzeciego wiersza nagłówka (grupy + etykiety + filtry). */
|
|
153
|
+
const [theadOffset2, setTheadOffset2] = useState(0);
|
|
152
154
|
const scrollRef = useRef(null);
|
|
153
155
|
const theadRef = useRef(null);
|
|
154
156
|
const editorHostRef = useRef(null);
|
|
@@ -234,14 +236,17 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
|
|
|
234
236
|
return;
|
|
235
237
|
requestLazyLoad({ pageIndex, pageSize: pageSizeState, sortField, sortDir, filter: filterText, columnFilters });
|
|
236
238
|
}, [lazy, pageIndex, pageSizeState, sortField, sortDir, filterText, columnFilters]);
|
|
237
|
-
//
|
|
239
|
+
// Offsety sticky drugiego i trzeciego wiersza nagłówka = wysokość jednego i dwóch pierwszych.
|
|
238
240
|
useLayoutEffect(() => {
|
|
239
|
-
const
|
|
241
|
+
const rows = theadRef.current?.rows;
|
|
240
242
|
// Dokładna wysokość, nie `offsetHeight`: ten zaokrągla (46,5 -> 47) i przy skali ekranu innej
|
|
241
243
|
// niż 100% między wierszami nagłówka zostawała szpara, przez którą prześwitywały wiersze.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
244
|
+
const h1 = rows?.[0]?.getBoundingClientRect().height ?? 0;
|
|
245
|
+
const h2 = rows?.[1]?.getBoundingClientRect().height ?? 0;
|
|
246
|
+
if (rows?.[0])
|
|
247
|
+
setTheadOffset(h1);
|
|
248
|
+
setTheadOffset2(h1 + h2);
|
|
249
|
+
}, [filterRowPosition, hasColumnFilters, visibleColumns, columnGroups]);
|
|
245
250
|
// Rozmiar viewportu dla wirtualizacji - także po zmianie rozmiaru kontenera bez przewinięcia.
|
|
246
251
|
useLayoutEffect(() => {
|
|
247
252
|
const el = scrollRef.current;
|
|
@@ -400,8 +405,22 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
|
|
|
400
405
|
const tableWidth = virtualScroll
|
|
401
406
|
? scrollable.pinnedWidth + scrollable.offsets[scrollable.offsets.length - 1]
|
|
402
407
|
: undefined;
|
|
403
|
-
/**
|
|
404
|
-
|
|
408
|
+
/**
|
|
409
|
+
* Wiersz grup nad etykietami: komórki nad pozycjami `slots`. Komórka przykleja się do lewej, gdy
|
|
410
|
+
* wszystkie jej kolumny są zamrożone - inaczej przewija się z nimi. Pusta lista = bez wiersza.
|
|
411
|
+
*/
|
|
412
|
+
const groupCells = columnGroups?.length
|
|
413
|
+
? groupHeaderCells(slots.map((s) => s.meta?.col.field ?? null), columnGroups).map((cell) => {
|
|
414
|
+
const metas = slots.slice(cell.start, cell.start + cell.span).map((s) => s.meta);
|
|
415
|
+
const spacer = metas.some((m) => m === null);
|
|
416
|
+
const frozen = !spacer && metas.every((m) => m?.frozen);
|
|
417
|
+
return { ...cell, key: slots[cell.start]?.key ?? String(cell.start), spacer, frozen, left: frozen ? (metas[0]?.left ?? undefined) : undefined };
|
|
418
|
+
})
|
|
419
|
+
: [];
|
|
420
|
+
/** Wiersz grup jest pierwszym wierszem nagłówka - przesuwa `aria-rowindex` pozostałych. */
|
|
421
|
+
const groupRows = groupCells.length ? 1 : 0;
|
|
422
|
+
/** Liczba wierszy nagłówka (grupy, etykiety, ewentualnie filtry) - przesunięcie `aria-rowindex`. */
|
|
423
|
+
const headerRowCount = groupRows + (hasColumnFilters ? 2 : 1);
|
|
405
424
|
/** `aria-colindex` komórki kolumny (od 1, z kolumnami systemowymi) - tylko w trybie wirtualnym. */
|
|
406
425
|
const colIndex = (meta) => virtualScroll ? leadingCells + meta.index + 1 : undefined;
|
|
407
426
|
// -- Przestawianie kolumn --
|
|
@@ -671,7 +690,7 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
|
|
|
671
690
|
return null;
|
|
672
691
|
}
|
|
673
692
|
};
|
|
674
|
-
const filterRowIndex = filterRowPosition === 'top' ? 1 : 2;
|
|
693
|
+
const filterRowIndex = groupRows + (filterRowPosition === 'top' ? 1 : 2);
|
|
675
694
|
const filterRow = (_jsxs("tr", { className: "kpt-data-table__filter-row", "aria-rowindex": virtualScroll ? filterRowIndex : undefined, children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable !== 'none' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft } })) : null, slots.map((slot) => slot.meta ? (_jsx("th", { style: { width: virtualScroll ? undefined : slot.meta.col.width, left: slot.meta.left }, "data-align": slot.meta.align, "aria-colindex": colIndex(slot.meta), className: cn(slot.meta.frozen && 'kpt-data-table__cell--frozen'), children: filterCell(slot.meta.col) }, slot.key)) : (hspacer(slot)))] }));
|
|
676
695
|
const editor = (row, col) => {
|
|
677
696
|
const custom = cellEditors?.[col.field];
|
|
@@ -743,7 +762,7 @@ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, f
|
|
|
743
762
|
startEdit(row, col, event);
|
|
744
763
|
}, children: editing ? (_jsx("div", { ref: editorHostRef, className: "kpt-data-table__editor", onClick: (event) => event.stopPropagation(), onBlur: onEditorFocusOut, children: editor(row, col) })) : cells?.[col.field] ? (cells[col.field](row, getValue(row, col.field))) : getValue(row, col.field) }, slot.key));
|
|
745
764
|
})] }), hasRowDetail && isExpanded(row) && !virtualScroll ? (_jsx("tr", { className: "kpt-data-table__detail-row", children: _jsx("td", { className: "kpt-data-table__detail", colSpan: colspan, children: renderRowDetail?.(row) }) })) : null] }, rowKey ? String(getValue(row, rowKey)) : index));
|
|
746
|
-
return (_jsxs("div", { className: cn('kpt-data-table', className), style: { ['--kpt-thead-offset']: `${theadOffset}px`, ...style }, ...rest, children: [filterable || exportable ? (_jsxs("div", { className: "kpt-data-table__toolbar", children: [filterable ? (_jsxs("div", { className: "kpt-data-table__search", "data-speech": filterSpeechState, children: [_jsx("input", { ref: filterRef, className: "kpt-data-table__filter", type: "search", placeholder: t('dataTable.search'), "aria-label": t('dataTable.search'), value: filterInput, onChange: (event) => onFilterInput(event.target.value) }), filterSpeech ? (_jsx(KptSpeechButton, { listeningColor: filterSpeechColor, speech: filterMic, size: "sm", unsupported: "hide", hostProps: { className: 'kpt-data-table__speech' } })) : null] })) : null, exportable ? (_jsx(KptButton, { variant: "outline", size: "sm", onClick: () => exportTableCsv(visibleColumns, (lazy ? data : sorted), exportFilename), children: t('dataTable.exportCsv') })) : null] })) : null, _jsx("div", { ref: scrollRef, className: "kpt-data-table__scroll", style: { maxHeight: scrollHeight ?? undefined }, onScroll: onScroll, children: _jsxs("table", { className: cn('kpt-data-table__table', hasFrozen && 'kpt-data-table__table--frozen', virtualScroll && 'kpt-data-table__table--virtual'), style: { width: tableWidth }, "aria-label": ariaLabel ?? undefined, "aria-busy": loading || undefined, "aria-rowcount": virtualScroll ? headerRowCount + sorted.length + (hasSummary ? 1 : 0) : undefined, "aria-colcount": virtualScroll ? leadingCells + visibleColumns.length : undefined, children: [caption ? _jsx("caption", { className: "kpt-data-table__caption", children: _jsx("span", { className: "kpt-data-table__caption-text", children: caption }) }) : null, virtualScroll ? (_jsxs("colgroup", { children: [hasRowDetail ? _jsx("col", { className: "kpt-data-table__col-expander" }) : null, selectable !== 'none' ? _jsx("col", { className: "kpt-data-table__col-checkbox" }) : null, slots.map((slot) => (_jsx("col", { style: { width: slot.width } }, slot.key)))] })) : null, _jsxs("thead", { ref: theadRef, children: [hasColumnFilters && filterRowPosition === 'top' ? filterRow : null, _jsxs("tr", { "aria-rowindex": virtualScroll ? (hasColumnFilters && filterRowPosition === 'top' ? 2 : 1) : undefined, children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable === 'multiple' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft }, children: _jsx("input", { type: "checkbox", checked: pageAllSelected, ref: (el) => {
|
|
765
|
+
return (_jsxs("div", { className: cn('kpt-data-table', className), style: { ['--kpt-thead-offset']: `${theadOffset}px`, ['--kpt-thead-offset-2']: `${theadOffset2}px`, ...style }, ...rest, children: [filterable || exportable ? (_jsxs("div", { className: "kpt-data-table__toolbar", children: [filterable ? (_jsxs("div", { className: "kpt-data-table__search", "data-speech": filterSpeechState, children: [_jsx("input", { ref: filterRef, className: "kpt-data-table__filter", type: "search", placeholder: t('dataTable.search'), "aria-label": t('dataTable.search'), value: filterInput, onChange: (event) => onFilterInput(event.target.value) }), filterSpeech ? (_jsx(KptSpeechButton, { listeningColor: filterSpeechColor, speech: filterMic, size: "sm", unsupported: "hide", hostProps: { className: 'kpt-data-table__speech' } })) : null] })) : null, exportable ? (_jsx(KptButton, { variant: "outline", size: "sm", onClick: () => exportTableCsv(visibleColumns, (lazy ? data : sorted), exportFilename), children: t('dataTable.exportCsv') })) : null] })) : null, _jsx("div", { ref: scrollRef, className: "kpt-data-table__scroll", style: { maxHeight: scrollHeight ?? undefined }, onScroll: onScroll, children: _jsxs("table", { className: cn('kpt-data-table__table', hasFrozen && 'kpt-data-table__table--frozen', virtualScroll && 'kpt-data-table__table--virtual'), style: { width: tableWidth }, "aria-label": ariaLabel ?? undefined, "aria-busy": loading || undefined, "aria-rowcount": virtualScroll ? headerRowCount + sorted.length + (hasSummary ? 1 : 0) : undefined, "aria-colcount": virtualScroll ? leadingCells + visibleColumns.length : undefined, children: [caption ? _jsx("caption", { className: "kpt-data-table__caption", children: _jsx("span", { className: "kpt-data-table__caption-text", children: caption }) }) : null, virtualScroll ? (_jsxs("colgroup", { children: [hasRowDetail ? _jsx("col", { className: "kpt-data-table__col-expander" }) : null, selectable !== 'none' ? _jsx("col", { className: "kpt-data-table__col-checkbox" }) : null, slots.map((slot) => (_jsx("col", { style: { width: slot.width } }, slot.key)))] })) : null, _jsxs("thead", { ref: theadRef, children: [groupCells.length ? (_jsxs("tr", { className: "kpt-data-table__group-row", "aria-rowindex": virtualScroll ? 1 : undefined, children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable !== 'none' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft } })) : null, groupCells.map((cell) => cell.spacer ? (_jsx("td", { className: "kpt-data-table__hspacer", colSpan: cell.span, "aria-hidden": "true" }, cell.key)) : (_jsx("th", { className: cn('kpt-data-table__group', !cell.header && 'kpt-data-table__group--empty', cell.frozen && 'kpt-data-table__cell--frozen'), style: { left: cell.left }, colSpan: cell.span > 1 ? cell.span : undefined, scope: cell.header ? 'colgroup' : undefined, children: cell.header }, cell.key)))] })) : null, hasColumnFilters && filterRowPosition === 'top' ? filterRow : null, _jsxs("tr", { "aria-rowindex": virtualScroll ? groupRows + (hasColumnFilters && filterRowPosition === 'top' ? 2 : 1) : undefined, children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable === 'multiple' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft }, children: _jsx("input", { type: "checkbox", checked: pageAllSelected, ref: (el) => {
|
|
747
766
|
if (el)
|
|
748
767
|
el.indeterminate = pageSomeSelected;
|
|
749
768
|
}, "aria-label": t('dataTable.selectAll'), onChange: toggleAllOnPage }) })) : selectable === 'single' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft } })) : null, slots.map((slot) => {
|
package/dist/data-table/llms.txt
CHANGED
|
@@ -18,6 +18,11 @@ rendered header.
|
|
|
18
18
|
|
|
19
19
|
## Props
|
|
20
20
|
- `columns` (required), `data`
|
|
21
|
+
- `columnGroups`: KptColumnGroup[] - grouped headers: a row above the column labels where adjacent columns of a
|
|
22
|
+
group share one cell (`[{ header: 'Macros', fields: ['carbs', 'protein', 'fat'] }]`, `scope="colgroup"`). It is
|
|
23
|
+
computed from the visible columns in their current order: a hidden column drops out, a group split by reordering
|
|
24
|
+
gets two cells with the same text, a column without a group gets an empty cell. A cell sticks to the left only
|
|
25
|
+
when all its columns are `frozen`. With column filters the header has three rows and all of them stay sticky
|
|
21
26
|
- `filterable`, `filterRowPosition` (`'bottom'` default), `pageSize` (0 = no pagination),
|
|
22
27
|
`pageSizeOptions`, `selectable` (`'none' | 'single' | 'multiple'`), `loading`, `emptyMessage`,
|
|
23
28
|
`rowKey`, `scrollHeight`, `virtualScroll` (rows **and** columns, see below), `columnWidth`
|
|
@@ -113,7 +118,7 @@ The toolbar, the states and the filter labels come from the `dataTable` namespac
|
|
|
113
118
|
the expanded row `--kpt-table-detail-bg`, the summary row `--kpt-table-summary-bg`.
|
|
114
119
|
|
|
115
120
|
## Not ported yet
|
|
116
|
-
|
|
121
|
+
A ready column-toggle UI is not part of either
|
|
117
122
|
port - `hiddenFields` is the hook to build one on.
|
|
118
123
|
|
|
119
124
|
## Filter by voice
|
package/dist/dialog/dialog.js
CHANGED
|
@@ -50,9 +50,16 @@ export function KptDialog({ open, onOpenChange, header = '', size = 'md', dismis
|
|
|
50
50
|
* `close` przychodzi z każdej drogi. Przy `dismissible=false` zamknąć wolno tylko z kodu:
|
|
51
51
|
* Chromium z CloseWatcherem pozwala anulować `cancel` jedynie po aktywacji użytkownika, więc drugi
|
|
52
52
|
* Escape z rzędu zamyka okno mimo `preventDefault` - wtedy otwieramy je z powrotem.
|
|
53
|
+
*
|
|
54
|
+
* Zdarzenie `close` jest asynchroniczne, więc może dojść, gdy okno jest już znowu otwarte: pod
|
|
55
|
+
* `StrictMode` efekt montuje się dwa razy (`showModal` -> sprzątanie `close` -> `showModal`)
|
|
56
|
+
* i dialog zamontowany od razu z `open` dostawał `onOpenChange(false)` od własnego sprzątania.
|
|
57
|
+
* Otwarty element znaczy, że to zamknięcie jest nieaktualne.
|
|
53
58
|
*/
|
|
54
59
|
const onClose = () => {
|
|
55
60
|
const el = dialogRef.current;
|
|
61
|
+
if (el?.open)
|
|
62
|
+
return;
|
|
56
63
|
if (!dismissible && open && el && !el.open) {
|
|
57
64
|
el.showModal();
|
|
58
65
|
return;
|
|
@@ -21,6 +21,12 @@ export interface KptGalleriaProps extends Omit<ComponentPropsWithRef<'div'>, 'on
|
|
|
21
21
|
* aktywną. Miniatura ma nazwę z `alt` obrazu i `aria-current` na aktywnej, a zmiana obrazu jest
|
|
22
22
|
* ogłaszana uprzejmie ("Obraz 2 z 4: ...") - dopiero po działaniu użytkownika, nie przy starcie.
|
|
23
23
|
*
|
|
24
|
+
* Ruch: nowe zdjęcie wjeżdża w kierunku zmiany na sprężynie, a poprzednie cofa się wolniej
|
|
25
|
+
* i przygasa (paralaksa); rodzaj z `enter` w `KptMotionProvider`, przy "ogranicz ruch" - przenikanie.
|
|
26
|
+
* Pasek miniatur nie ma paska przewijania: przewija się przeciąganiem (mysz, pióro, palec)
|
|
27
|
+
* albo strzałkami paska, krawędzie gasną tam, gdzie jest coś dalej, a aktywna miniatura
|
|
28
|
+
* zawsze wraca do widoku.
|
|
29
|
+
*
|
|
24
30
|
* @example
|
|
25
31
|
* <KptGalleria items={photos} activeIndex={index} onActiveIndexChange={setIndex} />
|
|
26
32
|
*/
|