@konce-pt/react 0.8.3 → 0.8.5
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/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/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/public-api.d.ts +13 -1
- 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/package.json +16 -9
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# KptChatMessage (@konce-pt/react/chat)
|
|
2
|
+
|
|
3
|
+
A single chat bubble: content, a clock, a send status, and optionally an avatar, a quote,
|
|
4
|
+
attachments and reactions.
|
|
5
|
+
Import: `import { KptChatMessage } from '@konce-pt/react/chat';`
|
|
6
|
+
|
|
7
|
+
## DOM
|
|
8
|
+
<div class="kpt-chat-message-host" role="article" aria-label="Anna, 09:22, Read">
|
|
9
|
+
<div class="kpt-chat-message__row" data-align="start">
|
|
10
|
+
<span class="kpt-chat-message__avatar">…KptAvatar…</span>
|
|
11
|
+
<div class="kpt-chat-message__stack">
|
|
12
|
+
<span class="kpt-chat-message__author">Anna Kowalska</span>
|
|
13
|
+
<div class="kpt-chat-message__bubble"
|
|
14
|
+
data-variant="neutral" data-align="start" data-position="last" data-tail>
|
|
15
|
+
<button class="kpt-chat-message__quote">…</button>
|
|
16
|
+
<p class="kpt-chat-message__text">…</p>
|
|
17
|
+
<ul class="kpt-chat-message__files">…</ul>
|
|
18
|
+
<span class="kpt-chat-message__meta"><time>09:22</time>…</span>
|
|
19
|
+
<ul class="kpt-chat-message__reactions">…</ul>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<span class="kpt-chat-message__actions">…KptIconButton, .kpt-chat-message__picker…</span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
## Props
|
|
27
|
+
- `message`: `KptChatMessage` — the whole object; the single props below win over it
|
|
28
|
+
- `author`, `text`, `time`, `status`, `attachments`, `reactions`, `replyTo`
|
|
29
|
+
- `align`: `'start' | 'end'`; `variant`: `'neutral' | 'primary' | 'solid' | 'ghost' | 'system'`
|
|
30
|
+
- `position`: `'single' | 'first' | 'middle' | 'last'`; `system`: boolean
|
|
31
|
+
- `showAuthor`, `showAvatar`, `showMeta` (default true), `tail` (default true), `actions`
|
|
32
|
+
- `reactionOptions`: `readonly KptChatReactionOption[]` (default `KPT_CHAT_REACTIONS`) — the picker set
|
|
33
|
+
- `onRetry`, `onReply`: `() => void`; `onReact`: `(emoji: string) => void`; `onQuoted`: `(quote) => void`
|
|
34
|
+
- `children` — bubble content; it renders next to `text`, so the bubble takes anything
|
|
35
|
+
- `className`, `ref` and every other `<div>` prop go to the host
|
|
36
|
+
|
|
37
|
+
## The default variant follows the alignment
|
|
38
|
+
Your own message gets the full `solid`, someone else's gets `neutral`. The theme is monochrome, so
|
|
39
|
+
the difference between bubbles is lightness, not hue — `--kpt-color-primary-subtle` has exactly the
|
|
40
|
+
lightness of `--kpt-color-muted`, which would make the two sides indistinguishable. For a quieter
|
|
41
|
+
list set `variant="primary"` by hand.
|
|
42
|
+
|
|
43
|
+
## Grouping is what the shape is made of
|
|
44
|
+
`position` decides which corner collapses and which bubble wears the tail — inside a group the
|
|
45
|
+
corner on the sender's side drops to `--kpt-radius-sm`. `KptChat` gets all of it from
|
|
46
|
+
`buildChatRows()`; set it by hand only when you build the list yourself. The tail is skipped for
|
|
47
|
+
`ghost` and `system`, which have no background to cut it out of.
|
|
48
|
+
|
|
49
|
+
## An unsent message must be able to say so
|
|
50
|
+
`status="failed"` outlines the bubble and puts a retry arrow **outside** it, in the action column
|
|
51
|
+
on the outer side of the list. `KptChat` also keeps the meta row visible on such a bubble even in
|
|
52
|
+
the middle of a group — the arrow with nothing to explain it would be a mystery.
|
|
53
|
+
Statuses: `sending` (clock), `sent` (check), `delivered` (double check), `read` (the same icon in
|
|
54
|
+
the accent colour) and `failed`.
|
|
55
|
+
|
|
56
|
+
## Examples
|
|
57
|
+
<KptChatMessage message={msg} align="end" position="last" />
|
|
58
|
+
<KptChatMessage align="end" status="failed" onRetry={resend}>It did not go out</KptChatMessage>
|
|
59
|
+
<KptChatMessage system text="Anna joined the conversation" />
|
|
60
|
+
|
|
61
|
+
## i18n
|
|
62
|
+
`retry`, `react`, `replyTo` (`{author}`) and the status names from the `chat` namespace. The clock
|
|
63
|
+
is formatted by `Intl` in the active locale.
|
|
64
|
+
|
|
65
|
+
## Tokens
|
|
66
|
+
`--kpt-color-muted` (incoming) / `--kpt-color-primary` + `--kpt-color-on-primary` (own),
|
|
67
|
+
failed `--kpt-color-danger-subtle` + `--kpt-color-danger-border` — the failed rule also resets the
|
|
68
|
+
text colour, or a white `solid` label would land on pale pink.
|
|
69
|
+
|
|
70
|
+
## Accessibility
|
|
71
|
+
The host is an `article` labelled with author, time and status — content alone would say nothing
|
|
72
|
+
about who wrote it or whether it was sent. The status icon is `aria-hidden`; the meaning travels in
|
|
73
|
+
the label. A system notice is a `note`, never an `article`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef } from 'react';
|
|
2
|
+
import type { KptChatAuthor } from '@konce-pt/chat';
|
|
3
|
+
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
4
|
+
export interface KptChatTypingProps extends Omit<ComponentPropsWithRef<'div'>, 'children'>, KptI18nOverrides {
|
|
5
|
+
/** Osoby piszące w tej chwili. Pusta lista chowa wskaźnik. */
|
|
6
|
+
authors?: readonly KptChatAuthor[];
|
|
7
|
+
/** Własna etykieta zamiast tej ze słownika. */
|
|
8
|
+
label?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Wskaźnik pisania — trzy kropki i informacja, kto pisze.
|
|
12
|
+
*
|
|
13
|
+
* Pusta lista autorów nie renderuje niczego, więc można go trzymać w drzewie na stałe
|
|
14
|
+
* i sterować wyłącznie danymi.
|
|
15
|
+
*
|
|
16
|
+
* Element jest `role="status"` z `aria-live="polite"`: czytnik ogłasza wejście i zniknięcie
|
|
17
|
+
* wskaźnika, ale nie przerywa czytania wiadomości. Sama animacja kropek nic nie mówi —
|
|
18
|
+
* informację niesie etykieta.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* <KptChatTyping authors={typing} />
|
|
22
|
+
*/
|
|
23
|
+
export declare function KptChatTyping({ authors, label, locale, dictionary, className, ...rest }: Readonly<KptChatTypingProps>): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../internal/cn.js";
|
|
3
|
+
import { useKptMessages } from "../i18n/i18n-context.js";
|
|
4
|
+
/**
|
|
5
|
+
* Wskaźnik pisania — trzy kropki i informacja, kto pisze.
|
|
6
|
+
*
|
|
7
|
+
* Pusta lista autorów nie renderuje niczego, więc można go trzymać w drzewie na stałe
|
|
8
|
+
* i sterować wyłącznie danymi.
|
|
9
|
+
*
|
|
10
|
+
* Element jest `role="status"` z `aria-live="polite"`: czytnik ogłasza wejście i zniknięcie
|
|
11
|
+
* wskaźnika, ale nie przerywa czytania wiadomości. Sama animacja kropek nic nie mówi —
|
|
12
|
+
* informację niesie etykieta.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <KptChatTyping authors={typing} />
|
|
16
|
+
*/
|
|
17
|
+
export function KptChatTyping({ authors = [], label = '', locale, dictionary, className, ...rest }) {
|
|
18
|
+
const { t } = useKptMessages({ locale, dictionary });
|
|
19
|
+
if (authors.length === 0 && !label)
|
|
20
|
+
return null;
|
|
21
|
+
/*
|
|
22
|
+
* Jedna osoba jest wymieniona z imienia, przy większej liczbie liczy się głowy —
|
|
23
|
+
* lista nazwisk rozjeżdża wiersz i i tak nikt jej nie czyta.
|
|
24
|
+
*/
|
|
25
|
+
const text = label ||
|
|
26
|
+
(authors.length === 1
|
|
27
|
+
? t('chat.typing', { name: authors[0].name })
|
|
28
|
+
: t('chat.typingMany', { count: authors.length }));
|
|
29
|
+
return (_jsx("div", { className: cn('kpt-chat-typing-host', className), role: "status", "aria-live": "polite", ...rest, children: _jsxs("div", { className: "kpt-chat-typing", children: [_jsxs("span", { className: "kpt-chat-typing__bubble", "aria-hidden": "true", children: [_jsx("span", { className: "kpt-chat-typing__dot" }), _jsx("span", { className: "kpt-chat-typing__dot" }), _jsx("span", { className: "kpt-chat-typing__dot" })] }), text ? _jsx("span", { className: "kpt-chat-typing__label", children: text }) : null] }) }));
|
|
30
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# KptChatTyping (@konce-pt/react/chat)
|
|
2
|
+
|
|
3
|
+
The typing indicator — three dots and a line saying who is typing.
|
|
4
|
+
Import: `import { KptChatTyping } from '@konce-pt/react/chat';`
|
|
5
|
+
|
|
6
|
+
## DOM
|
|
7
|
+
<div class="kpt-chat-typing-host" role="status" aria-live="polite">
|
|
8
|
+
<div class="kpt-chat-typing">
|
|
9
|
+
<span class="kpt-chat-typing__bubble" aria-hidden="true">
|
|
10
|
+
<span class="kpt-chat-typing__dot"></span>
|
|
11
|
+
<span class="kpt-chat-typing__dot"></span>
|
|
12
|
+
<span class="kpt-chat-typing__dot"></span>
|
|
13
|
+
</span>
|
|
14
|
+
<span class="kpt-chat-typing__label">Anna is typing…</span>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
## Props
|
|
19
|
+
- `authors`: `KptChatAuthor[]` — an empty list renders nothing at all
|
|
20
|
+
- `label`: string — your own wording instead of the dictionary's
|
|
21
|
+
- `className`, `ref` and every other `<div>` prop go to the host
|
|
22
|
+
|
|
23
|
+
## One name, then a headcount
|
|
24
|
+
One person is named; from two up the component counts heads. A list of names breaks the line and
|
|
25
|
+
nobody reads it anyway.
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
<KptChatTyping authors={[anna]} />
|
|
29
|
+
<KptChatTyping authors={typing} />
|
|
30
|
+
<KptChatTyping label="The assistant is thinking…" />
|
|
31
|
+
|
|
32
|
+
## i18n
|
|
33
|
+
`typing` (`{name}`) and `typingMany` (`{count}`) from the `chat` namespace.
|
|
34
|
+
|
|
35
|
+
## Tokens
|
|
36
|
+
The bubble `--kpt-color-surface-variant`, the dots `--kpt-color-text-muted`.
|
|
37
|
+
|
|
38
|
+
## Accessibility
|
|
39
|
+
`role="status"` with `aria-live="polite"` announces the indicator without interrupting a message
|
|
40
|
+
being read. Under `prefers-reduced-motion` the dots stop bouncing and pulse instead — they are the
|
|
41
|
+
only carrier of "someone is typing", so switching them off entirely would remove the message.
|
package/dist/chip/chip.d.ts
CHANGED
|
@@ -2,8 +2,16 @@ import type { ComponentPropsWithRef } from 'react';
|
|
|
2
2
|
import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
|
|
3
3
|
import type { KptHostProps } from '../internal/types.ts';
|
|
4
4
|
export type KptChipVariant = 'neutral' | 'primary' | 'success' | 'danger' | 'warning' | 'info';
|
|
5
|
+
/** Wielkość liter etykiety. */
|
|
6
|
+
export type KptChipTextTransform = 'none' | 'capitalize' | 'uppercase' | 'lowercase';
|
|
5
7
|
export interface KptChipProps extends ComponentPropsWithRef<'span'>, KptI18nOverrides {
|
|
6
8
|
variant?: KptChipVariant;
|
|
9
|
+
/**
|
|
10
|
+
* Wielkość liter etykiety. `capitalize` podnosi pierwszą literę każdego słowa — także po
|
|
11
|
+
* dywizie („Agent-Ready"), bo CSS nie wie, gdzie kończy się wyraz złożony; przy takich
|
|
12
|
+
* napisach pewniejszy jest zapis wprost w treści.
|
|
13
|
+
*/
|
|
14
|
+
textTransform?: KptChipTextTransform;
|
|
7
15
|
/** Dokłada przycisk usuwania. */
|
|
8
16
|
removable?: boolean;
|
|
9
17
|
disabled?: boolean;
|
|
@@ -18,5 +26,6 @@ export interface KptChipProps extends ComponentPropsWithRef<'span'>, KptI18nOver
|
|
|
18
26
|
* @example
|
|
19
27
|
* <KptChip>Angular</KptChip>
|
|
20
28
|
* <KptChip variant="success" removable onRemove={remove}>UI</KptChip>
|
|
29
|
+
* <KptChip textTransform="capitalize">nowość · zestaw startowy</KptChip>
|
|
21
30
|
*/
|
|
22
|
-
export declare function KptChip({ variant, removable, disabled, onRemove, locale, dictionary, hostProps, className, children, ...rest }: Readonly<KptChipProps>): import("react").JSX.Element;
|
|
31
|
+
export declare function KptChip({ variant, textTransform, removable, disabled, onRemove, locale, dictionary, hostProps, className, children, ...rest }: Readonly<KptChipProps>): import("react").JSX.Element;
|
package/dist/chip/chip.js
CHANGED
|
@@ -8,9 +8,10 @@ import { useKptMessages } from "../i18n/i18n-context.js";
|
|
|
8
8
|
* @example
|
|
9
9
|
* <KptChip>Angular</KptChip>
|
|
10
10
|
* <KptChip variant="success" removable onRemove={remove}>UI</KptChip>
|
|
11
|
+
* <KptChip textTransform="capitalize">nowość · zestaw startowy</KptChip>
|
|
11
12
|
*/
|
|
12
|
-
export function KptChip({ variant = 'neutral', removable = false, disabled = false, onRemove, locale, dictionary, hostProps, className, children, ...rest }) {
|
|
13
|
+
export function KptChip({ variant = 'neutral', textTransform = 'none', removable = false, disabled = false, onRemove, locale, dictionary, hostProps, className, children, ...rest }) {
|
|
13
14
|
const { t } = useKptMessages({ locale, dictionary });
|
|
14
15
|
const { className: hostClassName, ...hostRest } = hostProps ?? {};
|
|
15
|
-
return (_jsx("span", { className: cn('kpt-chip-host', hostClassName), ...hostRest, children: _jsxs("span", { className: cn('kpt-chip', disabled && 'kpt-chip--disabled', className), "data-variant": variant, ...rest, children: [children, removable ? (_jsx("button", { type: "button", className: "kpt-chip__remove", disabled: disabled, onClick: onRemove, "aria-label": t('common.remove'), children: _jsx(KptIcon, { name: "x" }) })) : null] }) }));
|
|
16
|
+
return (_jsx("span", { className: cn('kpt-chip-host', hostClassName), ...hostRest, children: _jsxs("span", { className: cn('kpt-chip', disabled && 'kpt-chip--disabled', className), "data-variant": variant, "data-text-transform": textTransform === 'none' ? undefined : textTransform, ...rest, children: [children, removable ? (_jsx("button", { type: "button", className: "kpt-chip__remove", disabled: disabled, onClick: onRemove, "aria-label": t('common.remove'), children: _jsx(KptIcon, { name: "x" }) })) : null] }) }));
|
|
16
17
|
}
|
package/dist/chip/llms.txt
CHANGED
|
@@ -13,6 +13,7 @@ Import: `import { KptChip } from '@konce-pt/react';`
|
|
|
13
13
|
|
|
14
14
|
## Props
|
|
15
15
|
- `variant`: 'neutral' | 'primary' | 'success' | 'danger' | 'warning' | 'info' (default 'neutral')
|
|
16
|
+
- `textTransform`: 'none' | 'capitalize' | 'uppercase' | 'lowercase' (default 'none') — the label's letter case
|
|
16
17
|
- `removable`: boolean — adds the remove button
|
|
17
18
|
- `disabled`: boolean
|
|
18
19
|
- `onRemove()` — fired by the remove button
|
|
@@ -31,6 +32,14 @@ The remove button's label comes from `common.remove` in the dictionary.
|
|
|
31
32
|
<KptChip>React</KptChip>
|
|
32
33
|
<KptChip variant="success" removable onRemove={() => drop(tag)}>UI</KptChip>
|
|
33
34
|
<KptCard.Tags><KptChip variant="info">Daily</KptChip></KptCard.Tags>
|
|
35
|
+
<KptChip textTransform="capitalize">new · starter kit</KptChip>
|
|
36
|
+
|
|
37
|
+
## Letter case
|
|
38
|
+
`textTransform` lands as `data-text-transform` on `.kpt-chip` and sets `text-transform` there, so
|
|
39
|
+
the label can stay in the application's own wording while the chip decides how it is displayed.
|
|
40
|
+
`capitalize` raises the first letter of every word — **including after a hyphen**
|
|
41
|
+
("Agent-Ready"), because CSS does not know where a compound word ends. For labels with a hyphen,
|
|
42
|
+
spelling it out in the content is the exact route.
|
|
34
43
|
|
|
35
44
|
## Tokens
|
|
36
45
|
`--kpt-color-muted`, `--kpt-color-on-muted` and the semantic colour pairs of each variant
|
|
@@ -6,8 +6,20 @@ export interface KptDataViewProps<T> extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
6
6
|
layout?: KptDataViewLayout;
|
|
7
7
|
defaultLayout?: KptDataViewLayout;
|
|
8
8
|
onLayoutChange?: (layout: KptDataViewLayout) => void;
|
|
9
|
-
/** Rozmiar strony; 0 wyłącza stronicowanie. */
|
|
9
|
+
/** Rozmiar strony; 0 wyłącza stronicowanie. Sterowalny — select paginatora go zmienia. */
|
|
10
10
|
pageSize?: number;
|
|
11
|
+
defaultPageSize?: number;
|
|
12
|
+
onPageSizeChange?: (pageSize: number) => void;
|
|
13
|
+
/** Rozmiary do wyboru w paginatorze. Pusta lista = select z jedną, bieżącą wartością. */
|
|
14
|
+
pageSizeOptions?: readonly number[];
|
|
15
|
+
/** Liczba kolumn siatki jako SUFIT, nie sztywna liczba; 0 = tyle, ile się zmieści. */
|
|
16
|
+
gridColumns?: number;
|
|
17
|
+
defaultGridColumns?: number;
|
|
18
|
+
onGridColumnsChange?: (columns: number) => void;
|
|
19
|
+
/** Wartości w pasku gęstości. Mniej niż dwie = paska nie ma po co pokazywać. */
|
|
20
|
+
gridColumnsOptions?: readonly number[];
|
|
21
|
+
/** Najwęższa sensowna karta — poniżej tego siatka zdejmuje kolumnę, zamiast ściskać. */
|
|
22
|
+
gridMinWidth?: string;
|
|
11
23
|
paginated?: boolean;
|
|
12
24
|
/** Szablon elementu — odpowiednik `<ng-template let-item>` z Angulara. */
|
|
13
25
|
renderItem: (item: T, index: number) => ReactNode;
|
|
@@ -16,6 +28,6 @@ export interface KptDataViewProps<T> extends Omit<ComponentPropsWithRef<'div'>,
|
|
|
16
28
|
* DataView — lista elementów w układzie listy albo siatki, z opcjonalnym stronicowaniem.
|
|
17
29
|
*
|
|
18
30
|
* @example
|
|
19
|
-
* <KptDataView items={products}
|
|
31
|
+
* <KptDataView items={products} defaultPageSize={6} renderItem={(p) => <div>{p.name}</div>} />
|
|
20
32
|
*/
|
|
21
|
-
export declare function KptDataView<T>({ items, layout, defaultLayout, onLayoutChange, pageSize, paginated, renderItem, locale, dictionary, className, ...rest }: Readonly<KptDataViewProps<T>>): import("react").JSX.Element;
|
|
33
|
+
export declare function KptDataView<T>({ items, layout, defaultLayout, onLayoutChange, pageSize, defaultPageSize, onPageSizeChange, pageSizeOptions, gridColumns, defaultGridColumns, onGridColumnsChange, gridColumnsOptions, gridMinWidth, paginated, renderItem, locale, dictionary, className, ...rest }: Readonly<KptDataViewProps<T>>): import("react").JSX.Element;
|
|
@@ -9,17 +9,34 @@ import { KptPaginator } from "../paginator/paginator.js";
|
|
|
9
9
|
* DataView — lista elementów w układzie listy albo siatki, z opcjonalnym stronicowaniem.
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
|
-
* <KptDataView items={products}
|
|
12
|
+
* <KptDataView items={products} defaultPageSize={6} renderItem={(p) => <div>{p.name}</div>} />
|
|
13
13
|
*/
|
|
14
|
-
export function KptDataView({ items = [], layout, defaultLayout = 'grid', onLayoutChange, pageSize = 0, paginated = true, renderItem, locale, dictionary, className, ...rest }) {
|
|
14
|
+
export function KptDataView({ items = [], layout, defaultLayout = 'grid', onLayoutChange, pageSize, defaultPageSize = 0, onPageSizeChange, pageSizeOptions = [], gridColumns, defaultGridColumns = 0, onGridColumnsChange, gridColumnsOptions = [], gridMinWidth = '10rem', paginated = true, renderItem, locale, dictionary, className, ...rest }) {
|
|
15
15
|
const { t } = useKptMessages({ locale, dictionary });
|
|
16
16
|
const [currentLayout, setLayout] = useControllableState({
|
|
17
17
|
value: layout,
|
|
18
18
|
defaultValue: defaultLayout,
|
|
19
19
|
onChange: onLayoutChange,
|
|
20
20
|
});
|
|
21
|
+
const [size, setSize] = useControllableState({
|
|
22
|
+
value: pageSize,
|
|
23
|
+
defaultValue: defaultPageSize,
|
|
24
|
+
onChange: onPageSizeChange,
|
|
25
|
+
});
|
|
26
|
+
const [columns, setColumns] = useControllableState({
|
|
27
|
+
value: gridColumns,
|
|
28
|
+
defaultValue: defaultGridColumns,
|
|
29
|
+
onChange: onGridColumnsChange,
|
|
30
|
+
});
|
|
21
31
|
const [pageIndex, setPageIndex] = useState(0);
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
32
|
+
// Opcje selecta: własna lista albo — gdy pusta — sam bieżący rozmiar.
|
|
33
|
+
const pagerSizeOptions = pageSizeOptions.length ? pageSizeOptions : [size];
|
|
34
|
+
// Pager zostaje na ekranie także wtedy, gdy wszystko mieści się na jednej stronie, o ile jest
|
|
35
|
+
// w czym wybierać. Bez tego wybranie największego rozmiaru chowa select, którym użytkownik
|
|
36
|
+
// właśnie się posłużył — i nie ma jak wrócić do mniejszego.
|
|
37
|
+
// Pasek gęstości ma sens tylko w siatce i tylko wtedy, gdy jest w czym wybierać.
|
|
38
|
+
const showColumns = currentLayout === 'grid' && gridColumnsOptions.length > 1;
|
|
39
|
+
const showPager = paginated && size > 0 && (items.length > size || pagerSizeOptions.length > 1);
|
|
40
|
+
const pageItems = !paginated || size <= 0 ? items : items.slice(pageIndex * size, pageIndex * size + size);
|
|
41
|
+
return (_jsx("div", { className: cn('kpt-data-view-host', className), ...rest, children: _jsxs("div", { className: "kpt-data-view", children: [_jsxs("div", { className: "kpt-data-view__toolbar", children: [_jsx("div", { className: "kpt-data-view__count", children: t('dataView.count', { count: items.length }) }), _jsxs("div", { className: "kpt-data-view__controls", children: [showColumns ? (_jsx("div", { className: "kpt-data-view__columns", role: "group", "aria-label": t('dataView.columns'), children: gridColumnsOptions.map((n) => (_jsxs("button", { type: "button", className: cn(columns === n && 'is-active'), "aria-pressed": columns === n, onClick: () => setColumns(n), children: [_jsx(KptIcon, { name: "grid-dots" }), n] }, n))) })) : null, _jsxs("div", { className: "kpt-data-view__switch", children: [_jsx("button", { type: "button", className: cn(currentLayout === 'list' && 'is-active'), "aria-label": t('dataView.list'), "aria-pressed": currentLayout === 'list', onClick: () => setLayout('list'), children: _jsx(KptIcon, { name: "list" }) }), _jsx("button", { type: "button", className: cn(currentLayout === 'grid' && 'is-active'), "aria-label": t('dataView.grid'), "aria-pressed": currentLayout === 'grid', onClick: () => setLayout('grid'), children: _jsx(KptIcon, { name: "layout-grid" }) })] })] })] }), _jsx("div", { className: "kpt-data-view__content", "data-layout": currentLayout, "data-columns": columns ? '' : undefined, style: { '--_min': gridMinWidth, '--_cols': columns || undefined }, children: pageItems.length ? (pageItems.map((item, index) => (_jsx("div", { className: "kpt-data-view__item", children: renderItem(item, index) }, index)))) : (_jsx("div", { className: "kpt-data-view__empty", children: t('dataView.empty') })) }), showPager ? (_jsx(KptPaginator, { length: items.length, pageIndex: pageIndex, onPageIndexChange: setPageIndex, pageSize: size, onPageSizeChange: setSize, pageSizeOptions: pagerSizeOptions })) : null] }) }));
|
|
25
42
|
}
|
package/dist/data-view/llms.txt
CHANGED
|
@@ -20,7 +20,11 @@ Import: `import { KptDataView } from '@konce-pt/react';`
|
|
|
20
20
|
## Props
|
|
21
21
|
- `items`: `T[]`
|
|
22
22
|
- `layout` / `defaultLayout` / `onLayoutChange` — `'list' | 'grid'` (default `'grid'`)
|
|
23
|
-
- `pageSize
|
|
23
|
+
- `pageSize` / `defaultPageSize` / `onPageSizeChange` — number; 0 turns pagination off
|
|
24
|
+
- `pageSizeOptions`: number[] (empty = the select offers the current size only)
|
|
25
|
+
- `gridColumns` / `defaultGridColumns` / `onGridColumnsChange` — number; 0 = as many as fit
|
|
26
|
+
- `gridColumnsOptions`: number[] (fewer than two = no density bar)
|
|
27
|
+
- `gridMinWidth`: string (default `10rem`) — the narrowest card worth rendering
|
|
24
28
|
- `paginated`: boolean (default true)
|
|
25
29
|
- `renderItem(item, index)` — required; the equivalent of `<ng-template let-item>`
|
|
26
30
|
- `className`, `ref` and every other `<div>` prop go to the host
|
|
@@ -29,20 +33,29 @@ Import: `import { KptDataView } from '@konce-pt/react';`
|
|
|
29
33
|
`data-layout` on the content switches the grid rules; the toolbar only flips it. Everything else —
|
|
30
34
|
the item's markup — is the caller's, which is why there are no card props here.
|
|
31
35
|
|
|
36
|
+
## The column count is a ceiling
|
|
37
|
+
The density bar shows up in the grid layout when `gridColumnsOptions` holds more than one value.
|
|
38
|
+
The chosen number caps the grid rather than pinning it: the track minimum grows to the width of
|
|
39
|
+
one of N columns, so a narrow container drops to two or one column instead of squeezing cards
|
|
40
|
+
below `gridMinWidth`.
|
|
41
|
+
|
|
32
42
|
## The pager appears when it is needed
|
|
33
|
-
Only when `paginated`,
|
|
34
|
-
under a single page of results is noise
|
|
43
|
+
Only when `paginated`, the page size is above 0, and either the items outgrow one page or
|
|
44
|
+
`pageSizeOptions` holds more than one size — a paginator under a single page of results is noise,
|
|
45
|
+
but hiding it right after someone picks the largest size would strand them there.
|
|
35
46
|
|
|
36
47
|
## Examples
|
|
37
|
-
<KptDataView items={products}
|
|
48
|
+
<KptDataView items={products} defaultPageSize={6} renderItem={(p) => <div>{p.name}</div>} />
|
|
49
|
+
<KptDataView items={products} defaultPageSize={6} pageSizeOptions={[3, 6, 9]} renderItem={renderCard} />
|
|
50
|
+
<KptDataView items={products} defaultGridColumns={2} gridColumnsOptions={[2, 3, 4]} gridMinWidth="14rem" renderItem={renderCard} />
|
|
38
51
|
<KptDataView items={products} layout="list" paginated={false} renderItem={renderCard} />
|
|
39
52
|
|
|
40
53
|
## i18n
|
|
41
|
-
The count
|
|
42
|
-
`list`, `grid`, `empty`).
|
|
54
|
+
The count, the two switch buttons and the density group come from the `dataView` namespace
|
|
55
|
+
(`count` with `{count}`, `list`, `grid`, `columns`, `empty`).
|
|
43
56
|
|
|
44
57
|
## Tokens
|
|
45
|
-
The grid gap `--kpt-space-
|
|
58
|
+
The grid gap `--kpt-space-3`, the active switch `--kpt-color-primary`.
|
|
46
59
|
|
|
47
60
|
## Accessibility
|
|
48
61
|
Both layout buttons carry labels from the dictionary; an empty list renders a message instead of
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isDevBuild(): boolean;
|
|
@@ -21,6 +21,11 @@ export interface KptOverlayProps {
|
|
|
21
21
|
* zasłoniłby to pole i zabrałby mu każde kliknięcie.
|
|
22
22
|
*/
|
|
23
23
|
withBackdrop?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Otwieraj nad wyzwalaczem, dopóki jest tam miejsce (odpowiednik `placement="top"` w porcie
|
|
26
|
+
* Angulara, gdzie kolejnością pozycji steruje CDK). Bez tego panel wychodzi spod wyzwalacza.
|
|
27
|
+
*/
|
|
28
|
+
preferAbove?: boolean;
|
|
24
29
|
children: ReactNode;
|
|
25
30
|
}
|
|
26
|
-
export declare function KptOverlay({ open, anchor, onClose, matchAnchorWidth, offset, align, withBackdrop, children, }: Readonly<KptOverlayProps>): import("react").ReactPortal | null;
|
|
31
|
+
export declare function KptOverlay({ open, anchor, onClose, matchAnchorWidth, offset, align, withBackdrop, preferAbove, children, }: Readonly<KptOverlayProps>): import("react").ReactPortal | null;
|
package/dist/internal/overlay.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createPortal } from 'react-dom';
|
|
|
4
4
|
import { useKptOverlayContainer } from "./overlay-container.js";
|
|
5
5
|
/** Ta sama warstwa co `--kpt-z-overlay` w tokenach — panel ma być nad treścią aplikacji. */
|
|
6
6
|
const Z_INDEX = 'var(--kpt-z-overlay, 1100)';
|
|
7
|
-
export function KptOverlay({ open, anchor, onClose, matchAnchorWidth = false, offset = 4, align = 'start', withBackdrop = true, children, }) {
|
|
7
|
+
export function KptOverlay({ open, anchor, onClose, matchAnchorWidth = false, offset = 4, align = 'start', withBackdrop = true, preferAbove = false, children, }) {
|
|
8
8
|
const container = useKptOverlayContainer();
|
|
9
9
|
const panelRef = useRef(null);
|
|
10
10
|
const [position, setPosition] = useState(null);
|
|
@@ -17,13 +17,15 @@ export function KptOverlay({ open, anchor, onClose, matchAnchorWidth = false, of
|
|
|
17
17
|
const below = window.innerHeight - rect.bottom;
|
|
18
18
|
// Odbicie w górę tylko wtedy, gdy pod spodem naprawdę brakuje miejsca, a nad wyzwalaczem
|
|
19
19
|
// jest go więcej — inaczej panel skakałby przy każdym przewinięciu o kilka pikseli.
|
|
20
|
-
|
|
20
|
+
// Przy `preferAbove` jest odwrotnie: panel stoi nad wyzwalaczem, dopóki się tam mieści.
|
|
21
|
+
const fitsAbove = panelHeight === 0 || rect.top > panelHeight + offset;
|
|
22
|
+
const flip = preferAbove ? fitsAbove : panelHeight > 0 && below < panelHeight + offset && rect.top > below;
|
|
21
23
|
setPosition({
|
|
22
24
|
top: flip ? rect.top - panelHeight - offset : rect.bottom + offset,
|
|
23
25
|
left: align === 'end' ? rect.right - panelWidth : rect.left,
|
|
24
26
|
...(matchAnchorWidth ? { width: rect.width } : {}),
|
|
25
27
|
});
|
|
26
|
-
}, [anchor, matchAnchorWidth, offset, align]);
|
|
28
|
+
}, [anchor, matchAnchorWidth, offset, align, preferAbove]);
|
|
27
29
|
useLayoutEffect(() => {
|
|
28
30
|
if (!open) {
|
|
29
31
|
setPosition(null);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# KptMention / KptMentions (@konce-pt/react)
|
|
2
|
+
|
|
3
|
+
Mentions in text: `@someone` as an element you can hover to see a peek card with the rest of the
|
|
4
|
+
connections. `KptMention` is a single mention; `KptMentions` turns a whole paragraph into text plus
|
|
5
|
+
mentions.
|
|
6
|
+
Import: `import { KptMention, KptMentions } from '@konce-pt/react';`
|
|
7
|
+
|
|
8
|
+
## DOM
|
|
9
|
+
<span class="kpt-mentions-host">
|
|
10
|
+
<span class="kpt-mentions__text">A question for </span>
|
|
11
|
+
<span class="kpt-peek-host kpt-mention-host">
|
|
12
|
+
<button class="kpt-mention" data-kind="person">@Anna Kowalska</button>
|
|
13
|
+
</span>
|
|
14
|
+
<span class="kpt-mentions__text"> about the estimate.</span>
|
|
15
|
+
</span>
|
|
16
|
+
|
|
17
|
+
## KptMention — props
|
|
18
|
+
- `item`: `KptMentionItem` — the whole entry at once; the single props below win over it
|
|
19
|
+
- `id`, `label`, `kind`: string — `kind` lands in `data-kind`, so it can be styled per type
|
|
20
|
+
- `prefix`: string (default '@'), `href`: string | null — with `href` the mention is a link
|
|
21
|
+
- `placement`, `openDelay` — passed to `KptPeek`
|
|
22
|
+
- `disabled`: boolean — no card; the mention stays a plain highlighted word
|
|
23
|
+
- `children`: ReactNode — the card
|
|
24
|
+
|
|
25
|
+
## KptMentions — props
|
|
26
|
+
- `text`: string — the content with `@id` or `@[Name with spaces](id)`
|
|
27
|
+
- `items`: `readonly KptMentionItem[]` — entries the ids resolve against
|
|
28
|
+
- `prefix`, `placement` — passed to every mention
|
|
29
|
+
- `renderCard`: `(item, id, label) => ReactNode` — the card; without it mentions are plain words
|
|
30
|
+
|
|
31
|
+
## Example
|
|
32
|
+
<KptMentions
|
|
33
|
+
text={note}
|
|
34
|
+
items={people}
|
|
35
|
+
renderCard={(item, id, label) => <KptCard>{item?.data?.role ?? label}</KptCard>}
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
## The text stays text
|
|
39
|
+
`KptMentions` does not render HTML: the paragraph goes in as a plain string, `parseMentionText()`
|
|
40
|
+
from `@konce-pt/mention` splits it into segments and the mention is an element the library builds
|
|
41
|
+
itself. An application does not have to push user-written content through a sanitizer to get
|
|
42
|
+
mentions out of it.
|
|
43
|
+
|
|
44
|
+
An id that is not on the list still renders — the label falls back to the id, because a note has to
|
|
45
|
+
stay readable after someone leaves the team. Without `renderCard` the mention is a highlighted
|
|
46
|
+
word, not a button: there is nothing to open, so it does not pretend otherwise.
|
|
47
|
+
|
|
48
|
+
## Mentions in the editor
|
|
49
|
+
`KptRichText` takes `mentions`, `mentionLimit` and `onMention`: typing `@` opens the suggestion
|
|
50
|
+
list, arrows and Enter (or Tab) insert the mention as
|
|
51
|
+
`<span class="kpt-mention" data-mention="id" contenteditable="false">`. The id survives saving, and
|
|
52
|
+
`contenteditable="false"` keeps the mention in one piece.
|
|
53
|
+
|
|
54
|
+
## Tokens
|
|
55
|
+
The mention is underlined with a **dotted** line: `--kpt-color-primary-border` at rest, darkening
|
|
56
|
+
to `--kpt-color-primary` on hover and while the card is open. Dotted, not solid — a solid underline
|
|
57
|
+
would read as a link, and a mention usually leads nowhere, it opens a card. Text
|
|
58
|
+
`--kpt-color-primary`; a mention without a card `--kpt-color-text` + `--kpt-font-weight-medium` and
|
|
59
|
+
no line at all, since there is nothing to promise. Focus ring `--kpt-color-focus-ring`. The card
|
|
60
|
+
takes its own tokens from `KptPeek`.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactNode } from 'react';
|
|
2
|
+
import type { KptMentionItem } from '@konce-pt/mention';
|
|
3
|
+
import { type KptPeekPlacement } from '../peek/peek.tsx';
|
|
4
|
+
export interface KptMentionProps extends Omit<ComponentPropsWithRef<'span'>, 'id' | 'prefix' | 'children'> {
|
|
5
|
+
/** Cała pozycja naraz. Pojedyncze propsy poniżej mają nad nią priorytet. */
|
|
6
|
+
item?: KptMentionItem | null;
|
|
7
|
+
/** Identyfikator zapisany w treści — nie zmienia się przy zmianie nazwy. */
|
|
8
|
+
id?: string;
|
|
9
|
+
/** Nazwa do pokazania; bez niej bierze się ją z `item`, a w ostateczności z `id`. */
|
|
10
|
+
label?: string;
|
|
11
|
+
/** Rodzaj (osoba, zadanie, dokument) — ląduje w `data-kind`, więc da się go ostylować. */
|
|
12
|
+
kind?: string;
|
|
13
|
+
/** Znak przed nazwą. Pusty łańcuch zostawia samą nazwę. */
|
|
14
|
+
prefix?: string;
|
|
15
|
+
/** Adres, pod który prowadzi klik. Bez niego wzmianka jest przyciskiem otwierającym kartę. */
|
|
16
|
+
href?: string | null;
|
|
17
|
+
placement?: KptPeekPlacement;
|
|
18
|
+
openDelay?: number;
|
|
19
|
+
/** Wyłączona wzmianka nie otwiera karty — zostaje samym napisem. */
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
/** Treść karty. Bez niej wzmianka też zostaje napisem: nie ma czego pokazać. */
|
|
22
|
+
children?: ReactNode;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Wzmianka w tekście — `@ktoś` albo `@coś` jako element, na który da się najechać i zobaczyć
|
|
26
|
+
* kartę podglądu z resztą powiązań. Treść karty daje aplikacja przez `children`: biblioteka nie
|
|
27
|
+
* wie, czym jest wzmiankowana rzecz, a osoba, zadanie i dokument wyglądają inaczej.
|
|
28
|
+
*
|
|
29
|
+
* Wyzwalacz jest `<button>`, a nie `<span>`: karta otwiera się też z klawiatury i dotyku
|
|
30
|
+
* (przypięcie), a to wymaga elementu, na który da się wejść Tabem. Z `href` staje się linkiem,
|
|
31
|
+
* bo wtedy klik prowadzi gdzieś naprawdę.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* <KptMention id="anna.k" label="Anna Kowalska">
|
|
35
|
+
* <KptCard>Profil, projekty, kontakt…</KptCard>
|
|
36
|
+
* </KptMention>
|
|
37
|
+
*/
|
|
38
|
+
export declare function KptMention({ item, id, label, kind, prefix, href, placement, openDelay, disabled, children, className, ...rest }: Readonly<KptMentionProps>): import("react").JSX.Element;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../internal/cn.js";
|
|
3
|
+
import { KptPeek } from "../peek/peek.js";
|
|
4
|
+
/**
|
|
5
|
+
* Wzmianka w tekście — `@ktoś` albo `@coś` jako element, na który da się najechać i zobaczyć
|
|
6
|
+
* kartę podglądu z resztą powiązań. Treść karty daje aplikacja przez `children`: biblioteka nie
|
|
7
|
+
* wie, czym jest wzmiankowana rzecz, a osoba, zadanie i dokument wyglądają inaczej.
|
|
8
|
+
*
|
|
9
|
+
* Wyzwalacz jest `<button>`, a nie `<span>`: karta otwiera się też z klawiatury i dotyku
|
|
10
|
+
* (przypięcie), a to wymaga elementu, na który da się wejść Tabem. Z `href` staje się linkiem,
|
|
11
|
+
* bo wtedy klik prowadzi gdzieś naprawdę.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* <KptMention id="anna.k" label="Anna Kowalska">
|
|
15
|
+
* <KptCard>Profil, projekty, kontakt…</KptCard>
|
|
16
|
+
* </KptMention>
|
|
17
|
+
*/
|
|
18
|
+
export function KptMention({ item = null, id = '', label = '', kind = '', prefix = '@', href = null, placement = 'bottom', openDelay = 300, disabled = false, children, className, ...rest }) {
|
|
19
|
+
const display = prefix + (label || item?.label || id || item?.id || '');
|
|
20
|
+
const resolvedKind = kind || item?.kind || undefined;
|
|
21
|
+
const plain = disabled || !children;
|
|
22
|
+
if (plain) {
|
|
23
|
+
// Bez karty wzmianka nie udaje kontrolki: zostaje wyróżnionym napisem, którego nie ma
|
|
24
|
+
// po co dotykać Tabem.
|
|
25
|
+
return (_jsx("span", { className: cn('kpt-mention-host', className), ...rest, children: _jsx("span", { className: "kpt-mention", "data-plain": "", "data-kind": resolvedKind, children: display }) }));
|
|
26
|
+
}
|
|
27
|
+
const trigger = href ? (_jsx("a", { className: "kpt-mention", href: href, "data-kind": resolvedKind, children: display })) : (_jsx("button", { type: "button", className: "kpt-mention", "data-kind": resolvedKind, children: display }));
|
|
28
|
+
return (_jsx(KptPeek, { className: cn('kpt-mention-host', className), trigger: trigger, placement: placement, openDelay: openDelay, label: display, ...rest, children: children }));
|
|
29
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ComponentPropsWithRef, ReactNode } from 'react';
|
|
2
|
+
import { type KptMentionItem } from '@konce-pt/mention';
|
|
3
|
+
import type { KptPeekPlacement } from '../peek/peek.tsx';
|
|
4
|
+
export interface KptMentionsProps<T = unknown> extends Omit<ComponentPropsWithRef<'span'>, 'children' | 'prefix'> {
|
|
5
|
+
/** Treść z wzmiankami: `@id` albo `@[Nazwa ze spacją](id)`. */
|
|
6
|
+
text?: string;
|
|
7
|
+
/** Pozycje, po których rozwiązują się identyfikatory. Nieznane `id` zostaje etykietą. */
|
|
8
|
+
items?: readonly KptMentionItem<T>[];
|
|
9
|
+
/** Znak przed nazwą we wzmiance. */
|
|
10
|
+
prefix?: string;
|
|
11
|
+
placement?: KptPeekPlacement;
|
|
12
|
+
/** Karta wzmianki. Bez niej wzmianki są samym napisem, bo nie ma czego pokazać. */
|
|
13
|
+
renderCard?: (item: KptMentionItem<T> | null, id: string, label: string) => ReactNode;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Akapit ze wzmiankami — tekst wchodzi jako zwykły napis, a `@wzmianki` wychodzą z niego jako
|
|
17
|
+
* elementy z kartą podglądu. Rozbiciem zajmuje się `parseMentionText()` z `@konce-pt/mention`
|
|
18
|
+
* (czysty TS z testami), więc oba porty czytają tę samą treść tak samo.
|
|
19
|
+
*
|
|
20
|
+
* Wzmianek nie renderujemy z HTML-a: treść zostaje tekstem, a wzmianka jest elementem, który
|
|
21
|
+
* biblioteka sama zbudowała. Aplikacja nie musi więc przepuszczać przez sanitizer czegoś,
|
|
22
|
+
* co i tak pochodzi od jej użytkowników.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* <KptMentions
|
|
26
|
+
* text={note}
|
|
27
|
+
* items={people}
|
|
28
|
+
* renderCard={(item, id, label) => <KptCard>{item?.data?.role ?? label}</KptCard>}
|
|
29
|
+
* />
|
|
30
|
+
*/
|
|
31
|
+
export declare function KptMentions<T = unknown>({ text, items, prefix, placement, renderCard, className, ...rest }: Readonly<KptMentionsProps<T>>): import("react").JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { parseMentionText } from '@konce-pt/mention';
|
|
3
|
+
import { cn } from "../internal/cn.js";
|
|
4
|
+
import { KptMention } from "./mention.js";
|
|
5
|
+
/**
|
|
6
|
+
* Akapit ze wzmiankami — tekst wchodzi jako zwykły napis, a `@wzmianki` wychodzą z niego jako
|
|
7
|
+
* elementy z kartą podglądu. Rozbiciem zajmuje się `parseMentionText()` z `@konce-pt/mention`
|
|
8
|
+
* (czysty TS z testami), więc oba porty czytają tę samą treść tak samo.
|
|
9
|
+
*
|
|
10
|
+
* Wzmianek nie renderujemy z HTML-a: treść zostaje tekstem, a wzmianka jest elementem, który
|
|
11
|
+
* biblioteka sama zbudowała. Aplikacja nie musi więc przepuszczać przez sanitizer czegoś,
|
|
12
|
+
* co i tak pochodzi od jej użytkowników.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <KptMentions
|
|
16
|
+
* text={note}
|
|
17
|
+
* items={people}
|
|
18
|
+
* renderCard={(item, id, label) => <KptCard>{item?.data?.role ?? label}</KptCard>}
|
|
19
|
+
* />
|
|
20
|
+
*/
|
|
21
|
+
export function KptMentions({ text = '', items = [], prefix = '@', placement = 'bottom', renderCard, className, ...rest }) {
|
|
22
|
+
const segments = parseMentionText(text, items);
|
|
23
|
+
return (_jsx("span", { className: cn('kpt-mentions-host', className), ...rest, children: segments.map((segment, index) => segment.kind === 'text' ? (_jsx("span", { className: "kpt-mentions__text", children: segment.text }, index)) : (_jsx(KptMention, { id: segment.id, label: segment.label, kind: segment.item?.kind ?? '', prefix: prefix, placement: placement, children: renderCard?.(segment.item, segment.id, segment.label) }, index))) }));
|
|
24
|
+
}
|