@konce-pt/react 0.8.3 → 0.9.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 +4 -4
- package/dist/backdrop/backdrop.d.ts +79 -0
- package/dist/backdrop/backdrop.js +195 -0
- package/dist/backdrop/llms.txt +132 -0
- package/dist/button/button.d.ts +23 -7
- package/dist/button/button.js +33 -9
- package/dist/button/llms.txt +48 -12
- package/dist/button-group/button-group.d.ts +7 -1
- package/dist/button-group/button-group.js +9 -4
- package/dist/button-group/llms.txt +30 -5
- package/dist/chat/chat.d.ts +53 -0
- package/dist/chat/chat.js +160 -0
- package/dist/chat/llms.txt +78 -0
- package/dist/chat-composer/chat-composer.d.ts +41 -0
- package/dist/chat-composer/chat-composer.js +81 -0
- package/dist/chat-composer/llms.txt +56 -0
- package/dist/chat-dock/chat-dock.d.ts +40 -0
- package/dist/chat-dock/chat-dock.js +67 -0
- package/dist/chat-dock/llms.txt +71 -0
- package/dist/chat-entry/public-api.d.ts +12 -0
- package/dist/chat-entry/public-api.js +10 -0
- package/dist/chat-message/chat-message.d.ts +53 -0
- package/dist/chat-message/chat-message.js +112 -0
- package/dist/chat-message/llms.txt +73 -0
- package/dist/chat-typing/chat-typing.d.ts +23 -0
- package/dist/chat-typing/chat-typing.js +30 -0
- package/dist/chat-typing/llms.txt +41 -0
- package/dist/chip/chip.d.ts +10 -1
- package/dist/chip/chip.js +3 -2
- package/dist/chip/llms.txt +9 -0
- package/dist/data-view/data-view.d.ts +15 -3
- package/dist/data-view/data-view.js +22 -5
- package/dist/data-view/llms.txt +20 -7
- package/dist/dialog/dialog.d.ts +9 -1
- package/dist/dialog/dialog.js +2 -2
- package/dist/internal/dev.d.ts +1 -0
- package/dist/internal/dev.js +3 -0
- package/dist/internal/overlay.d.ts +6 -1
- package/dist/internal/overlay.js +5 -3
- package/dist/mention/llms.txt +60 -0
- package/dist/mention/mention.d.ts +38 -0
- package/dist/mention/mention.js +29 -0
- package/dist/mention/mentions.d.ts +31 -0
- package/dist/mention/mentions.js +24 -0
- package/dist/order-list/llms.txt +16 -5
- package/dist/order-list/order-list.d.ts +5 -3
- package/dist/order-list/order-list.js +69 -4
- package/dist/peek/llms.txt +42 -0
- package/dist/peek/peek.d.ts +35 -0
- package/dist/peek/peek.js +127 -0
- package/dist/pick-list/llms.txt +19 -5
- package/dist/pick-list/pick-list.d.ts +7 -2
- package/dist/pick-list/pick-list.js +123 -17
- package/dist/progress/llms.txt +70 -22
- package/dist/progress/progress.d.ts +34 -7
- package/dist/progress/progress.js +46 -9
- package/dist/public-api.d.ts +15 -3
- package/dist/public-api.js +6 -0
- package/dist/rich-text/llms.txt +10 -0
- package/dist/rich-text/rich-text.d.ts +11 -1
- package/dist/rich-text/rich-text.js +157 -5
- package/dist/rich-text/sanitize.js +10 -1
- package/dist/roadmap/roadmap.js +1 -3
- package/dist/shared/types.d.ts +2 -0
- package/package.json +16 -9
package/README.md
CHANGED
|
@@ -169,9 +169,9 @@ Data table with sorting, filtering, pagination and a custom cell:
|
|
|
169
169
|
| **Navigation** | `KptTabs`, `KptAccordion`, `KptBreadcrumb`, `KptStepper`, `KptMenu`, `KptMenubar`, `KptMegamenu`, `KptContextMenu` |
|
|
170
170
|
| **Data** | `KptDataTable`, `KptPaginator`, `KptTree`, `KptTimeline`, `KptCarousel`, `KptDataView`, `KptPickList`, `KptOrderList`, `KptGalleria`, `KptMeterGroup` |
|
|
171
171
|
| **Feedback & overlay** | `KptAlert`, `KptDialog`, `kptToast` + `KptToastContainer`, `KptTooltip`, `KptPopover`, `KptDrawer`, `KptBottomSheet`, `kptConfirm`, `KptBadge`, `KptChip`, `KptAvatar`, `KptAvatarGroup`, `KptSpinner`, `KptProgress`, `KptSkeleton`, `KptEmpty`, `KptImage`, `KptAuth` |
|
|
172
|
-
| **Subpaths** | `@konce-pt/react/charts` (`KptChart`), `/roadmap` (`KptRoadmap`), `/grid` (`KptGrid`, `KptCol`, `KptFlex`), `/map` (`KptMap` + legend, POI card, search), `/icons` (full Tabler set) |
|
|
172
|
+
| **Subpaths** | `@konce-pt/react/charts` (`KptChart`), `/chat` (`KptChat`, `KptChatMessage`, `KptChatTyping`, `KptChatComposer`, `KptChatDock`), `/roadmap` (`KptRoadmap`), `/grid` (`KptGrid`, `KptCol`, `KptFlex`), `/map` (`KptMap` + legend, POI card, search), `/icons` (full Tabler set) |
|
|
173
173
|
|
|
174
|
-
The heavy pieces (charts, roadmap, maps, the full icon set) live in subpaths on purpose, so the core
|
|
174
|
+
The heavy pieces (charts, chat, roadmap, maps, the full icon set) live in subpaths on purpose, so the core
|
|
175
175
|
entry point stays small. Every component ships an `llms.txt` API sheet next to its source.
|
|
176
176
|
|
|
177
177
|
## Theming
|
|
@@ -393,8 +393,8 @@ Angulara.
|
|
|
393
393
|
### Komponenty (80+)
|
|
394
394
|
|
|
395
395
|
Formularze, przyciski i akcje, layout, nawigacja, dane (z flagową `KptDataTable`), feedback
|
|
396
|
-
i overlay — pełna lista w tabeli powyżej. Ciężkie rzeczy (wykresy, roadmapa, mapy, pełny Tabler)
|
|
397
|
-
siedzą w subpathach `@konce-pt/react/{charts,roadmap,grid,map,icons}`, żeby nie obciążać rdzenia.
|
|
396
|
+
i overlay — pełna lista w tabeli powyżej. Ciężkie rzeczy (wykresy, czat, roadmapa, mapy, pełny Tabler)
|
|
397
|
+
siedzą w subpathach `@konce-pt/react/{charts,chat,roadmap,grid,map,icons}`, żeby nie obciążać rdzenia.
|
|
398
398
|
Każdy komponent ma obok źródła plik `llms.txt` z opisem API.
|
|
399
399
|
|
|
400
400
|
### Dokumentacja
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type ComponentPropsWithRef } from 'react';
|
|
2
|
+
import { type KptBackdropAnchor, type KptBackdropEffect } from '@konce-pt/backdrop';
|
|
3
|
+
export type { KptBackdropAnchor, KptBackdropEffect } from '@konce-pt/backdrop';
|
|
4
|
+
export interface KptBackdropProps extends ComponentPropsWithRef<'div'> {
|
|
5
|
+
/** Rysowany efekt. */
|
|
6
|
+
effect?: KptBackdropEffect;
|
|
7
|
+
/** Tło sekcji. Puste bierze `--kpt-color-surface`. */
|
|
8
|
+
background?: string;
|
|
9
|
+
/** Tempo; 1 to spokojne tempo obu efektów, 0 zatrzymuje obraz. */
|
|
10
|
+
speed?: number;
|
|
11
|
+
/** Ziarno losowania — ta sama wartość daje ten sam układ kwadratów i ten sam przebieg wstęgi. */
|
|
12
|
+
seed?: number;
|
|
13
|
+
/** Zatrzymuje animację, zostawiając ostatnią klatkę. */
|
|
14
|
+
paused?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Przenosi barwy wpisane na sztywno na powierzchnię bieżącego motywu: czerń dobrana do białego
|
|
17
|
+
* tła staje się bielą, gdy tło jest ciemne. Rusza wyłącznie jasność — odcień i nasycenie
|
|
18
|
+
* zostają, więc barwa marki nie zmienia się w inną barwę.
|
|
19
|
+
*
|
|
20
|
+
* Dotyczy `color`, `colors` i `background`, ale tylko wtedy, gdy podano je literałem. Prop pusty
|
|
21
|
+
* bierze token, a zapis z `var()` sam po token sięga — jedno i drugie już idzie za motywem
|
|
22
|
+
* i adaptacja odwróciłaby je drugi raz.
|
|
23
|
+
*/
|
|
24
|
+
adaptive?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* `signal-grid`: kolor kwadratów — dowolny zapis CSS, także sięgający po tokeny, np.
|
|
27
|
+
* `color-mix(in oklab, #5b8af2, var(--kpt-color-surface) 30%)`. Taki zapis przelicza się przy
|
|
28
|
+
* zmianie motywu; literał zostaje literałem. Puste bierze `--kpt-color-primary`.
|
|
29
|
+
*/
|
|
30
|
+
color?: string;
|
|
31
|
+
/** `signal-grid`: przy której krawędzi siatka jest najgęstsza. */
|
|
32
|
+
anchor?: KptBackdropAnchor;
|
|
33
|
+
/** `signal-grid`: bok kwadratu (px). */
|
|
34
|
+
cell?: number;
|
|
35
|
+
/** `signal-grid`: odstęp między kwadratami (px). */
|
|
36
|
+
gap?: number;
|
|
37
|
+
/** `signal-grid`: udział zapalonych komórek przy kotwicy, 0–1. */
|
|
38
|
+
density?: number;
|
|
39
|
+
/** `signal-grid`: jak szybko siatka gaśnie ku przeciwnej krawędzi. */
|
|
40
|
+
falloff?: number;
|
|
41
|
+
/**
|
|
42
|
+
* `wave`: rampa barw, po której przesuwa się wstęga — każdy przystanek dowolnym zapisem CSS.
|
|
43
|
+
* Zamknięta w pętlę, więc ostatnia barwa wraca do pierwszej. Pusta bierze `--kpt-color-primary`
|
|
44
|
+
* i dwie barwy wyprowadzone z niego obrotem odcienia — paleta biblioteki jest neutralna, więc
|
|
45
|
+
* domyślna wstęga jest jednobarwna i przygaszona. Po kolory sięga się tu, nie do tokenów
|
|
46
|
+
* statusowych. Zapis może sięgać po tokeny i wtedy idzie za motywem. Przystanek, którego
|
|
47
|
+
* przeglądarka nie uznaje za kolor, wypada z rampy; gdy wypadną wszystkie, zostaje rampa
|
|
48
|
+
* domyślna.
|
|
49
|
+
*/
|
|
50
|
+
colors?: readonly string[];
|
|
51
|
+
/** `wave`: kąt, pod jakim prąd przecina kadr (stopnie); 0 to poziomo w prawo. */
|
|
52
|
+
rotation?: number;
|
|
53
|
+
/** `wave`: szerokość wstęgi jako ułamek wysokości kadru. */
|
|
54
|
+
band?: number;
|
|
55
|
+
/** `wave`: jak mocno faluje linia środkowa. Zero zatrzymuje falowanie, nie barwy. */
|
|
56
|
+
amplitude?: number;
|
|
57
|
+
/** Propsy warstwy z treścią (`.kpt-backdrop__content`). */
|
|
58
|
+
contentProps?: ComponentPropsWithRef<'div'>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Dekoracyjna warstwa tła dla sekcji: komponent obejmuje treść i maluje pod nią animowany canvas.
|
|
62
|
+
* Sięga się po niego tam, gdzie płaska powierzchnia wygląda na niedokończoną, a wideo albo duży
|
|
63
|
+
* obraz kosztowałyby transfer i nie poszłyby za motywem.
|
|
64
|
+
*
|
|
65
|
+
* Komponent jest opakowaniem: canvas leży pod spodem, a dzieci idą warstwę wyżej i zachowują się
|
|
66
|
+
* zwyczajnie. Rysowanie stoi, gdy nie ma czego oglądać — sekcja poza ekranem, karta w tle,
|
|
67
|
+
* `paused`, `speed` równe zeru albo ograniczony ruch w systemie; wtedy zostaje jedna statyczna
|
|
68
|
+
* klatka zamiast pustki, bo efekt jest częścią kompozycji, nie ozdobą.
|
|
69
|
+
*
|
|
70
|
+
* Rysowanie i cała matematyka siedzą w `@konce-pt/backdrop`, wspólnym z portem Angulara —
|
|
71
|
+
* ten komponent to sam cykl życia.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* <KptBackdrop effect="wave" rotation={18} colors={['#5b8af2', '#a86df2', '#f25b9e']}>
|
|
75
|
+
* <h1>Build your app from the first prompt</h1>
|
|
76
|
+
* <KptButton>Get started</KptButton>
|
|
77
|
+
* </KptBackdrop>
|
|
78
|
+
*/
|
|
79
|
+
export declare function KptBackdrop({ effect, background, speed, seed, paused, adaptive, color, anchor, cell, gap, density, falloff, colors, rotation, band, amplitude, contentProps, className, style, children, ...rest }: Readonly<KptBackdropProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { KPT_BACKDROP_MAX_DPR, KPT_BACKDROP_MAX_STEP, KPT_SIGNAL_GRID_CELL, KPT_SIGNAL_GRID_DENSITY, KPT_SIGNAL_GRID_FALLOFF, KPT_SIGNAL_GRID_GAP, KPT_WAVE_AMPLITUDE, KPT_WAVE_BAND, KPT_WAVE_ROTATION, adaptBackdropValue, createBackdropRenderer, observeBackdropTheme, readBackdropInk, readBackdropLead, readBackdropSurface, resolveBackdropColors, } from '@konce-pt/backdrop';
|
|
4
|
+
import { cn } from "../internal/cn.js";
|
|
5
|
+
import { isDevBuild } from "../internal/dev.js";
|
|
6
|
+
const EMPTY = [];
|
|
7
|
+
/**
|
|
8
|
+
* Dekoracyjna warstwa tła dla sekcji: komponent obejmuje treść i maluje pod nią animowany canvas.
|
|
9
|
+
* Sięga się po niego tam, gdzie płaska powierzchnia wygląda na niedokończoną, a wideo albo duży
|
|
10
|
+
* obraz kosztowałyby transfer i nie poszłyby za motywem.
|
|
11
|
+
*
|
|
12
|
+
* Komponent jest opakowaniem: canvas leży pod spodem, a dzieci idą warstwę wyżej i zachowują się
|
|
13
|
+
* zwyczajnie. Rysowanie stoi, gdy nie ma czego oglądać — sekcja poza ekranem, karta w tle,
|
|
14
|
+
* `paused`, `speed` równe zeru albo ograniczony ruch w systemie; wtedy zostaje jedna statyczna
|
|
15
|
+
* klatka zamiast pustki, bo efekt jest częścią kompozycji, nie ozdobą.
|
|
16
|
+
*
|
|
17
|
+
* Rysowanie i cała matematyka siedzą w `@konce-pt/backdrop`, wspólnym z portem Angulara —
|
|
18
|
+
* ten komponent to sam cykl życia.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* <KptBackdrop effect="wave" rotation={18} colors={['#5b8af2', '#a86df2', '#f25b9e']}>
|
|
22
|
+
* <h1>Build your app from the first prompt</h1>
|
|
23
|
+
* <KptButton>Get started</KptButton>
|
|
24
|
+
* </KptBackdrop>
|
|
25
|
+
*/
|
|
26
|
+
export function KptBackdrop({ effect = 'signal-grid', background = '', speed = 1, seed = 0, paused = false, adaptive = false, color = '', anchor = 'right', cell = KPT_SIGNAL_GRID_CELL, gap = KPT_SIGNAL_GRID_GAP, density = KPT_SIGNAL_GRID_DENSITY, falloff = KPT_SIGNAL_GRID_FALLOFF, colors = EMPTY, rotation = KPT_WAVE_ROTATION, band = KPT_WAVE_BAND, amplitude = KPT_WAVE_AMPLITUDE, contentProps, className, style, children, ...rest }) {
|
|
27
|
+
const host = useRef(null);
|
|
28
|
+
const canvas = useRef(null);
|
|
29
|
+
const context = useRef(null);
|
|
30
|
+
const renderer = useRef(null);
|
|
31
|
+
const size = useRef({ width: 0, height: 0 });
|
|
32
|
+
/** Zegar animacji w sekundach — w refie, żeby przebudowa renderera nie cofała obrazu. */
|
|
33
|
+
const clock = useRef(0);
|
|
34
|
+
/** Kolor wiodący, z którym zbudowany jest bieżący renderer — punkt odniesienia dla zmiany motywu. */
|
|
35
|
+
const lead = useRef('');
|
|
36
|
+
/** Ostatnio zgłoszony zestaw odrzuconych barw — żeby nie powtarzać tego samego ostrzeżenia. */
|
|
37
|
+
const reported = useRef('');
|
|
38
|
+
/** Tło po adaptacji. Puste znaczy „nie ma czego podmieniać" i wtedy liczy się samo `background`. */
|
|
39
|
+
const [adapted, setAdapted] = useState('');
|
|
40
|
+
/**
|
|
41
|
+
* Ton i kolor treści dobrane do malowanego tła. Puste znaczy „sekcja nie ma własnego tła"
|
|
42
|
+
* i wtedy treść zostaje przy `--kpt-color-on-surface` ze stylu.
|
|
43
|
+
*/
|
|
44
|
+
const [ink, setInk] = useState({ tone: '', color: '' });
|
|
45
|
+
const rate = useRef(speed);
|
|
46
|
+
rate.current = speed;
|
|
47
|
+
const [onScreen, setOnScreen] = useState(true);
|
|
48
|
+
const [reduced, setReduced] = useState(false);
|
|
49
|
+
/** Licznik zmian motywu; podbicie każe przeliczyć kolory z tokenów na nowo. */
|
|
50
|
+
const [theme, setTheme] = useState(0);
|
|
51
|
+
const paint = useCallback(() => {
|
|
52
|
+
if (context.current)
|
|
53
|
+
renderer.current?.draw(context.current, clock.current);
|
|
54
|
+
}, []);
|
|
55
|
+
const measure = useCallback(() => {
|
|
56
|
+
const element = canvas.current;
|
|
57
|
+
if (!element)
|
|
58
|
+
return;
|
|
59
|
+
context.current ??= element.getContext('2d');
|
|
60
|
+
const rect = element.getBoundingClientRect();
|
|
61
|
+
const dpr = Math.min(globalThis.devicePixelRatio || 1, KPT_BACKDROP_MAX_DPR);
|
|
62
|
+
const width = Math.round(rect.width);
|
|
63
|
+
const height = Math.round(rect.height);
|
|
64
|
+
size.current = { width, height };
|
|
65
|
+
element.width = Math.round(width * dpr);
|
|
66
|
+
element.height = Math.round(height * dpr);
|
|
67
|
+
// Zapis width/height zeruje kontekst, więc skala musi wrócić po każdym pomiarze.
|
|
68
|
+
context.current?.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
69
|
+
}, []);
|
|
70
|
+
// Tablica barw przychodzi nowa przy każdym renderze rodzica, więc do zależności idzie jej treść.
|
|
71
|
+
const ramp = colors.join('|');
|
|
72
|
+
const list = useMemo(() => (ramp === '' ? EMPTY : ramp.split('|')), [ramp]);
|
|
73
|
+
// Każda zmiana wejść buduje renderer od nowa — zamiast doklejać ścieżki „co się zmieniło".
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
// Kolory rozwiązuje się na canvasie, nie na hoście: sonda `var()` pisze inline style, a nasłuch
|
|
76
|
+
// motywu ogląda `style` hosta i jego przodków — zapis na hoście zapętliłby przebudowy.
|
|
77
|
+
const scope = canvas.current;
|
|
78
|
+
if (!scope)
|
|
79
|
+
return undefined;
|
|
80
|
+
measure();
|
|
81
|
+
const resolved = resolveBackdropColors(scope, color, list, adaptive);
|
|
82
|
+
lead.current = resolved.color;
|
|
83
|
+
const painted = adaptive ? adaptBackdropValue(background, readBackdropSurface(scope)) : background;
|
|
84
|
+
setAdapted(adaptive ? painted : '');
|
|
85
|
+
// Ton liczymy z tła, które sekcja naprawdę pomaluje — czyli już po adaptacji.
|
|
86
|
+
setInk(readBackdropInk(scope, painted));
|
|
87
|
+
// Efekt chodzi przy każdej zmianie wejść, więc ciągnięcie suwaka nad wadliwą rampą zasypałoby
|
|
88
|
+
// konsolę tym samym zdaniem — zgłaszamy dopiero zestaw inny niż poprzednio.
|
|
89
|
+
const rejected = resolved.invalid.join(', ');
|
|
90
|
+
if (rejected !== reported.current) {
|
|
91
|
+
reported.current = rejected;
|
|
92
|
+
if (rejected !== '' && isDevBuild()) {
|
|
93
|
+
console.warn(`[KptBackdrop] Pominięto barwy, których przeglądarka nie rozpoznaje: ${rejected}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const current = createBackdropRenderer({
|
|
97
|
+
effect,
|
|
98
|
+
color: resolved.color,
|
|
99
|
+
colors: resolved.colors,
|
|
100
|
+
anchor,
|
|
101
|
+
cell,
|
|
102
|
+
gap,
|
|
103
|
+
density,
|
|
104
|
+
falloff,
|
|
105
|
+
rotation,
|
|
106
|
+
band,
|
|
107
|
+
amplitude,
|
|
108
|
+
seed,
|
|
109
|
+
});
|
|
110
|
+
current.resize(size.current.width, size.current.height);
|
|
111
|
+
renderer.current = current;
|
|
112
|
+
paint();
|
|
113
|
+
return () => {
|
|
114
|
+
current.dispose();
|
|
115
|
+
renderer.current = null;
|
|
116
|
+
};
|
|
117
|
+
}, [effect, color, list, anchor, cell, gap, density, falloff, rotation, band, amplitude, seed, adaptive, background, theme, measure, paint]);
|
|
118
|
+
// Sam rozmiar nie zmienia ustawień, więc renderer zostaje — przelicza tylko geometrię.
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
const element = canvas.current;
|
|
121
|
+
if (!element)
|
|
122
|
+
return undefined;
|
|
123
|
+
const observer = new ResizeObserver(() => {
|
|
124
|
+
const rect = element.getBoundingClientRect();
|
|
125
|
+
if (Math.round(rect.width) === size.current.width && Math.round(rect.height) === size.current.height)
|
|
126
|
+
return;
|
|
127
|
+
measure();
|
|
128
|
+
renderer.current?.resize(size.current.width, size.current.height);
|
|
129
|
+
paint();
|
|
130
|
+
});
|
|
131
|
+
observer.observe(element);
|
|
132
|
+
return () => observer.disconnect();
|
|
133
|
+
}, [measure, paint]);
|
|
134
|
+
// Sekcja poza ekranem i karta w tle to ten sam przypadek: nie ma czego oglądać.
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
const element = host.current;
|
|
137
|
+
if (!element)
|
|
138
|
+
return undefined;
|
|
139
|
+
const update = (visible) => setOnScreen(visible && !document.hidden);
|
|
140
|
+
const observer = new IntersectionObserver((entries) => update(entries.some((entry) => entry.isIntersecting)));
|
|
141
|
+
observer.observe(element);
|
|
142
|
+
const onTabSwitch = () => setOnScreen(!document.hidden);
|
|
143
|
+
document.addEventListener('visibilitychange', onTabSwitch);
|
|
144
|
+
return () => {
|
|
145
|
+
observer.disconnect();
|
|
146
|
+
document.removeEventListener('visibilitychange', onTabSwitch);
|
|
147
|
+
};
|
|
148
|
+
}, []);
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
const query = matchMedia('(prefers-reduced-motion: reduce)');
|
|
151
|
+
setReduced(query.matches);
|
|
152
|
+
const onChange = (event) => setReduced(event.matches);
|
|
153
|
+
query.addEventListener('change', onChange);
|
|
154
|
+
return () => query.removeEventListener('change', onChange);
|
|
155
|
+
}, []);
|
|
156
|
+
// Zmiana motywu przestawia tokeny, a kolory renderera są już wyliczone — trzeba je odczytać
|
|
157
|
+
// na nowo. Nasłuch trafia też w zmiany niezwiązane z barwą, więc licznik podbija się dopiero
|
|
158
|
+
// wtedy, gdy odczyt naprawdę dał inny kolor.
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
const element = host.current;
|
|
161
|
+
if (!element)
|
|
162
|
+
return undefined;
|
|
163
|
+
return observeBackdropTheme(element, () => {
|
|
164
|
+
const scope = canvas.current;
|
|
165
|
+
if (!scope)
|
|
166
|
+
return;
|
|
167
|
+
// Ten sam rachunek co w efekcie budującym — inaczej strażnik porównywałby wartość sprzed
|
|
168
|
+
// adaptacji z zapamiętaną po adaptacji i podbijałby licznik bez przerwy.
|
|
169
|
+
const next = adaptive ? adaptBackdropValue(color, readBackdropSurface(scope)) : color;
|
|
170
|
+
if (readBackdropLead(scope, next) === lead.current)
|
|
171
|
+
return;
|
|
172
|
+
setTheme((count) => count + 1);
|
|
173
|
+
});
|
|
174
|
+
}, [color, adaptive]);
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (paused || reduced || !onScreen || speed <= 0)
|
|
177
|
+
return undefined;
|
|
178
|
+
let frame = 0;
|
|
179
|
+
let last = 0;
|
|
180
|
+
const tick = (now) => {
|
|
181
|
+
const step = last === 0 ? 0 : Math.min(now - last, KPT_BACKDROP_MAX_STEP);
|
|
182
|
+
last = now;
|
|
183
|
+
// Prędkość wchodzi w zegar, nie w fazę — suwak przyspiesza obraz zamiast go przeskakiwać.
|
|
184
|
+
clock.current += (step / 1000) * rate.current;
|
|
185
|
+
paint();
|
|
186
|
+
frame = requestAnimationFrame(tick);
|
|
187
|
+
};
|
|
188
|
+
frame = requestAnimationFrame(tick);
|
|
189
|
+
return () => cancelAnimationFrame(frame);
|
|
190
|
+
}, [paused, reduced, onScreen, speed, paint]);
|
|
191
|
+
const { className: contentClassName, ...contentRest } = contentProps ?? {};
|
|
192
|
+
/** Tło malowane na hoście: po adaptacji, jeśli jest, inaczej to, co podano. */
|
|
193
|
+
const surface = adapted || background;
|
|
194
|
+
return (_jsxs("div", { ref: host, className: cn('kpt-backdrop-host', className), "data-effect": effect, "data-tone": ink.tone || undefined, style: { ...(surface ? { background: surface } : null), ...(ink.color ? { color: ink.color } : null), ...style }, ...rest, children: [_jsx("canvas", { ref: canvas, className: "kpt-backdrop__canvas", "aria-hidden": "true" }), _jsx("div", { className: cn('kpt-backdrop__content', contentClassName), ...contentRest, children: children })] }));
|
|
195
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# KptBackdrop (@konce-pt/react)
|
|
2
|
+
|
|
3
|
+
A decorative background layer for a section: the component wraps your content and paints an
|
|
4
|
+
animated canvas beneath it. You reach for it where a flat surface looks unfinished, and where
|
|
5
|
+
a video or a large image would cost bandwidth and refuse to follow the theme.
|
|
6
|
+
Children sit a layer above the canvas and behave normally.
|
|
7
|
+
Import: `import { KptBackdrop } from '@konce-pt/react';`
|
|
8
|
+
|
|
9
|
+
## DOM
|
|
10
|
+
<div class="kpt-backdrop-host" data-effect="wave">
|
|
11
|
+
<canvas class="kpt-backdrop__canvas" aria-hidden="true"></canvas>
|
|
12
|
+
<div class="kpt-backdrop__content">…</div>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## Props (shared)
|
|
16
|
+
- `effect`: 'signal-grid' | 'wave' (default 'signal-grid')
|
|
17
|
+
- `background`: string — the section background; empty takes `--kpt-color-surface`
|
|
18
|
+
- `speed`: number (default 1) — 1 is the calm pace of either effect, 0 freezes the picture
|
|
19
|
+
- `seed`: number (default 0) — the same value gives the same layout and the same sway
|
|
20
|
+
- `paused`: boolean — stops the animation, leaving the last frame
|
|
21
|
+
- `adaptive`: boolean — carries hardcoded colours onto the current theme's surface
|
|
22
|
+
- `contentProps` — props for the `.kpt-backdrop__content` layer
|
|
23
|
+
- `className`, `ref` and every other `<div>` prop go to `.kpt-backdrop-host`
|
|
24
|
+
|
|
25
|
+
## Props (signal-grid)
|
|
26
|
+
- `color`: string — the square colour, any CSS notation, tokens included; empty takes
|
|
27
|
+
`--kpt-color-primary`
|
|
28
|
+
- `anchor`: 'left' | 'right' (default 'right') — the edge the grid is densest at
|
|
29
|
+
- `cell`: number (default 5), `gap`: number (default 11) — the square and the space between, in px
|
|
30
|
+
- `density`: number (default 0.8) — the share of lit cells at the anchor, 0–1
|
|
31
|
+
- `falloff`: number (default 1.4) — how fast the grid fades towards the far edge
|
|
32
|
+
|
|
33
|
+
## Props (wave)
|
|
34
|
+
- `colors`: string[] — the colour ramp the current slides along, closed into a loop; every
|
|
35
|
+
stop takes any CSS notation, `color`'s rules exactly
|
|
36
|
+
- `rotation`: number (default 12) — the angle the current crosses the frame at, in degrees
|
|
37
|
+
- `band`: number (default 0.34) — the band's width as a fraction of the frame's height
|
|
38
|
+
- `amplitude`: number (default 0.16) — how far the centre line sways
|
|
39
|
+
|
|
40
|
+
A prop that does not belong to the current effect is simply not read.
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
<KptBackdrop effect="wave" rotation={18} colors={['#5B8AF2', '#A86DF2', '#F25B9E']}>
|
|
44
|
+
<h1>Build your app from the first prompt</h1>
|
|
45
|
+
<KptButton>Get started</KptButton>
|
|
46
|
+
</KptBackdrop>
|
|
47
|
+
|
|
48
|
+
## The two effects
|
|
49
|
+
`signal-grid` is a fine grid of squares that quietly twinkle, dense at one edge and fading to
|
|
50
|
+
empty at the other; the gradient comes from **how many** squares are lit, not from opacity laid
|
|
51
|
+
over an even grid. `wave` is one soft current of light with hues sliding along it and no hard edge
|
|
52
|
+
anywhere; `rotation` turns it, `band` widens it, `amplitude` sways it.
|
|
53
|
+
|
|
54
|
+
Without `colors` the wave's ramp is `--kpt-color-primary` plus two hues derived from it by
|
|
55
|
+
rotation. The library's palette is neutral, so that default current is single-hued and muted.
|
|
56
|
+
|
|
57
|
+
Every stop is put to the browser, so `oklch()`, `color-mix()`, `rgb()` and named colours are read
|
|
58
|
+
the same as hex; alpha is dropped. A stop the browser refuses falls out of the ramp, and if every
|
|
59
|
+
one of them falls out the default ramp takes over rather than the section going blank — a dev
|
|
60
|
+
build says in the console which stops it dropped.
|
|
61
|
+
|
|
62
|
+
## The same picture in both ports
|
|
63
|
+
Everything that decides how it looks — the grid layout, the density ramp, the twinkle, the current
|
|
64
|
+
and the colour ramp, plus the renderers themselves — lives in `@konce-pt/backdrop`, shared with
|
|
65
|
+
the Angular port. This component is the lifecycle: `rAF`, `ResizeObserver`, `IntersectionObserver`,
|
|
66
|
+
the reduced-motion query and the theme watch. The same props therefore give the
|
|
67
|
+
same frame in either port.
|
|
68
|
+
|
|
69
|
+
## Rest
|
|
70
|
+
Drawing stops when there is nothing to watch: the section is off-screen, the tab is in the
|
|
71
|
+
background, `paused` is set, `speed` is 0, or the system asks for reduced motion. What is left is
|
|
72
|
+
a single static frame, not emptiness. `speed` feeds the clock rather than the phase, so a speed
|
|
73
|
+
slider accelerates the picture instead of jumping it. A change of `colors` is tracked by content,
|
|
74
|
+
not by array identity, so a fresh array from a parent render does not rebuild the renderer.
|
|
75
|
+
|
|
76
|
+
## Accessibility
|
|
77
|
+
The canvas carries `aria-hidden="true"` and `pointer-events: none` — it is decoration with no
|
|
78
|
+
content and nothing to hit. Children keep their own roles and tab order.
|
|
79
|
+
|
|
80
|
+
## Tokens
|
|
81
|
+
`--kpt-color-primary` (squares and the default ramp), `--kpt-color-surface` (background),
|
|
82
|
+
`--kpt-color-on-surface`. Section-wide overrides: `--kpt-backdrop-color`, `--kpt-backdrop-surface`.
|
|
83
|
+
|
|
84
|
+
## Adaptive colours
|
|
85
|
+
A literal stays put by design, which is right for a brand colour and wrong for a section meant to
|
|
86
|
+
read in both themes. `adaptive` closes that gap: it carries `color`, `colors` and `background`
|
|
87
|
+
onto the surface of the current theme, keeping each one's distance from that surface. Black picked
|
|
88
|
+
for a white section comes out white on a near-black ground, and that white background comes out at
|
|
89
|
+
the surface's own lightness. Only lightness moves — hue and chroma stay, so a brand colour keeps
|
|
90
|
+
being that colour and only changes how light it is.
|
|
91
|
+
|
|
92
|
+
It touches literals only. An empty input takes a token, and a value reaching through `var()`
|
|
93
|
+
reaches a token too; both already follow the theme, and adapting them would turn them back again.
|
|
94
|
+
Values written for the light theme are the assumption — the library keeps light in `:root` and
|
|
95
|
+
dark as the override.
|
|
96
|
+
|
|
97
|
+
## Content on a custom background
|
|
98
|
+
The host paints `--kpt-color-surface` and lets content inherit `--kpt-color-on-surface`, which is
|
|
99
|
+
right until the section gets a `background` of its own — then that token describes the page around
|
|
100
|
+
it, not the ground the content actually sits on. A dark section in a light theme would come out
|
|
101
|
+
black on black.
|
|
102
|
+
|
|
103
|
+
So a section with its own background works out its own ink: the host sets `color` to match what it
|
|
104
|
+
paints, and announces the result as `data-tone` (`light` for a light background, `dark` for a dark
|
|
105
|
+
one). Text follows by inheritance; anything needing more than a text colour — buttons in the
|
|
106
|
+
section, say — can hang its own tokens off the attribute:
|
|
107
|
+
|
|
108
|
+
:where(div.kpt-backdrop-host[data-tone='light']) .hero { --kpt-button-filled-bg: #15181a; }
|
|
109
|
+
|
|
110
|
+
The tone is computed from the background the section really paints, so with `adaptive` on it
|
|
111
|
+
matches the adapted value, not the one written in the template. A section without its own
|
|
112
|
+
background announces nothing and content stays on `--kpt-color-on-surface`.
|
|
113
|
+
|
|
114
|
+
## Theme
|
|
115
|
+
Canvas does not expand `var()`, so the colour is read once and baked into the renderer — the
|
|
116
|
+
component therefore watches for anything that can restyle the tokens under it and reads them
|
|
117
|
+
again. `[data-theme="dark"]` is not tied to `:root`, so the watch covers `data-theme`, `class`
|
|
118
|
+
and `style` on every ancestor from the host up to `<html>`, plus the `prefers-color-scheme` query
|
|
119
|
+
for an application that keeps its dark tokens in a media query. A runtime override of
|
|
120
|
+
`--kpt-color-primary` is picked up the same way as a theme switch. The renderer is rebuilt only
|
|
121
|
+
when the colour actually came out different, so an unrelated class landing on `<html>` costs one
|
|
122
|
+
cheap read and nothing else.
|
|
123
|
+
|
|
124
|
+
Whether an explicit `color` or `colors` follows the theme is up to how it is written. A literal
|
|
125
|
+
stays put — `#5b8af2` is `#5b8af2` in either theme, which is what someone spelling out a brand
|
|
126
|
+
colour wants. An expression reaching for a token is resolved against the tree on every rebuild, so
|
|
127
|
+
it moves with the theme on its own:
|
|
128
|
+
|
|
129
|
+
<KptBackdrop color="color-mix(in oklab, #5b8af2, var(--kpt-color-surface) 30%)">
|
|
130
|
+
|
|
131
|
+
`var(--kpt-color-primary)` works the same way. A name that resolves to nothing falls back to the
|
|
132
|
+
token for `color`, and drops out of the ramp for `colors`.
|
package/dist/button/button.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type ComponentPropsWithRef } from 'react';
|
|
2
2
|
import type { AsChildProps, KptHostProps } from '../internal/types.ts';
|
|
3
|
-
export type KptButtonVariant = 'filled' | 'tonal' | 'outline' | 'text' | 'danger';
|
|
4
|
-
export type KptButtonSize = 'sm' | 'md' | 'lg';
|
|
5
|
-
export interface KptButtonProps extends ComponentPropsWithRef<'button'>, AsChildProps {
|
|
3
|
+
export type KptButtonVariant = 'filled' | 'elevated' | 'tonal' | 'outline' | 'text' | 'link' | 'danger' | 'success' | 'warning' | 'info';
|
|
4
|
+
export type KptButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
5
|
+
export interface KptButtonProps extends Omit<ComponentPropsWithRef<'button'>, 'href'>, AsChildProps {
|
|
6
6
|
/** Wariant wizualny. */
|
|
7
7
|
variant?: KptButtonVariant;
|
|
8
8
|
/** Rozmiar. */
|
|
@@ -11,17 +11,33 @@ export interface KptButtonProps extends ComponentPropsWithRef<'button'>, AsChild
|
|
|
11
11
|
loading?: boolean;
|
|
12
12
|
/** Pełna szerokość kontenera. */
|
|
13
13
|
fullWidth?: boolean;
|
|
14
|
+
/** Nazwa ikony wiodącej z rejestru (`KptIconRegistry`). */
|
|
15
|
+
icon?: string;
|
|
16
|
+
/** Nazwa ikony zamykającej — zwykle znacznik kierunku, nie akcja. */
|
|
17
|
+
iconEnd?: string;
|
|
18
|
+
/** Klasa dokładana do ikony wiodącej; obok stałego haka `.kpt-button__icon`. */
|
|
19
|
+
iconClassName?: string;
|
|
20
|
+
iconEndClassName?: string;
|
|
21
|
+
/** Podany adres zamienia `<button>` na `<a>`. Ignorowany przy `asChild`. */
|
|
22
|
+
href?: string;
|
|
23
|
+
target?: string;
|
|
24
|
+
rel?: string;
|
|
14
25
|
/** Propsy wrappera hosta (`.kpt-button-host`). `className` i `ref` idą na `<button>`. */
|
|
15
26
|
hostProps?: KptHostProps<'span'>;
|
|
16
27
|
}
|
|
17
28
|
/**
|
|
18
29
|
* Przycisk Koncept UI. Renderuje natywny `<button>` (semantyka + submit formularza)
|
|
19
|
-
* w hoście `.kpt-button-host
|
|
20
|
-
* bo host niesie własne style
|
|
30
|
+
* albo `<a>`, gdy podasz `href` — w hoście `.kpt-button-host`, czyli dwa poziomy DOM,
|
|
31
|
+
* dokładnie jak w porcie Angulara, bo host niesie własne style.
|
|
32
|
+
*
|
|
33
|
+
* Ikonę można podać nazwą (`icon`/`iconEnd`) albo wstawić w `children` — oba sposoby
|
|
34
|
+
* dostają ten sam odstęp i tę samą skalę. Przy `loading` spinner zajmuje miejsce ikony wiodącej.
|
|
21
35
|
*
|
|
22
36
|
* @example
|
|
23
37
|
* <KptButton onClick={save}>Zapisz</KptButton>
|
|
24
|
-
* <KptButton
|
|
38
|
+
* <KptButton icon="device-floppy" onClick={save}>Zapisz</KptButton>
|
|
39
|
+
* <KptButton iconEnd="chevron-right" variant="outline">Dalej</KptButton>
|
|
40
|
+
* <KptButton variant="link" href="/docs">Dokumentacja</KptButton>
|
|
25
41
|
* <KptButton asChild><a href="/docs">Dokumentacja</a></KptButton>
|
|
26
42
|
*/
|
|
27
|
-
export declare function KptButton({ variant, size, type, loading, fullWidth, disabled, asChild, hostProps, className, children, ...rest }: Readonly<KptButtonProps>): import("react").JSX.Element;
|
|
43
|
+
export declare function KptButton({ variant, size, type, loading, fullWidth, disabled, asChild, icon, iconEnd, iconClassName, iconEndClassName, href, target, rel, hostProps, className, children, ...rest }: Readonly<KptButtonProps>): import("react").JSX.Element;
|
package/dist/button/button.js
CHANGED
|
@@ -2,22 +2,28 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { isValidElement } from 'react';
|
|
3
3
|
import { cn } from "../internal/cn.js";
|
|
4
4
|
import { renderAsChild } from "../internal/slot.js";
|
|
5
|
+
import { KptIcon } from "../icon/icon.js";
|
|
5
6
|
/**
|
|
6
7
|
* Przycisk Koncept UI. Renderuje natywny `<button>` (semantyka + submit formularza)
|
|
7
|
-
* w hoście `.kpt-button-host
|
|
8
|
-
* bo host niesie własne style
|
|
8
|
+
* albo `<a>`, gdy podasz `href` — w hoście `.kpt-button-host`, czyli dwa poziomy DOM,
|
|
9
|
+
* dokładnie jak w porcie Angulara, bo host niesie własne style.
|
|
10
|
+
*
|
|
11
|
+
* Ikonę można podać nazwą (`icon`/`iconEnd`) albo wstawić w `children` — oba sposoby
|
|
12
|
+
* dostają ten sam odstęp i tę samą skalę. Przy `loading` spinner zajmuje miejsce ikony wiodącej.
|
|
9
13
|
*
|
|
10
14
|
* @example
|
|
11
15
|
* <KptButton onClick={save}>Zapisz</KptButton>
|
|
12
|
-
* <KptButton
|
|
16
|
+
* <KptButton icon="device-floppy" onClick={save}>Zapisz</KptButton>
|
|
17
|
+
* <KptButton iconEnd="chevron-right" variant="outline">Dalej</KptButton>
|
|
18
|
+
* <KptButton variant="link" href="/docs">Dokumentacja</KptButton>
|
|
13
19
|
* <KptButton asChild><a href="/docs">Dokumentacja</a></KptButton>
|
|
14
20
|
*/
|
|
15
|
-
export function KptButton({ variant = 'filled', size = 'md', type = 'button', loading = false, fullWidth = false, disabled = false, asChild = false, hostProps, className, children, ...rest }) {
|
|
21
|
+
export function KptButton({ variant = 'filled', size = 'md', type = 'button', loading = false, fullWidth = false, disabled = false, asChild = false, icon, iconEnd, iconClassName, iconEndClassName, href, target, rel, hostProps, className, children, ...rest }) {
|
|
16
22
|
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
17
23
|
const isDisabled = disabled || loading;
|
|
18
|
-
// Zawartość jest identyczna
|
|
24
|
+
// Zawartość jest identyczna we wszystkich trybach — w `asChild` podmieniamy tylko element
|
|
19
25
|
// zewnętrzny, nie strukturę środka, żeby SCSS (`__spinner`, `__content`) dalej działał.
|
|
20
|
-
const content = (_jsxs(_Fragment, { children: [loading ? _jsx("span", { className: "kpt-button__spinner", "aria-hidden": "true" }) : null, _jsx("span", { className: "kpt-button__content", children: asChildContent(asChild, children) })] }));
|
|
26
|
+
const content = (_jsxs(_Fragment, { children: [loading ? (_jsx("span", { className: "kpt-button__spinner", "aria-hidden": "true" })) : icon ? (_jsx(KptIcon, { className: cn('kpt-button__icon', iconClassName), name: icon })) : null, _jsx("span", { className: "kpt-button__content", children: asChildContent(asChild, children) }), iconEnd ? (_jsx(KptIcon, { className: cn('kpt-button__icon', 'kpt-button__icon--end', iconEndClassName), name: iconEnd })) : null] }));
|
|
21
27
|
const buttonProps = {
|
|
22
28
|
className: cn('kpt-button', loading && 'kpt-button--loading', className),
|
|
23
29
|
'data-variant': variant,
|
|
@@ -27,13 +33,31 @@ export function KptButton({ variant = 'filled', size = 'md', type = 'button', lo
|
|
|
27
33
|
'aria-busy': loading || undefined,
|
|
28
34
|
...rest,
|
|
29
35
|
};
|
|
30
|
-
return (_jsx("span", { className: cn('kpt-button-host', fullWidth && 'kpt-button-host--full', hostClassName), ...hostRest, children:
|
|
36
|
+
return (_jsx("span", { className: cn('kpt-button-host', fullWidth && 'kpt-button-host--full', hostClassName), ...hostRest, children: renderInner({ asChild, children, buttonProps, content, href, target, rel, type, isDisabled }) }));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Element wewnętrzny. `asChild` ma pierwszeństwo przed `href` — kto podstawia własny
|
|
40
|
+
* element, ten decyduje o znaczniku.
|
|
41
|
+
*/
|
|
42
|
+
function renderInner(args) {
|
|
43
|
+
const { asChild, children, buttonProps, content, href, target, rel, type, isDisabled } = args;
|
|
44
|
+
if (asChild && isValidElement(children)) {
|
|
45
|
+
return renderAsChild(children, { ...buttonProps, ...disabledProps(children, isDisabled) }, content);
|
|
46
|
+
}
|
|
47
|
+
if (href !== undefined) {
|
|
48
|
+
return (_jsx("a", {
|
|
49
|
+
// Wyłączony odnośnik traci `href`, a nie tylko dostaje atrybut — inaczej dałoby się go
|
|
50
|
+
// kliknąć i wejść na niego z klawiatury.
|
|
51
|
+
href: isDisabled ? undefined : href, target: target,
|
|
52
|
+
// `target="_blank"` bez `rel` to wektor na `window.opener` — dokładamy domyślne.
|
|
53
|
+
rel: rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined), "aria-disabled": isDisabled || undefined, tabIndex: isDisabled ? -1 : undefined, ...buttonProps, children: content }));
|
|
54
|
+
}
|
|
55
|
+
return (_jsx("button", { type: type, disabled: isDisabled, ...buttonProps, children: content }));
|
|
31
56
|
}
|
|
32
57
|
/**
|
|
33
58
|
* Stan wyłączenia dla podstawionego elementu. Natywny `<button>` dostaje `disabled`
|
|
34
59
|
* (SCSS stylizuje `:disabled`); każdy inny element — `aria-disabled`, bo `disabled`
|
|
35
|
-
* nie ma tam znaczenia.
|
|
36
|
-
* więc `asChild` + `disabled` to świadomie ograniczone połączenie.
|
|
60
|
+
* nie ma tam znaczenia.
|
|
37
61
|
*/
|
|
38
62
|
function disabledProps(child, isDisabled) {
|
|
39
63
|
if (!isDisabled)
|