@public-ui/sample-react 4.3.0-rc.4 → 4.3.0-rc.6
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/package.json +17 -17
- package/src/components/Navigation.tsx +75 -16
- package/src/components/Sidebar.tsx +37 -8
- package/src/components/abbr/basic.tsx +2 -6
- package/src/components/avatar/routes.ts +2 -0
- package/src/components/avatar/size.tsx +26 -0
- package/src/components/button/buttons-in-fixed-grid.tsx +27 -0
- package/src/components/button/routes.ts +2 -0
- package/src/components/button/variants.tsx +30 -10
- package/src/components/dialog/routes.ts +2 -0
- package/src/components/dialog/scroll-lock.tsx +46 -0
- package/src/components/handout/basic.tsx +2 -6
- package/src/components/link-button/basic.tsx +56 -20
- package/src/components/select/multiple-dropdown.tsx +36 -0
- package/src/components/select/routes.ts +2 -0
- package/src/components/table/interactive-child-elements.tsx +2 -2
- package/src/components/table/routes.ts +1 -1
- package/src/components/table/stateful-with-selection.tsx +72 -82
- package/src/components/table/stateful-with-single-selection.tsx +71 -79
- package/src/scenarios/same-height-of-all-interactive-elements.tsx +16 -16
- package/tsconfig.json +1 -1
- package/src/components/input-checkbox/aria-details.tsx +0 -53
- package/src/components/single-select/aria-details.tsx +0 -93
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@public-ui/sample-react",
|
|
3
|
-
"version": "4.3.0-rc.
|
|
3
|
+
"version": "4.3.0-rc.6",
|
|
4
4
|
"description": "This app contains samples for the KoliBri/Public UI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "EUPL-1.2",
|
|
@@ -9,34 +9,34 @@
|
|
|
9
9
|
"url": "https://github.com/public-ui/kolibri"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@hookform/resolvers": "5.
|
|
12
|
+
"@hookform/resolvers": "5.5.7",
|
|
13
13
|
"@stencil/core": "4.38.3",
|
|
14
|
-
"@types/node": "25.9.
|
|
14
|
+
"@types/node": "25.9.5",
|
|
15
15
|
"@types/papaparse": "5.5.2",
|
|
16
16
|
"@types/react": "19.2.17",
|
|
17
17
|
"@types/react-dom": "19.2.3",
|
|
18
|
-
"adopted-style-sheets": "1.1.9-rc.
|
|
19
|
-
"papaparse": "5.5.
|
|
20
|
-
"react": "19.2.
|
|
21
|
-
"react-dom": "19.2.
|
|
22
|
-
"react-hook-form": "7.
|
|
18
|
+
"adopted-style-sheets": "1.1.9-rc.25",
|
|
19
|
+
"papaparse": "5.5.4",
|
|
20
|
+
"react": "19.2.8",
|
|
21
|
+
"react-dom": "19.2.8",
|
|
22
|
+
"react-hook-form": "7.83.0",
|
|
23
23
|
"react-number-format": "5.4.5",
|
|
24
|
-
"react-router": "7.
|
|
25
|
-
"react-router-dom": "7.
|
|
24
|
+
"react-router": "7.18.2",
|
|
25
|
+
"react-router-dom": "7.18.2",
|
|
26
26
|
"tslib": "2.8.1",
|
|
27
27
|
"typescript": "5.9.3",
|
|
28
28
|
"world_countries_lists": "3.3.0",
|
|
29
29
|
"zod": "4.4.3",
|
|
30
|
-
"@public-ui/
|
|
31
|
-
"@public-ui/
|
|
32
|
-
"@public-ui/react-hook-form-adapter": "4.3.0-rc.
|
|
30
|
+
"@public-ui/react-v19": "4.3.0-rc.6",
|
|
31
|
+
"@public-ui/components": "4.3.0-rc.6",
|
|
32
|
+
"@public-ui/react-hook-form-adapter": "4.3.0-rc.6"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"eslint": "9.39.
|
|
36
|
-
"knip": "6.
|
|
37
|
-
"prettier": "3.
|
|
35
|
+
"eslint": "9.39.5",
|
|
36
|
+
"knip": "6.29.0",
|
|
37
|
+
"prettier": "3.9.6",
|
|
38
38
|
"prettier-plugin-organize-imports": "4.3.0",
|
|
39
|
-
"stylelint": "17.
|
|
39
|
+
"stylelint": "17.14.1"
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
42
42
|
"assets",
|
|
@@ -1,38 +1,87 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { KolInputText, KolTree, KolTreeItem } from '@public-ui/react-v19';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
+
import { useState } from 'react';
|
|
3
4
|
import { useHref, useMatch, useResolvedPath } from 'react-router-dom';
|
|
4
|
-
import { useMobile } from '../hooks/useMobile';
|
|
5
5
|
import type { Route, Routes } from '../shares/types';
|
|
6
6
|
|
|
7
7
|
type NavigationProps = {
|
|
8
8
|
routes: Routes;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
function
|
|
12
|
-
|
|
11
|
+
function isRoutes(x: Route): x is Routes {
|
|
12
|
+
return typeof x === 'object' && x !== null;
|
|
13
|
+
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
function cloneSubtree(route: Route): Route {
|
|
16
|
+
if (!isRoutes(route)) return route; // FC einfach durchreichen
|
|
17
|
+
const out: Routes = {};
|
|
18
|
+
for (const [k, v] of Object.entries(route)) out[k] = cloneSubtree(v);
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function tokenize(query: string): string[] {
|
|
23
|
+
return query.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
function
|
|
26
|
+
function pathMatchesTokens(pathSegments: string[], tokens: string[]): boolean {
|
|
27
|
+
const segs = pathSegments.map((s) => s.toLowerCase());
|
|
28
|
+
const fullPath = segs.join('/');
|
|
29
|
+
|
|
30
|
+
return tokens.every((t) => segs.some((seg) => seg.includes(t)) || fullPath.includes(t));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function walkPath(current: Routes, tokens: string[], path: string[]): Routes {
|
|
34
|
+
const out: Routes = {};
|
|
35
|
+
|
|
36
|
+
for (const [key, value] of Object.entries(current)) {
|
|
37
|
+
const nextPath = [...path, key];
|
|
38
|
+
|
|
39
|
+
const matchesHere = pathMatchesTokens(nextPath, tokens);
|
|
40
|
+
|
|
41
|
+
if (matchesHere) {
|
|
42
|
+
// ganzer Subtree ab hier (weil Pfad bis hier alle Tokens erfüllt)
|
|
43
|
+
out[key] = cloneSubtree(value);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (isRoutes(value)) {
|
|
48
|
+
const filteredChildren = walkPath(value, tokens, nextPath);
|
|
49
|
+
if (Object.keys(filteredChildren).length > 0) {
|
|
50
|
+
out[key] = filteredChildren; // Pfad zu Treffern behalten
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function filterRoutes(routes: Routes, query: string): Routes {
|
|
59
|
+
const tokens = tokenize(query);
|
|
60
|
+
if (tokens.length === 0) return cloneSubtree(routes) as Routes;
|
|
61
|
+
|
|
62
|
+
return walkPath(routes, tokens, []);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function ComponentNavContainer({ children }: { children?: React.ReactNode }): React.ReactNode {
|
|
66
|
+
return <div className="mt scrollable-container">{children}</div>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function TreeItem({ label, to, children, open }: any) {
|
|
24
70
|
const href = useHref(to);
|
|
25
71
|
const resolved = useResolvedPath(to);
|
|
26
72
|
const match = useMatch({ path: resolved.pathname, end: true });
|
|
27
73
|
|
|
28
74
|
return (
|
|
29
|
-
<KolTreeItem _label={label} _href={href} _active={!!match}>
|
|
75
|
+
<KolTreeItem _label={label} _href={href} _active={!!match} _open={open}>
|
|
30
76
|
{children}
|
|
31
77
|
</KolTreeItem>
|
|
32
78
|
);
|
|
33
79
|
}
|
|
34
80
|
|
|
35
81
|
function Navigation({ routes }: NavigationProps): React.ReactNode {
|
|
82
|
+
const [query, setQuery] = useState<string>('');
|
|
83
|
+
let filteredRoutes = filterRoutes(routes, query);
|
|
84
|
+
|
|
36
85
|
const buildSubTree = (parentName: string, children: Route) => {
|
|
37
86
|
return Object.keys(children).map((childName) => {
|
|
38
87
|
const isTreeExample = parentName === 'tree' && childName === 'basic/:subPage';
|
|
@@ -43,15 +92,25 @@ function Navigation({ routes }: NavigationProps): React.ReactNode {
|
|
|
43
92
|
});
|
|
44
93
|
};
|
|
45
94
|
|
|
46
|
-
const parentTreeElements = Object.entries(
|
|
47
|
-
<TreeItem key={parentName} label={parentName} to={parentName}>
|
|
95
|
+
const parentTreeElements = Object.entries(filteredRoutes).map(([parentName, children]) => (
|
|
96
|
+
<TreeItem key={parentName} label={parentName} to={parentName} open={!!query}>
|
|
48
97
|
{buildSubTree(parentName, children)}
|
|
49
98
|
</TreeItem>
|
|
50
99
|
));
|
|
51
100
|
|
|
52
101
|
return (
|
|
53
102
|
<ComponentNavContainer>
|
|
54
|
-
<
|
|
103
|
+
<KolInputText
|
|
104
|
+
_label="Suche"
|
|
105
|
+
_on={{
|
|
106
|
+
onInput: (event: Event) => {
|
|
107
|
+
const input = event.target as HTMLInputElement;
|
|
108
|
+
setQuery(input.value);
|
|
109
|
+
filteredRoutes = filterRoutes(routes, query);
|
|
110
|
+
},
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
<nav className="main-nav">
|
|
55
114
|
<KolTree _label="Navigation" class="block">
|
|
56
115
|
{parentTreeElements}
|
|
57
116
|
</KolTree>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { FC } from 'react';
|
|
2
|
-
import React, { useMemo } from 'react';
|
|
2
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
|
3
3
|
|
|
4
|
-
import { KolButton, KolHeading, KolSelect, KolVersion } from '@public-ui/react-v19';
|
|
4
|
+
import { KolButton, KolDrawer, KolHeading, KolSelect, KolVersion } from '@public-ui/react-v19';
|
|
5
|
+
import { useMobile } from '../hooks/useMobile';
|
|
5
6
|
|
|
6
7
|
import type { SelectOption } from '@public-ui/components';
|
|
8
|
+
import { useLocation } from 'react-router';
|
|
7
9
|
import type { Theme } from '../shares/theme';
|
|
8
10
|
import type { Routes } from '../shares/types';
|
|
9
11
|
import Navigation from './Navigation';
|
|
@@ -81,6 +83,14 @@ export const Sidebar: FC<Props> = ({ version, themes, theme, routes, routeList,
|
|
|
81
83
|
[themes],
|
|
82
84
|
);
|
|
83
85
|
|
|
86
|
+
const isMobile = useMobile();
|
|
87
|
+
const locationWatch = useLocation();
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
drawerElement.current?.close();
|
|
91
|
+
}, [locationWatch]);
|
|
92
|
+
const drawerElement = useRef<HTMLKolDrawerElement>(null);
|
|
93
|
+
|
|
84
94
|
return (
|
|
85
95
|
<aside className="app-sidebar p-4">
|
|
86
96
|
<div className="scrollable-container-wrapper">
|
|
@@ -89,16 +99,35 @@ export const Sidebar: FC<Props> = ({ version, themes, theme, routes, routeList,
|
|
|
89
99
|
<KolVersion _label={version}></KolVersion>
|
|
90
100
|
</div>
|
|
91
101
|
<BuildInformation buildDate={buildDate} commitHash={commitHash} />
|
|
92
|
-
<KolSelect _label="Theme" _options={themeOption} _on={{ onChange: handleThemeSelectChange }} _value={theme} class="mt"></KolSelect>
|
|
93
|
-
<KolHeading _label="Components" _level={2} className="block mt"></KolHeading>
|
|
102
|
+
{!isMobile ? <KolSelect _label="Theme" _options={themeOption} _on={{ onChange: handleThemeSelectChange }} _value={theme} class="mt"></KolSelect> : ''}
|
|
103
|
+
{!isMobile ? <KolHeading _label="Components" _level={2} className="block mt"></KolHeading> : ''}
|
|
94
104
|
<div className="flex flex-justify-between flex-items-center mt">
|
|
95
105
|
<KolButton _icons="kolicon-chevron-left" _hideLabel _label="Previous component" _on={{ onClick: handlePreviousClick }} />
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
|
|
107
|
+
{isMobile ? (
|
|
108
|
+
<div className="flex gap-4 flex-items-center">
|
|
109
|
+
<span className="text-base text-center">
|
|
110
|
+
{formatSampleAsLabel()} ({getIndexOfSample() + 1}/{routeList.length})
|
|
111
|
+
</span>
|
|
112
|
+
<KolButton _label="Navigation" _hideLabel _icons={{ right: 'kolicon-settings' }} _on={{ onClick: () => drawerElement.current?.showModal() }} />
|
|
113
|
+
</div>
|
|
114
|
+
) : (
|
|
115
|
+
<span className="text-base text-center">
|
|
116
|
+
{formatSampleAsLabel()} ({getIndexOfSample() + 1}/{routeList.length})
|
|
117
|
+
</span>
|
|
118
|
+
)}
|
|
119
|
+
|
|
99
120
|
<KolButton _icons="kolicon-chevron-right" _hideLabel _label="Next component" _on={{ onClick: handleNextClick }} />
|
|
100
121
|
</div>
|
|
101
|
-
|
|
122
|
+
{isMobile ? (
|
|
123
|
+
<KolDrawer _align="top" _label="Navigation" _hasCloser={true} ref={drawerElement}>
|
|
124
|
+
<KolSelect _label="Theme" _options={themeOption} _on={{ onChange: handleThemeSelectChange }} _value={theme} class="mt"></KolSelect>
|
|
125
|
+
<KolHeading _label="Components" _level={2} className="block mt"></KolHeading>
|
|
126
|
+
<Navigation routes={routes} />
|
|
127
|
+
</KolDrawer>
|
|
128
|
+
) : (
|
|
129
|
+
<Navigation routes={routes} />
|
|
130
|
+
)}
|
|
102
131
|
</div>
|
|
103
132
|
</aside>
|
|
104
133
|
);
|
|
@@ -7,15 +7,11 @@ import type { FC } from 'react';
|
|
|
7
7
|
export const AbbrBasic: FC = () => (
|
|
8
8
|
<>
|
|
9
9
|
<SampleDescription>
|
|
10
|
-
<p>KolAbbr shows an abbreviation
|
|
10
|
+
<p>KolAbbr shows an abbreviation.</p>
|
|
11
11
|
</SampleDescription>
|
|
12
12
|
|
|
13
13
|
<p>
|
|
14
|
-
I am <KolAbbr
|
|
15
|
-
</p>
|
|
16
|
-
|
|
17
|
-
<p>
|
|
18
|
-
I am <KolAbbr>e.g.</KolAbbr> an abbreviation without label.
|
|
14
|
+
I am <KolAbbr>e.g.</KolAbbr> an abbreviation.
|
|
19
15
|
</p>
|
|
20
16
|
</>
|
|
21
17
|
);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { KolAvatar } from '@public-ui/react-v19';
|
|
2
|
+
import type { FC } from 'react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { SampleDescription } from '../SampleDescription';
|
|
5
|
+
|
|
6
|
+
export const AvatarSize: FC = () => (
|
|
7
|
+
<>
|
|
8
|
+
<SampleDescription>
|
|
9
|
+
<p>
|
|
10
|
+
KolAvatar is <code>6.25rem</code> (100px at the default 16px root font-size) wide/tall by default and simply fills its host element, so it can be
|
|
11
|
+
resized proportionally with plain CSS <code>width</code> and/or <code>height</code> (set through the <code>style</code> prop). Setting only{' '}
|
|
12
|
+
<code>width</code> is the reliable way to size it below the default. If <code>width</code> and <code>height</code> are set to different values, the
|
|
13
|
+
avatar stays square by using the larger of the two - it is centered on its host and may visually extend beyond it on the smaller axis. The
|
|
14
|
+
initials' font size always scales with the resulting size automatically.
|
|
15
|
+
</p>
|
|
16
|
+
</SampleDescription>
|
|
17
|
+
|
|
18
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
19
|
+
<KolAvatar style={{ width: '30px' }} _label="Elke Mustermann" />
|
|
20
|
+
<KolAvatar style={{ width: '30px', height: '45px' }} _label="Marianne" />
|
|
21
|
+
<KolAvatar style={{ width: '60px', height: '30px' }} _color="#0000FF" _label="Christian" />
|
|
22
|
+
<KolAvatar _src="assets/img_avatar.jpg" _label="Elke Mustermann" />
|
|
23
|
+
<KolAvatar style={{ width: '150px' }} _src="assets/img_avatar.jpg" _label="Elke Mustermann" />
|
|
24
|
+
</div>
|
|
25
|
+
</>
|
|
26
|
+
);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { KolButton, KolLinkButton } from '@public-ui/react-v19';
|
|
2
|
+
import type { FC } from 'react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { useToasterService } from '../../hooks/useToasterService';
|
|
5
|
+
import { SampleDescription } from '../SampleDescription';
|
|
6
|
+
|
|
7
|
+
export const ButtonInFixedGrid: FC = () => {
|
|
8
|
+
const { dummyClickEventHandler } = useToasterService();
|
|
9
|
+
|
|
10
|
+
const dummyEventHandler = {
|
|
11
|
+
onClick: dummyClickEventHandler,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
<SampleDescription>
|
|
17
|
+
<p>All this buttons are in a grid with fixed height (60px) and width (25% of screen) and should accept this dimensions.</p>
|
|
18
|
+
</SampleDescription>
|
|
19
|
+
<div className="grid gap-4 fixed-height-grid">
|
|
20
|
+
<KolButton _variant="primary" _icons="kolicon-house" _label="Button" _on={dummyEventHandler} />
|
|
21
|
+
<KolButton _variant="primary" _hideLabel _icons="kolicon-house" _label="Button" _on={dummyEventHandler} />
|
|
22
|
+
<KolLinkButton _variant="primary" _href="#" _icons="kolicon-house" _label="Link-Button" _on={dummyEventHandler} />
|
|
23
|
+
<KolLinkButton _variant="primary" _hideLabel _href="#" _icons="kolicon-house" _label="Link-Button" _on={dummyEventHandler} />
|
|
24
|
+
</div>
|
|
25
|
+
</>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -3,6 +3,7 @@ import { ButtonAccessKey } from './access-key';
|
|
|
3
3
|
import { ButtonAriaDescription } from './aria-description';
|
|
4
4
|
import { ButtonBaselined } from './baselined';
|
|
5
5
|
import { ButtonBasic } from './basic';
|
|
6
|
+
import { ButtonInFixedGrid } from './buttons-in-fixed-grid';
|
|
6
7
|
import { ButtonDisabled } from './disabled';
|
|
7
8
|
import { ButtonExpertSlot } from './expert-slot';
|
|
8
9
|
import { ButtonFocusOptions } from './focus-options';
|
|
@@ -30,5 +31,6 @@ export const BUTTON_ROUTES: Routes = {
|
|
|
30
31
|
'expert-slot': ButtonExpertSlot,
|
|
31
32
|
'row-reverse-tooltip': ButtonRowReverseTooltip,
|
|
32
33
|
'focus-options': ButtonFocusOptions,
|
|
34
|
+
'fixed-grid': ButtonInFixedGrid,
|
|
33
35
|
},
|
|
34
36
|
};
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { KolButton, KolHeading } from '@public-ui/react-v19';
|
|
2
2
|
import type { FC } from 'react';
|
|
3
|
-
import React from 'react';
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
4
|
+
import { useSearchParams } from 'react-router-dom';
|
|
4
5
|
import { useToasterService } from '../../hooks/useToasterService';
|
|
6
|
+
import { fetchVariantData } from '../../shares/fetchVariantData';
|
|
7
|
+
import { getCustomThemes } from '../../shares/store';
|
|
5
8
|
import { SampleDescription } from '../SampleDescription';
|
|
6
9
|
|
|
7
10
|
export const ButtonVariants: FC = () => {
|
|
11
|
+
const [searchParams] = useSearchParams();
|
|
12
|
+
const theme = searchParams.get('theme') ?? getCustomThemes()?.[0]?.key;
|
|
13
|
+
const data = useMemo(() => (theme ? fetchVariantData(theme, 'buttonVariants') : []), [theme]);
|
|
14
|
+
|
|
8
15
|
const { dummyClickEventHandler } = useToasterService();
|
|
9
16
|
|
|
10
17
|
const dummyEventHandler = {
|
|
@@ -14,19 +21,32 @@ export const ButtonVariants: FC = () => {
|
|
|
14
21
|
return (
|
|
15
22
|
<>
|
|
16
23
|
<SampleDescription>
|
|
17
|
-
<p>
|
|
24
|
+
<p>
|
|
25
|
+
This story showcases all available button variants for this theme. You can import <code>ButtonVariantsEnum</code> from your theme to always use the
|
|
26
|
+
right variants.
|
|
27
|
+
</p>
|
|
18
28
|
</SampleDescription>
|
|
19
29
|
|
|
20
30
|
<div className="grid gap-8">
|
|
21
31
|
<section className="grid gap-4">
|
|
22
|
-
<KolHeading _level={2} _label="All
|
|
23
|
-
<div className="flex flex-wrap gap-4">
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
<KolHeading _level={2} _label="All theme exclusive button variants" />
|
|
33
|
+
<div className="flex flex-wrap gap-4 items-center">
|
|
34
|
+
{!Array.isArray(data) || data.length === 0 ? (
|
|
35
|
+
<p>This theme has no variants for this component.</p>
|
|
36
|
+
) : (
|
|
37
|
+
data.map((element) => {
|
|
38
|
+
return <KolButton _icons="kolicon-house" _label={`${element}`} _variant={element} key={element} _on={dummyEventHandler} />;
|
|
39
|
+
})
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
<div className="flex flex-wrap gap-4 items-center">
|
|
43
|
+
{!Array.isArray(data) || data.length === 0 ? (
|
|
44
|
+
<p>This theme has no variants for this component.</p>
|
|
45
|
+
) : (
|
|
46
|
+
data.map((element) => {
|
|
47
|
+
return <KolButton _hideLabel _icons="kolicon-settings" _label={`${element}`} _variant={element} key={element} _on={dummyEventHandler} />;
|
|
48
|
+
})
|
|
49
|
+
)}
|
|
30
50
|
</div>
|
|
31
51
|
</section>
|
|
32
52
|
</div>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Routes } from '../../shares/types';
|
|
2
2
|
import { DialogBasic } from './basic';
|
|
3
|
+
import { DialogScrollLock } from './scroll-lock';
|
|
3
4
|
import { DialogWithAlert } from './with-alert';
|
|
4
5
|
|
|
5
6
|
export const DIALOG_ROUTES: Routes = {
|
|
6
7
|
dialog: {
|
|
7
8
|
basic: DialogBasic,
|
|
9
|
+
'scroll-lock': DialogScrollLock,
|
|
8
10
|
'with-alert': DialogWithAlert,
|
|
9
11
|
},
|
|
10
12
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { FC } from 'react';
|
|
2
|
+
import React, { useRef } from 'react';
|
|
3
|
+
|
|
4
|
+
import { KolButton, KolDialog, KolDrawer } from '@public-ui/react-v19';
|
|
5
|
+
import { SampleDescription } from '../SampleDescription';
|
|
6
|
+
|
|
7
|
+
export const DialogScrollLock: FC = () => {
|
|
8
|
+
const dialogRef = useRef<HTMLKolDialogElement>(null);
|
|
9
|
+
const drawerRef = useRef<HTMLKolDrawerElement>(null);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<SampleDescription>
|
|
14
|
+
<p>
|
|
15
|
+
Demonstrates that opening <strong>KolDialog</strong>/<strong>KolModal</strong> or <strong>KolDrawer</strong> modally locks the background scroll: the
|
|
16
|
+
page behind the overlay must not scroll, neither via mouse wheel/touch over the backdrop nor by dragging the page scrollbar. Non-modal{' '}
|
|
17
|
+
<code>show()</code> does not lock.
|
|
18
|
+
</p>
|
|
19
|
+
<p>
|
|
20
|
+
<strong>How to test:</strong> scroll this long page down a bit, open the dialog or drawer below, then try to scroll the background (mouse wheel over
|
|
21
|
+
the dimmed backdrop, or drag the scrollbar) – it should stay put. Close the overlay again and the page should scroll normally, restored to the same
|
|
22
|
+
position without a layout shift.
|
|
23
|
+
</p>
|
|
24
|
+
</SampleDescription>
|
|
25
|
+
|
|
26
|
+
<div className="flex flex-wrap gap-4 mb-4">
|
|
27
|
+
<KolButton _label="Open modal dialog" _on={{ onClick: () => dialogRef.current?.showModal() }} />
|
|
28
|
+
<KolButton _label="Open modal drawer" _on={{ onClick: () => drawerRef.current?.showModal() }} />
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<KolDialog ref={dialogRef} _label="Scroll lock dialog" _variant="card" _width="30%">
|
|
32
|
+
<p className="mt-0">While I am open, the background must not scroll.</p>
|
|
33
|
+
<KolButton _label="Close" _on={{ onClick: () => dialogRef.current?.closeModal() }} />
|
|
34
|
+
</KolDialog>
|
|
35
|
+
|
|
36
|
+
<KolDrawer ref={drawerRef} _label="Scroll lock drawer" _align="right">
|
|
37
|
+
<p className="mt-0">While I am open, the background must not scroll.</p>
|
|
38
|
+
<KolButton _label="Close" _on={{ onClick: () => drawerRef.current?.close() }} />
|
|
39
|
+
</KolDrawer>
|
|
40
|
+
|
|
41
|
+
<div style={{ height: '200vh', flexShrink: 0 }} className="border border-dashed border-gray p-4">
|
|
42
|
+
<p>This tall block simulates a long page. Scroll down, open an overlay above, and try to scroll here.</p>
|
|
43
|
+
</div>
|
|
44
|
+
</>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -242,11 +242,7 @@ export const HandoutBasic: FC = () => {
|
|
|
242
242
|
<KolCard className="col-span-6 sm:col-span-6 md:col-span-3 xl:col-span-2" _label="Abbreviation and Progress" _level={2}>
|
|
243
243
|
<div slot="" className="grid gap-2 p-2">
|
|
244
244
|
<p>
|
|
245
|
-
I am <KolAbbr
|
|
246
|
-
</p>
|
|
247
|
-
|
|
248
|
-
<p>
|
|
249
|
-
I am <KolAbbr>e.g.</KolAbbr> an abbreviation without label.
|
|
245
|
+
I am <KolAbbr>e.g.</KolAbbr> an abbreviation.
|
|
250
246
|
</p>
|
|
251
247
|
<KolProgress _variant="bar" _max={100} _value={33} _label="Progress" />
|
|
252
248
|
<KolProgress _variant="cycle" _max={100} _value={66} _label="Progress" />
|
|
@@ -455,7 +451,7 @@ export const HandoutBasic: FC = () => {
|
|
|
455
451
|
<KolInputRadio _orientation="horizontal" _options="[{'label':'Mr.','value':0},{'label':'Mrs.','value':1}]" _value="0" _label={`Salutation`} />
|
|
456
452
|
<KolInputCheckbox _label="">
|
|
457
453
|
<span slot="expert">
|
|
458
|
-
I accept the <KolAbbr
|
|
454
|
+
I accept the <KolAbbr>AGB</KolAbbr>.
|
|
459
455
|
</span>
|
|
460
456
|
</KolInputCheckbox>
|
|
461
457
|
</div>
|
|
@@ -1,28 +1,64 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
2
|
|
|
3
3
|
import { KolLinkButton } from '@public-ui/react-v19';
|
|
4
4
|
|
|
5
5
|
import type { FC } from 'react';
|
|
6
|
+
import { useSearchParams } from 'react-router-dom';
|
|
7
|
+
import { useToasterService } from '../../hooks/useToasterService';
|
|
8
|
+
import { fetchVariantData } from '../../shares/fetchVariantData';
|
|
9
|
+
import { getCustomThemes } from '../../shares/store';
|
|
6
10
|
import { SampleDescription } from '../SampleDescription';
|
|
7
11
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
export const LinkButtonBasic: FC = () => {
|
|
13
|
+
const [searchParams] = useSearchParams();
|
|
14
|
+
const theme = searchParams.get('theme') ?? getCustomThemes()?.[0]?.key;
|
|
15
|
+
const data = useMemo(() => (theme ? fetchVariantData(theme, 'buttonVariants') : []), [theme]);
|
|
16
|
+
|
|
17
|
+
const { dummyClickEventHandler } = useToasterService();
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
const dummyEventHandler = {
|
|
20
|
+
onClick: dummyClickEventHandler,
|
|
21
|
+
};
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<SampleDescription>
|
|
25
|
+
<p>KolLinkButton renders a link that looks like a button. The sample shows the different theme exclusive styling variants.</p>
|
|
26
|
+
</SampleDescription>
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
28
|
+
<div className="grid gap-8">
|
|
29
|
+
<section className="grid gap-4">
|
|
30
|
+
<div className="flex flex-wrap gap-4 items-center">
|
|
31
|
+
{!Array.isArray(data) || data.length === 0 ? (
|
|
32
|
+
<p>This theme has no variants for this component.</p>
|
|
33
|
+
) : (
|
|
34
|
+
data.map((element) => {
|
|
35
|
+
return (
|
|
36
|
+
<KolLinkButton _href="#/back-page" _icons="kolicon-house" _label={`${element}`} _variant={element} key={element} _on={dummyEventHandler} />
|
|
37
|
+
);
|
|
38
|
+
})
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
<div className="flex flex-wrap gap-4 items-center">
|
|
42
|
+
{!Array.isArray(data) || data.length === 0 ? (
|
|
43
|
+
<p>This theme has no variants for this component.</p>
|
|
44
|
+
) : (
|
|
45
|
+
data.map((element) => {
|
|
46
|
+
return (
|
|
47
|
+
<KolLinkButton
|
|
48
|
+
_href="#/back-page"
|
|
49
|
+
_hideLabel
|
|
50
|
+
_icons="kolicon-settings"
|
|
51
|
+
_label={`${element}`}
|
|
52
|
+
_variant={element}
|
|
53
|
+
key={element}
|
|
54
|
+
_on={dummyEventHandler}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
})
|
|
58
|
+
)}
|
|
59
|
+
</div>
|
|
60
|
+
</section>
|
|
61
|
+
</div>
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { KolSelect } from '@public-ui/react-v19';
|
|
2
|
+
import type { FC } from 'react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { InputEventValueDemo } from '../InputEventValueDemo';
|
|
5
|
+
import { SampleDescription } from '../SampleDescription';
|
|
6
|
+
|
|
7
|
+
export const SelectMultipleDropdown: FC = () => (
|
|
8
|
+
<>
|
|
9
|
+
<SampleDescription>
|
|
10
|
+
<p>Shows KolSelect with multiple=true and rows=1 - a select with multiselection in a dropdown.</p>
|
|
11
|
+
</SampleDescription>
|
|
12
|
+
|
|
13
|
+
<InputEventValueDemo
|
|
14
|
+
label="KolSelect"
|
|
15
|
+
renderInput={(handlers) => (
|
|
16
|
+
<KolSelect
|
|
17
|
+
_label="Select options"
|
|
18
|
+
_options={[
|
|
19
|
+
{ label: 'One', value: 'one' },
|
|
20
|
+
{ label: 'Two', value: 'two' },
|
|
21
|
+
{ label: 'Three', value: 'three' },
|
|
22
|
+
{ label: 'Four', value: 'four' },
|
|
23
|
+
{ label: 'Five', value: 'five' },
|
|
24
|
+
{ label: 'Six', value: 'six' },
|
|
25
|
+
{ label: 'Seven', value: 'seven' },
|
|
26
|
+
{ label: 'Eight', value: 'eight' },
|
|
27
|
+
{ label: 'Nine', value: 'nine' },
|
|
28
|
+
]}
|
|
29
|
+
_multiple={true}
|
|
30
|
+
_rows={1}
|
|
31
|
+
_on={handlers}
|
|
32
|
+
/>
|
|
33
|
+
)}
|
|
34
|
+
/>
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Routes } from '../../shares/types';
|
|
2
2
|
import { SelectBasic } from './basic';
|
|
3
3
|
import { SelectOnInputOnChange } from './get-value';
|
|
4
|
+
import { SelectMultipleDropdown } from './multiple-dropdown';
|
|
4
5
|
|
|
5
6
|
export const SELECT_ROUTES: Routes = {
|
|
6
7
|
select: {
|
|
7
8
|
basic: SelectBasic,
|
|
8
9
|
'get-value': SelectOnInputOnChange,
|
|
10
|
+
'multiple-dropdown': SelectMultipleDropdown,
|
|
9
11
|
},
|
|
10
12
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { KoliBriTableCell, KoliBriTableHeaderCell, VariantClassNamePropType } from '@public-ui/components';
|
|
2
2
|
import { createReactRenderElement, KolButton, KolButtonLink, KolLink, KolLinkButton, KolTableStateless } from '@public-ui/react-v19';
|
|
3
3
|
import type { ComponentProps, FC } from 'react';
|
|
4
4
|
import React from 'react';
|
|
@@ -16,7 +16,7 @@ function KolButtonWrapper(props: KolButtonProps) {
|
|
|
16
16
|
return <KolButton {...props} _on={dummyEventHandler} />;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const getButtonHeaderCell = (variant:
|
|
19
|
+
const getButtonHeaderCell = (variant: VariantClassNamePropType): KoliBriTableHeaderCell => {
|
|
20
20
|
const variantLabel = `${variant}`;
|
|
21
21
|
const capitalizedVariant = variantLabel.charAt(0).toUpperCase() + variantLabel.slice(1);
|
|
22
22
|
return {
|
|
@@ -53,8 +53,8 @@ export const TABLE_ROUTES: Routes = {
|
|
|
53
53
|
'stateful-export': TableStatefulExport,
|
|
54
54
|
'stateful-reset-sort': TableStatefulResetSort,
|
|
55
55
|
'stateful-settings-persistence': TableStatefulSettingsPersistence,
|
|
56
|
-
'stateful-with-selection': TableStatefulWithSelection,
|
|
57
56
|
'stateless-with-settings-menu': TableStatelessWithSettingsMenu,
|
|
57
|
+
'stateful-with-selection': TableStatefulWithSelection,
|
|
58
58
|
'stateful-with-single-selection': TableStatefulWithSingleSelection,
|
|
59
59
|
stateless: TableStateless,
|
|
60
60
|
'stateless-async': TableStatelessAsync,
|
|
@@ -1,111 +1,101 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { KoliBriTableDataType, KoliBriTableSelection } from '@public-ui/components';
|
|
2
|
+
import { KolTableStateful } from '@public-ui/react-v19';
|
|
3
3
|
import type { FC } from 'react';
|
|
4
|
-
import React
|
|
5
|
-
|
|
6
|
-
import { getRoot } from '../../shares/react-roots';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
7
6
|
import { SampleDescription } from '../SampleDescription';
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
{ id: '1001', name: 'Foo Bar', internalIdentifier: `AAA1001` },
|
|
11
|
-
{ id: '1002', name: 'Foo Baz', internalIdentifier: `AAA1002` },
|
|
12
|
-
{ id: '1003', name: 'This row is always unchecked', internalIdentifier: `AAA1003` },
|
|
13
|
-
{ id: '1004', name: 'This row is always checked', internalIdentifier: `AAA1004` },
|
|
14
|
-
];
|
|
15
|
-
type Data = (typeof DATA)[0];
|
|
16
|
-
type KolTableStatefulElement = {
|
|
17
|
-
addEventListener: (type: string, listener: (event: CustomEvent<Data[]>) => void) => void;
|
|
18
|
-
removeEventListener: (type: string, listener: (event: CustomEvent<Data[]>) => void) => void;
|
|
19
|
-
getSelection?: () => Promise<KoliBriTableDataType[] | null>;
|
|
20
|
-
};
|
|
8
|
+
import type { KoliBriTableHeaderCellWithLogic } from '@public-ui/components';
|
|
21
9
|
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
type ProjectTask = {
|
|
11
|
+
id: string;
|
|
12
|
+
project: string;
|
|
13
|
+
owner: string;
|
|
14
|
+
};
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
const HEADERS: { horizontal: KoliBriTableHeaderCellWithLogic[][] } = {
|
|
17
|
+
horizontal: [
|
|
18
|
+
[
|
|
19
|
+
{ key: 'id', label: 'ID', width: 80 },
|
|
20
|
+
{ key: 'project', label: 'Project', width: 200 },
|
|
21
|
+
{ key: 'owner', label: 'Owner', width: 200 },
|
|
22
|
+
{
|
|
23
|
+
type: 'action',
|
|
24
|
+
key: 'actions',
|
|
25
|
+
label: 'Actions',
|
|
26
|
+
width: 250,
|
|
27
|
+
actions: (row) => {
|
|
28
|
+
const simpleRow = row as ProjectTask;
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
type: 'button',
|
|
32
|
+
_label: 'Details',
|
|
33
|
+
_icons: 'kolicon-eye',
|
|
34
|
+
_hideLabel: true,
|
|
35
|
+
_on: {
|
|
36
|
+
onClick: () => alert(`Details: ${simpleRow.id} - ${simpleRow.project}`),
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
],
|
|
44
|
+
};
|
|
28
45
|
|
|
29
|
-
|
|
30
|
-
|
|
46
|
+
const DATA: ProjectTask[] = [
|
|
47
|
+
{
|
|
48
|
+
id: 'T-01',
|
|
49
|
+
project: 'Onboarding checklist',
|
|
50
|
+
owner: 'Alex Rivera',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'T-02',
|
|
54
|
+
project: 'Accessibility audit',
|
|
55
|
+
owner: 'Jamie Chen',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'T-03',
|
|
59
|
+
project: 'UX audit',
|
|
60
|
+
owner: 'Tyler Gray',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'T-04',
|
|
64
|
+
project: 'Software Architectur',
|
|
65
|
+
owner: 'Tess Richardson',
|
|
66
|
+
},
|
|
67
|
+
];
|
|
31
68
|
|
|
32
69
|
export const TableStatefulWithSelection: FC = () => {
|
|
33
|
-
const [selectedValue, setSelectedValue] = useState<Data[] | null>();
|
|
34
|
-
|
|
35
70
|
const selection: KoliBriTableSelection = {
|
|
36
|
-
label: (row) => `Selection for ${(row as
|
|
37
|
-
selectedKeys:
|
|
38
|
-
disabledKeys: ['
|
|
39
|
-
keyPropertyName: '
|
|
71
|
+
label: (row) => `Selection for ${(row as ProjectTask).id}`,
|
|
72
|
+
selectedKeys: ['T-01'],
|
|
73
|
+
disabledKeys: ['T-04'],
|
|
74
|
+
keyPropertyName: 'id',
|
|
40
75
|
};
|
|
41
76
|
|
|
42
|
-
const kolTableStatefulRef = useRef<HTMLKolTableStatefulElement>(null);
|
|
43
|
-
|
|
44
|
-
const handleSelectionChangeEvent = ({ detail: selection }: CustomEvent<Data[]>) => {
|
|
45
|
-
console.log('Selection change via event', selection);
|
|
46
|
-
};
|
|
47
77
|
const handleSelectionChangeCallback = (_event: Event, selection: KoliBriTableDataType[] | null) => {
|
|
48
78
|
console.log('Selection change via callback', selection);
|
|
49
79
|
};
|
|
50
80
|
|
|
51
|
-
const handleButtonClick = async () => {
|
|
52
|
-
const tableElement = kolTableStatefulRef.current as unknown as KolTableStatefulElement | null;
|
|
53
|
-
const selection = await tableElement?.getSelection?.();
|
|
54
|
-
setSelectedValue(selection as Data[] | null);
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
const tableElement = kolTableStatefulRef.current as unknown as KolTableStatefulElement | null;
|
|
59
|
-
const selectionChangeEvent = 'kolSelectionChange';
|
|
60
|
-
tableElement?.addEventListener(selectionChangeEvent, handleSelectionChangeEvent);
|
|
61
|
-
|
|
62
|
-
return () => {
|
|
63
|
-
tableElement?.removeEventListener(selectionChangeEvent, handleSelectionChangeEvent);
|
|
64
|
-
};
|
|
65
|
-
}, [kolTableStatefulRef]);
|
|
66
|
-
|
|
67
|
-
const renderButton = (element: HTMLElement, cell: KoliBriTableCell) => {
|
|
68
|
-
const data = (cell as { data?: Data }).data;
|
|
69
|
-
const id = data?.id;
|
|
70
|
-
getRoot(createReactRenderElement(element)).render(<KolButtonWrapper label={`Click ${id}`} />);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
81
|
return (
|
|
74
82
|
<>
|
|
75
83
|
<SampleDescription>
|
|
76
|
-
<p>
|
|
84
|
+
<p>
|
|
85
|
+
Simple example using the refactored action column: Actions are defined once in the column header definition using a factory function. Two rows with
|
|
86
|
+
inline action buttons demonstrate clean separation between data and UI behavior.
|
|
87
|
+
</p>
|
|
77
88
|
</SampleDescription>
|
|
78
89
|
|
|
79
90
|
<section className="w-full">
|
|
80
91
|
<KolTableStateful
|
|
81
|
-
_label="
|
|
82
|
-
_headers={
|
|
83
|
-
horizontal: [
|
|
84
|
-
[
|
|
85
|
-
{ key: 'id', label: '#ID', textAlign: 'left' },
|
|
86
|
-
{ key: 'name', label: 'Name', textAlign: 'left' },
|
|
87
|
-
{ key: 'action', label: 'Action', textAlign: 'left', render: renderButton },
|
|
88
|
-
],
|
|
89
|
-
],
|
|
90
|
-
}}
|
|
92
|
+
_label="Tasks with action buttons"
|
|
93
|
+
_headers={HEADERS}
|
|
91
94
|
_data={DATA}
|
|
95
|
+
className="block"
|
|
92
96
|
_selection={selection}
|
|
93
97
|
_on={{ onSelectionChange: handleSelectionChangeCallback }}
|
|
94
|
-
className="block"
|
|
95
|
-
style={{ maxWidth: '600px' }}
|
|
96
|
-
ref={kolTableStatefulRef}
|
|
97
98
|
/>
|
|
98
|
-
<div className="grid grid-cols-3 items-end gap-4 mt-4">
|
|
99
|
-
<KolButton
|
|
100
|
-
_label="getSelection()"
|
|
101
|
-
_on={{
|
|
102
|
-
onClick: () => {
|
|
103
|
-
void handleButtonClick();
|
|
104
|
-
},
|
|
105
|
-
}}
|
|
106
|
-
></KolButton>
|
|
107
|
-
<pre className="text-base">{JSON.stringify(selectedValue, null, 2)}</pre>
|
|
108
|
-
</div>
|
|
109
99
|
</section>
|
|
110
100
|
</>
|
|
111
101
|
);
|
|
@@ -1,110 +1,102 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { KoliBriTableDataType, KoliBriTableSelection } from '@public-ui/components';
|
|
2
|
+
import { KolTableStateful } from '@public-ui/react-v19';
|
|
3
3
|
import type { FC } from 'react';
|
|
4
|
-
import React
|
|
5
|
-
|
|
6
|
-
import { getRoot } from '../../shares/react-roots';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
7
6
|
import { SampleDescription } from '../SampleDescription';
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
{ id: '1001', name: 'Foo Bar', internalIdentifier: `AAA1001` },
|
|
11
|
-
{ id: '1002', name: 'Foo Baz', internalIdentifier: `AAA1002` },
|
|
12
|
-
];
|
|
8
|
+
import type { KoliBriTableHeaderCellWithLogic } from '@public-ui/components';
|
|
13
9
|
|
|
14
|
-
type
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
getSelection?: () => Promise<KoliBriTableDataType[] | null>;
|
|
10
|
+
type ProjectTask = {
|
|
11
|
+
id: string;
|
|
12
|
+
project: string;
|
|
13
|
+
owner: string;
|
|
19
14
|
};
|
|
20
15
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
const HEADERS: { horizontal: KoliBriTableHeaderCellWithLogic[][] } = {
|
|
17
|
+
horizontal: [
|
|
18
|
+
[
|
|
19
|
+
{ key: 'id', label: 'ID', width: 80 },
|
|
20
|
+
{ key: 'project', label: 'Project', width: 200 },
|
|
21
|
+
{ key: 'owner', label: 'Owner', width: 200 },
|
|
22
|
+
{
|
|
23
|
+
type: 'action',
|
|
24
|
+
key: 'actions',
|
|
25
|
+
label: 'Actions',
|
|
26
|
+
width: 250,
|
|
27
|
+
actions: (row) => {
|
|
28
|
+
const simpleRow = row as ProjectTask;
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
type: 'button',
|
|
32
|
+
_label: 'Start',
|
|
33
|
+
_icons: 'kolicon-chevron-right',
|
|
34
|
+
_variant: 'secondary',
|
|
35
|
+
_on: {
|
|
36
|
+
onClick: () => alert(`Start task ${simpleRow.id}`),
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
],
|
|
44
|
+
};
|
|
27
45
|
|
|
28
|
-
|
|
29
|
-
|
|
46
|
+
const DATA: ProjectTask[] = [
|
|
47
|
+
{
|
|
48
|
+
id: 'T-01',
|
|
49
|
+
project: 'Onboarding checklist',
|
|
50
|
+
owner: 'Alex Rivera',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'T-02',
|
|
54
|
+
project: 'Accessibility audit',
|
|
55
|
+
owner: 'Jamie Chen',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'T-03',
|
|
59
|
+
project: 'UX audit',
|
|
60
|
+
owner: 'Tyler Gray',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'T-04',
|
|
64
|
+
project: 'Software Architectur',
|
|
65
|
+
owner: 'Tess Richardson',
|
|
66
|
+
},
|
|
67
|
+
];
|
|
30
68
|
|
|
31
69
|
export const TableStatefulWithSingleSelection: FC = () => {
|
|
32
|
-
const [selectedValue, setSelectedValue] = useState<Data | null>();
|
|
33
|
-
|
|
34
70
|
const selection: KoliBriTableSelection = {
|
|
35
|
-
label: (row) => `Selection for ${(row as
|
|
71
|
+
label: (row) => `Selection for ${(row as ProjectTask).id}`,
|
|
72
|
+
selectedKeys: ['T-01'],
|
|
73
|
+
disabledKeys: ['T-04'],
|
|
74
|
+
keyPropertyName: 'id',
|
|
36
75
|
multiple: false,
|
|
37
|
-
selectedKeys: selectedValue ? [selectedValue.internalIdentifier] : [],
|
|
38
|
-
keyPropertyName: 'internalIdentifier',
|
|
39
76
|
};
|
|
40
77
|
|
|
41
|
-
const kolTableStatefulRef = useRef<HTMLKolTableStatefulElement>(null);
|
|
42
|
-
|
|
43
|
-
const handleSelectionChangeEvent = ({ detail: selection }: CustomEvent<Data[]>) => {
|
|
44
|
-
console.log('Selection change via event', selection);
|
|
45
|
-
};
|
|
46
78
|
const handleSelectionChangeCallback = (_event: Event, selection: KoliBriTableDataType[] | null) => {
|
|
47
79
|
console.log('Selection change via callback', selection);
|
|
48
80
|
};
|
|
49
81
|
|
|
50
|
-
const handleButtonClick = async () => {
|
|
51
|
-
const tableElement = kolTableStatefulRef.current as unknown as KolTableStatefulElement | null;
|
|
52
|
-
const selection = await tableElement?.getSelection?.();
|
|
53
|
-
setSelectedValue(selection as Data | null);
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
const tableElement = kolTableStatefulRef.current as unknown as KolTableStatefulElement | null;
|
|
58
|
-
const selectionChangeEvent = 'kolSelectionChange';
|
|
59
|
-
tableElement?.addEventListener(selectionChangeEvent, handleSelectionChangeEvent);
|
|
60
|
-
|
|
61
|
-
return () => {
|
|
62
|
-
tableElement?.removeEventListener(selectionChangeEvent, handleSelectionChangeEvent);
|
|
63
|
-
};
|
|
64
|
-
}, [kolTableStatefulRef]);
|
|
65
|
-
|
|
66
|
-
const renderButton = (element: HTMLElement, cell: KoliBriTableCell) => {
|
|
67
|
-
const data = (cell as { data?: Data }).data;
|
|
68
|
-
const id = data?.id;
|
|
69
|
-
getRoot(createReactRenderElement(element)).render(<KolButtonWrapper label={`Click ${id}`} />);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
82
|
return (
|
|
73
83
|
<>
|
|
74
84
|
<SampleDescription>
|
|
75
|
-
<p>
|
|
85
|
+
<p>
|
|
86
|
+
Simple example using the refactored action column: Actions are defined once in the column header definition using a factory function. Two rows with
|
|
87
|
+
inline action buttons demonstrate clean separation between data and UI behavior.
|
|
88
|
+
</p>
|
|
76
89
|
</SampleDescription>
|
|
77
90
|
|
|
78
91
|
<section className="w-full">
|
|
79
92
|
<KolTableStateful
|
|
80
|
-
_label="
|
|
81
|
-
_headers={
|
|
82
|
-
horizontal: [
|
|
83
|
-
[
|
|
84
|
-
{ key: 'id', label: '#ID', textAlign: 'left' },
|
|
85
|
-
{ key: 'name', label: 'Name', textAlign: 'left' },
|
|
86
|
-
{ key: 'action', label: 'Action', textAlign: 'left', render: renderButton },
|
|
87
|
-
],
|
|
88
|
-
],
|
|
89
|
-
}}
|
|
93
|
+
_label="Tasks with action buttons"
|
|
94
|
+
_headers={HEADERS}
|
|
90
95
|
_data={DATA}
|
|
96
|
+
className="block"
|
|
91
97
|
_selection={selection}
|
|
92
98
|
_on={{ onSelectionChange: handleSelectionChangeCallback }}
|
|
93
|
-
className="block"
|
|
94
|
-
style={{ maxWidth: '600px' }}
|
|
95
|
-
ref={kolTableStatefulRef}
|
|
96
99
|
/>
|
|
97
|
-
<div className="grid grid-cols-3 items-end gap-4 mt-4">
|
|
98
|
-
<KolButton
|
|
99
|
-
_label="getSelection()"
|
|
100
|
-
_on={{
|
|
101
|
-
onClick: () => {
|
|
102
|
-
void handleButtonClick();
|
|
103
|
-
},
|
|
104
|
-
}}
|
|
105
|
-
></KolButton>
|
|
106
|
-
<pre className="text-base">{JSON.stringify(selectedValue, null, 2)}</pre>
|
|
107
|
-
</div>
|
|
108
100
|
</section>
|
|
109
101
|
</>
|
|
110
102
|
);
|
|
@@ -34,28 +34,28 @@ export const SameHeightOfAllInteractiveElements: FC = () => {
|
|
|
34
34
|
</p>
|
|
35
35
|
</SampleDescription>
|
|
36
36
|
<div className="w-full flex flex-wrap gap-4">
|
|
37
|
-
<KolButton _icons="
|
|
38
|
-
<KolButton _hideLabel _icons="
|
|
39
|
-
<KolLinkButton _href="#" _icons="
|
|
40
|
-
<KolLinkButton _hideLabel _href="#" _icons="
|
|
41
|
-
<KolCombobox _hideLabel _icons="
|
|
37
|
+
<KolButton _icons="kolicon-house" _label="Button" _on={dummyEventHandler} />
|
|
38
|
+
<KolButton _hideLabel _icons="kolicon-house" _label="Button" _on={dummyEventHandler} />
|
|
39
|
+
<KolLinkButton _href="#" _icons="kolicon-house" _label="Link-Button" _on={dummyEventHandler} />
|
|
40
|
+
<KolLinkButton _hideLabel _href="#" _icons="kolicon-house" _label="Link-Button" _on={dummyEventHandler} />
|
|
41
|
+
<KolCombobox _hideLabel _icons="kolicon-house" _label="Combobox" _suggestions={[]} />
|
|
42
42
|
<KolInputColor
|
|
43
43
|
_hideLabel
|
|
44
44
|
_icons={{
|
|
45
|
-
left: '
|
|
45
|
+
left: 'kolicon-house',
|
|
46
46
|
}}
|
|
47
47
|
_label="Input-Color"
|
|
48
48
|
/>
|
|
49
|
-
<KolInputFile _hideLabel _icons="
|
|
50
|
-
<KolInputDate _hideLabel _icons="
|
|
51
|
-
<KolInputEmail _hideLabel _icons="
|
|
52
|
-
<KolInputNumber _hideLabel _icons="
|
|
53
|
-
<KolInputPassword _hideLabel _icons="
|
|
54
|
-
<KolInputRange _hideLabel _icons="
|
|
55
|
-
<KolInputText _hideLabel _icons="
|
|
56
|
-
<KolSelect _hideLabel _icons="
|
|
57
|
-
<KolSingleSelect _hideLabel _icons="
|
|
58
|
-
<KolTextarea _hideLabel _icons="
|
|
49
|
+
<KolInputFile _hideLabel _icons="kolicon-house" _label="Input-File" />
|
|
50
|
+
<KolInputDate _hideLabel _icons="kolicon-house" _label="Input-Date" />
|
|
51
|
+
<KolInputEmail _hideLabel _icons="kolicon-house" _label="Input-Email" />
|
|
52
|
+
<KolInputNumber _hideLabel _icons="kolicon-house" _label="Input-Number" />
|
|
53
|
+
<KolInputPassword _hideLabel _icons="kolicon-house" _label="Input-Password" />
|
|
54
|
+
<KolInputRange _hideLabel _icons="kolicon-house" _label="Input-Range" />
|
|
55
|
+
<KolInputText _hideLabel _icons="kolicon-house" _label="Input-Text" />
|
|
56
|
+
<KolSelect _hideLabel _icons="kolicon-house" _label="Select" _options={[]} />
|
|
57
|
+
<KolSingleSelect _hideLabel _icons="kolicon-house" _label="Single Select" _options={[]} />
|
|
58
|
+
<KolTextarea _hideLabel _icons="kolicon-house" _label="Textarea" />
|
|
59
59
|
</div>
|
|
60
60
|
</>
|
|
61
61
|
);
|
package/tsconfig.json
CHANGED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { KolInputCheckbox } from '@public-ui/react-v19';
|
|
2
|
-
import type { FC } from 'react';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { SampleDescription } from '../SampleDescription';
|
|
5
|
-
|
|
6
|
-
export const InputCheckboxAriaDetails: FC = () => (
|
|
7
|
-
<>
|
|
8
|
-
<SampleDescription>
|
|
9
|
-
<p>
|
|
10
|
-
Demonstrates how to use <code>_ariaDetails</code> to reference an external element that provides detailed information about form inputs. The{' '}
|
|
11
|
-
<code>_ariaDetails</code> prop uses <code>ElementInternals.ariaDetailsElements</code> to cross the Shadow DOM boundary, making it accessible to screen
|
|
12
|
-
readers.
|
|
13
|
-
</p>
|
|
14
|
-
</SampleDescription>
|
|
15
|
-
|
|
16
|
-
<div className="grid gap-8">
|
|
17
|
-
{/* Checkbox with independent details */}
|
|
18
|
-
<div className="grid gap-4 p-4 border border-gray-300 rounded">
|
|
19
|
-
<h2 className="text-lg font-semibold">Checkbox with Aria Details</h2>
|
|
20
|
-
|
|
21
|
-
<KolInputCheckbox _label="Accept terms of service" _ariaDetails="checkbox-details" />
|
|
22
|
-
|
|
23
|
-
<div id="checkbox-details" className="p-4 bg-blue-50 border-l-4 border-blue-400 rounded" role="complementary">
|
|
24
|
-
<h3 className="font-semibold text-blue-900 mb-2">Terms and Conditions</h3>
|
|
25
|
-
<p className="text-sm text-blue-800">
|
|
26
|
-
By checking this box, you agree to our terms of service and privacy policy. Please read them carefully before proceeding.
|
|
27
|
-
</p>
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
{/* Multiple checkboxes with different details */}
|
|
32
|
-
<div className="grid gap-4 p-4 border border-gray-300 rounded">
|
|
33
|
-
<h2 className="text-lg font-semibold">Multiple Checkboxes with Individual Details</h2>
|
|
34
|
-
|
|
35
|
-
<KolInputCheckbox _label="Enable notifications" _ariaDetails="notification-details" />
|
|
36
|
-
|
|
37
|
-
<KolInputCheckbox _label="Share usage data" _ariaDetails="usage-details" />
|
|
38
|
-
|
|
39
|
-
<div id="notification-details" className="p-4 bg-green-50 border-l-4 border-green-400 rounded" role="complementary">
|
|
40
|
-
<h3 className="font-semibold text-green-900 mb-2">About Notifications</h3>
|
|
41
|
-
<p className="text-sm text-green-800">We will send you important updates and alerts about your account activity.</p>
|
|
42
|
-
</div>
|
|
43
|
-
|
|
44
|
-
<div id="usage-details" className="p-4 bg-orange-50 border-l-4 border-orange-400 rounded" role="complementary">
|
|
45
|
-
<h3 className="font-semibold text-orange-900 mb-2">About Usage Data</h3>
|
|
46
|
-
<p className="text-sm text-orange-800">
|
|
47
|
-
Sharing usage data helps us improve our service and provide better experiences. Your data is encrypted and never shared with third parties.
|
|
48
|
-
</p>
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
</>
|
|
53
|
-
);
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { KolSingleSelect } from '@public-ui/react-v19';
|
|
2
|
-
import type { FC } from 'react';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { SampleDescription } from '../SampleDescription';
|
|
5
|
-
|
|
6
|
-
export const SingleSelectAriaDetails: FC = () => (
|
|
7
|
-
<>
|
|
8
|
-
<SampleDescription>
|
|
9
|
-
<p>
|
|
10
|
-
Demonstrates how to use <code>_ariaDetails</code> to reference external elements that provide detailed information about select options. The{' '}
|
|
11
|
-
<code>_ariaDetails</code> prop uses <code>ElementInternals.ariaDetailsElements</code> to cross the Shadow DOM boundary, making it accessible to screen
|
|
12
|
-
readers.
|
|
13
|
-
</p>
|
|
14
|
-
</SampleDescription>
|
|
15
|
-
|
|
16
|
-
<div className="grid gap-8">
|
|
17
|
-
{/* Single Select with independent details */}
|
|
18
|
-
<div className="grid gap-4 p-4 border border-gray-300 rounded">
|
|
19
|
-
<h2 className="text-lg font-semibold">Single Select with Aria Details</h2>
|
|
20
|
-
|
|
21
|
-
<KolSingleSelect
|
|
22
|
-
_label="Select your country"
|
|
23
|
-
_ariaDetails="country-details"
|
|
24
|
-
_options={[
|
|
25
|
-
{ label: 'Germany', value: 'de' },
|
|
26
|
-
{ label: 'Austria', value: 'at' },
|
|
27
|
-
{ label: 'Switzerland', value: 'ch' },
|
|
28
|
-
{ label: 'Netherlands', value: 'nl' },
|
|
29
|
-
]}
|
|
30
|
-
/>
|
|
31
|
-
|
|
32
|
-
<div id="country-details" className="p-4 bg-blue-50 border-l-4 border-blue-400 rounded" role="complementary">
|
|
33
|
-
<h3 className="font-semibold text-blue-900 mb-2">Country Selection</h3>
|
|
34
|
-
<p className="text-sm text-blue-800">
|
|
35
|
-
Select your country of residence. This helps us provide localized content, shipping information, and applicable tax rates for your region.
|
|
36
|
-
</p>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
|
|
40
|
-
{/* Multiple Single Selects with different details */}
|
|
41
|
-
<div className="grid gap-4 p-4 border border-gray-300 rounded">
|
|
42
|
-
<h2 className="text-lg font-semibold">Multiple Selects with Individual Details</h2>
|
|
43
|
-
|
|
44
|
-
<KolSingleSelect
|
|
45
|
-
_label="Experience Level"
|
|
46
|
-
_ariaDetails="experience-details"
|
|
47
|
-
_options={[
|
|
48
|
-
{ label: 'Beginner', value: 'beginner' },
|
|
49
|
-
{ label: 'Intermediate', value: 'intermediate' },
|
|
50
|
-
{ label: 'Advanced', value: 'advanced' },
|
|
51
|
-
{ label: 'Expert', value: 'expert' },
|
|
52
|
-
]}
|
|
53
|
-
/>
|
|
54
|
-
|
|
55
|
-
<KolSingleSelect
|
|
56
|
-
_label="Support Plan"
|
|
57
|
-
_ariaDetails="support-details"
|
|
58
|
-
_options={[
|
|
59
|
-
{ label: 'Community (Free)', value: 'community' },
|
|
60
|
-
{ label: 'Standard', value: 'standard' },
|
|
61
|
-
{ label: 'Professional', value: 'professional' },
|
|
62
|
-
{ label: 'Enterprise', value: 'enterprise' },
|
|
63
|
-
]}
|
|
64
|
-
/>
|
|
65
|
-
|
|
66
|
-
<div id="experience-details" className="p-4 bg-green-50 border-l-4 border-green-400 rounded" role="complementary">
|
|
67
|
-
<h3 className="font-semibold text-green-900 mb-2">About Experience Level</h3>
|
|
68
|
-
<p className="text-sm text-green-800">
|
|
69
|
-
Your experience level helps us provide appropriate documentation, tutorials, and support. It also affects which features are recommended to you.
|
|
70
|
-
</p>
|
|
71
|
-
</div>
|
|
72
|
-
|
|
73
|
-
<div id="support-details" className="p-4 bg-orange-50 border-l-4 border-orange-400 rounded" role="complementary">
|
|
74
|
-
<h3 className="font-semibold text-orange-900 mb-2">Support Plans</h3>
|
|
75
|
-
<ul className="text-sm text-orange-800 space-y-1">
|
|
76
|
-
<li>
|
|
77
|
-
<strong>Community:</strong> Community forums only
|
|
78
|
-
</li>
|
|
79
|
-
<li>
|
|
80
|
-
<strong>Standard:</strong> Email support, 24-hour response
|
|
81
|
-
</li>
|
|
82
|
-
<li>
|
|
83
|
-
<strong>Professional:</strong> Priority email + phone, 4-hour response
|
|
84
|
-
</li>
|
|
85
|
-
<li>
|
|
86
|
-
<strong>Enterprise:</strong> Dedicated support, 1-hour response + SLA
|
|
87
|
-
</li>
|
|
88
|
-
</ul>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
</>
|
|
93
|
-
);
|