@konce-pt/react 0.11.1 → 0.11.2

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.
@@ -10,7 +10,7 @@ export interface KptButtonGroupProps<T> extends Omit<ComponentPropsWithRef<'div'
10
10
  disabled?: boolean;
11
11
  /** Wywoływane po wyborze — moment, w którym kontrolka staje się dotknięta. */
12
12
  onTouch?: () => void;
13
- /** Chowa etykiety — zostaje sama ikona, a `label` zasila `aria-label` i `title`. */
13
+ /** Chowa etykiety — zostaje sama ikona, a `label` zasila `aria-label` i dymek `KptTooltip`. */
14
14
  iconOnly?: boolean;
15
15
  /** Propsy wrappera hosta (`.kpt-button-group-host`). */
16
16
  hostProps?: KptHostProps<'span'>;
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../internal/cn.js";
3
3
  import { useControllableState } from "../internal/use-controllable-state.js";
4
4
  import { KptIcon } from "../icon/icon.js";
5
+ import { KptTooltip } from "../tooltip/tooltip.js";
5
6
  /**
6
7
  * Segmentowana grupa przycisków (toggle) — pojedynczy wybór z opcji.
7
8
  *
@@ -30,6 +31,6 @@ export function KptButtonGroup({ options = [], value, defaultValue, onValueChang
30
31
  };
31
32
  return (_jsx("span", { className: cn('kpt-button-group-host', hostClassName), ...hostRest, children: _jsx("div", { className: cn('kpt-button-group', className), role: "group", "data-size": size, "data-icon-only": iconOnly ? '' : undefined, ...rest, children: options.map((option) => {
32
33
  const active = option.value === current;
33
- return (_jsxs("button", { type: "button", className: cn('kpt-button-group__item', active && 'is-active'), disabled: disabled || option.disabled, "aria-pressed": active, "aria-label": iconOnly ? option.label : undefined, title: iconOnly ? option.label : undefined, onClick: () => select(option), children: [option.icon ? _jsx(KptIcon, { className: "kpt-button-group__icon", name: option.icon }) : null, iconOnly ? null : _jsx("span", { className: "kpt-button-group__label", children: option.label })] }, String(option.value)));
34
+ return (_jsx(KptTooltip, { text: iconOnly ? option.label : '', children: _jsxs("button", { type: "button", className: cn('kpt-button-group__item', active && 'is-active'), disabled: disabled || option.disabled, "aria-pressed": active, "aria-label": iconOnly ? option.label : undefined, onClick: () => select(option), children: [option.icon ? _jsx(KptIcon, { className: "kpt-button-group__icon", name: option.icon }) : null, iconOnly ? null : _jsx("span", { className: "kpt-button-group__label", children: option.label })] }) }, String(option.value)));
34
35
  }) }) }));
35
36
  }
@@ -5,6 +5,63 @@ import { composeRefs } from "../internal/compose-refs.js";
5
5
  const FOCUSABLE = 'a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])';
6
6
  /** Ile czasu ma wskaźnik na przejście z elementu na dymek, zanim ten zniknie (WCAG 1.4.13). */
7
7
  const HIDE_DELAY_MS = 120;
