@public-ui/sample-react 4.3.0-rc.5 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@public-ui/sample-react",
3
- "version": "4.3.0-rc.5",
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.4.0",
12
+ "@hookform/resolvers": "5.5.7",
13
13
  "@stencil/core": "4.38.3",
14
- "@types/node": "25.9.3",
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.22",
19
- "papaparse": "5.5.3",
20
- "react": "19.2.7",
21
- "react-dom": "19.2.7",
22
- "react-hook-form": "7.79.0",
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.17.0",
25
- "react-router-dom": "7.17.0",
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/components": "4.3.0-rc.5",
31
- "@public-ui/react-hook-form-adapter": "4.3.0-rc.5",
32
- "@public-ui/react-v19": "4.3.0-rc.5"
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.4",
36
- "knip": "6.16.1",
37
- "prettier": "3.8.4",
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.13.0"
39
+ "stylelint": "17.14.1"
40
40
  },
41
41
  "files": [
42
42
  "assets",
@@ -1,38 +1,87 @@
1
- import { KolAccordion, KolTree, KolTreeItem } from '@public-ui/react-v19';
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 ComponentNavContainer({ children }: { children?: React.ReactNode }): React.ReactNode {
12
- const isMobile = useMobile();
11
+ function isRoutes(x: Route): x is Routes {
12
+ return typeof x === 'object' && x !== null;
13
+ }
13
14
 
14
- return isMobile ? (
15
- <KolAccordion _label="All components" class="mt">
16
- {children}
17
- </KolAccordion>
18
- ) : (
19
- <div className="mt scrollable-container">{children}</div>
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 TreeItem({ label, to, children }: any) {
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(routes).map(([parentName, children]) => (
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
- <nav>
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
- <span className="text-base text-center">
97
- {formatSampleAsLabel()} ({getIndexOfSample() + 1}/{routeList.length})
98
- </span>
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
- <Navigation routes={routes} />
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
  );
@@ -1,8 +1,10 @@
1
1
  import type { Routes } from '../../shares/types';
2
2
  import { AvatarBasic } from './basic';
3
+ import { AvatarSize } from './size';
3
4
 
4
5
  export const AVATAR_ROUTES: Routes = {
5
6
  avatar: {
6
7
  basic: AvatarBasic,
8
+ size: AvatarSize,
7
9
  },
8
10
  };
@@ -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&apos; 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,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
  };
package/tsconfig.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "preserveConstEnums": true,
8
8
  "sourceMap": true,
9
9
  "preserveSymlinks": true,
10
- "moduleResolution": "node",
10
+ "moduleResolution": "bundler",
11
11
  "lib": ["es2017", "dom"],
12
12
  "typeRoots": ["node_modules/@types"],
13
13
  "noUnusedLocals": true,
@@ -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
- );