8
+ /** Strona przeciwna i dwie boczne — kolejność prób, gdy zadana strona się nie nadaje. */
9
+ const FALLBACKS = {
10
+ top: ['top', 'bottom', 'right', 'left'],
11
+ bottom: ['bottom', 'top', 'right', 'left'],
12
+ left: ['left', 'right', 'top', 'bottom'],
13
+ right: ['right', 'left', 'top', 'bottom'],
14
+ };
15
+ /**
16
+ * Ustawia dymek przy hoście, na pierwszej stronie z `FALLBACKS`, która się nadaje: dymek mieści się
17
+ * w oknie, a krawędź hosta od tej strony nie jest niczym przykryta (przyklejony nagłówek, menu, panel
18
+ * — sprawdzane `elementFromPoint` tuż przy krawędzi). Bez tego dymek przycisku wsuniętego pod
19
+ * nagłówek wyskakiwał nad nagłówkiem, odcięty od tego, co opisuje. Gdy żadna strona się nie nadaje,
20
+ * zostaje zadana. Na koniec dymek jest dociśnięty do okna z marginesem 4 px.
21
+ * Bliźniak tej funkcji żyje w drugim porcie — zmiany w obu naraz.
22
+ */
23
+ function placeTooltip(host, tip, preferred) {
24
+ const r = host.getBoundingClientRect();
25
+ const t = tip.getBoundingClientRect();
26
+ const gap = 8;
27
+ const margin = 4;
28
+ const vw = document.documentElement.clientWidth;
29
+ const vh = document.documentElement.clientHeight;
30
+ const at = (side) => {
31
+ switch (side) {
32
+ case 'bottom':
33
+ return { top: r.bottom + gap, left: r.left + r.width / 2 - t.width / 2 };
34
+ case 'left':
35
+ return { top: r.top + r.height / 2 - t.height / 2, left: r.left - t.width - gap };
36
+ case 'right':
37
+ return { top: r.top + r.height / 2 - t.height / 2, left: r.right + gap };
38
+ default:
39
+ return { top: r.top - t.height - gap, left: r.left + r.width / 2 - t.width / 2 };
40
+ }
41
+ };
42
+ const fits = (side) => {
43
+ const { top, left } = at(side);
44
+ // Po osi poprzecznej dymek i tak zostanie dociśnięty do okna — liczy się tylko oś strony.
45
+ return side === 'top' || side === 'bottom'
46
+ ? top >= margin && top + t.height <= vh - margin
47
+ : left >= margin && left + t.width <= vw - margin;
48
+ };
49
+ // Punkt 2 px w głąb hosta przy krawędzi danej strony; widoczny, gdy na wierzchu jest host, jego
50
+ // potomek albo sam dymek (stoi jeszcze w poprzednim miejscu).
51
+ const exposed = (side) => {
52
+ const x = side === 'left' ? r.left + 2 : side === 'right' ? r.right - 2 : r.left + r.width / 2;
53
+ const y = side === 'top' ? r.top + 2 : side === 'bottom' ? r.bottom - 2 : r.top + r.height / 2;
54
+ if (x < 0 || y < 0 || x >= vw || y >= vh)
55
+ return false;
56
+ const hit = document.elementFromPoint(x, y);
57
+ return !hit || host.contains(hit) || tip.contains(hit);
58
+ };
59
+ const side = FALLBACKS[preferred].find((s) => fits(s) && exposed(s)) ?? preferred;
60
+ const { top, left } = at(side);
61
+ tip.dataset['side'] = side;
62
+ tip.style.top = `${Math.min(Math.max(margin, top), Math.max(margin, vh - t.height - margin))}px`;
63
+ tip.style.left = `${Math.min(Math.max(margin, left), Math.max(margin, vw - t.width - margin))}px`;
64
+ }
8
65
  /**
9
66
  * Dymek podpowiedzi pokazywany na hover i focus. Lekki — element dodawany do `body`
10
67
  * i pozycjonowany względem dziecka.
@@ -60,30 +117,7 @@ export function KptTooltip({ text, position = 'top', onlyWhenTruncated = false,
60
117
  const host = hostRef.current;
61
118
  if (!tip || !host)
62
119
  return;
63
- const r = host.getBoundingClientRect();
64
- const t = tip.getBoundingClientRect();
65
- const gap = 8;
66
- let top;
67
- let left;
68
- switch (position) {
69
- case 'bottom':
70
- top = r.bottom + gap;
71
- left = r.left + r.width / 2 - t.width / 2;
72
- break;
73
- case 'left':
74
- top = r.top + r.height / 2 - t.height / 2;
75
- left = r.left - t.width - gap;
76
- break;
77
- case 'right':
78
- top = r.top + r.height / 2 - t.height / 2;
79
- left = r.right + gap;
80
- break;
81
- default:
82
- top = r.top - t.height - gap;
83
- left = r.left + r.width / 2 - t.width / 2;
84
- }
85
- tip.style.top = `${Math.max(4, top)}px`;
86
- tip.style.left = `${Math.max(4, left)}px`;
120
+ placeTooltip(host, tip, position);
87
121
  }, [position]);
88
122
  const onKeyDown = useCallback((event) => {
89
123
  if (event.key === 'Escape')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konce-pt/react",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "Open-source React 19 component library on the Koncept UI design system — the same kpt-* CSS layer and --kpt-* tokens as the Angular port. Zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "konce.pt",
@@ -65,17 +65,17 @@
65
65
  "leaflet": "^1.9.0"
66
66
  },
67
67
  "dependencies": {
68
- "@konce-pt/roadmap": "0.11.1",
69
- "@konce-pt/backdrop": "0.11.1",
70
- "@konce-pt/chart": "0.11.1",
71
- "@konce-pt/table": "0.11.1",
72
- "@konce-pt/icons": "0.11.1",
73
- "@konce-pt/chat": "0.11.1",
74
- "@konce-pt/map": "0.11.1",
75
- "@konce-pt/datetime": "0.11.1",
76
- "@konce-pt/mention": "0.11.1",
77
- "@konce-pt/tokens": "0.11.1",
78
- "@konce-pt/i18n": "0.11.1"
68
+ "@konce-pt/chat": "0.11.2",
69
+ "@konce-pt/backdrop": "0.11.2",
70
+ "@konce-pt/chart": "0.11.2",
71
+ "@konce-pt/i18n": "0.11.2",
72
+ "@konce-pt/mention": "0.11.2",
73
+ "@konce-pt/table": "0.11.2",
74
+ "@konce-pt/roadmap": "0.11.2",
75
+ "@konce-pt/tokens": "0.11.2",
76
+ "@konce-pt/map": "0.11.2",
77
+ "@konce-pt/datetime": "0.11.2",
78
+ "@konce-pt/icons": "0.11.2"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@types/react": "^19.2.0",