@public-ui/sample-react 4.3.0 → 4.3.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@public-ui/sample-react",
3
- "version": "4.3.0",
3
+ "version": "4.3.1-rc.0",
4
4
  "description": "This app contains samples for the KoliBri/Public UI",
5
5
  "type": "module",
6
6
  "license": "EUPL-1.2",
@@ -9,26 +9,26 @@
9
9
  "url": "https://github.com/public-ui/kolibri"
10
10
  },
11
11
  "dependencies": {
12
- "@hookform/resolvers": "5.5.7",
12
+ "@hookform/resolvers": "5.7.1",
13
13
  "@stencil/core": "4.38.3",
14
- "@types/node": "26.1.2",
14
+ "@types/node": "26.2.0",
15
15
  "@types/papaparse": "5.5.2",
16
16
  "papaparse": "5.5.4",
17
- "react-hook-form": "7.83.0",
17
+ "react-hook-form": "7.85.0",
18
18
  "react-number-format": "5.4.5",
19
19
  "tslib": "2.8.1",
20
20
  "typescript": "5.9.3",
21
21
  "world_countries_lists": "3.3.0",
22
22
  "zod": "4.4.3",
23
- "@public-ui/react-hook-form-adapter": "4.3.0",
24
- "@public-ui/react-v19": "4.3.0"
23
+ "@public-ui/react-hook-form-adapter": "4.3.1-rc.0",
24
+ "@public-ui/react-v19": "4.3.1-rc.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/react": "19.2.18",
28
28
  "@types/react-dom": "19.2.4",
29
29
  "adopted-style-sheets": "1.1.9-rc.25",
30
30
  "eslint": "9.39.5",
31
- "knip": "6.29.0",
31
+ "knip": "6.32.1",
32
32
  "prettier": "3.9.6",
33
33
  "prettier-plugin-organize-imports": "4.3.0",
34
34
  "stylelint": "17.14.1"
@@ -37,7 +37,7 @@
37
37
  "react": "19.2.8",
38
38
  "react-dom": "19.2.8",
39
39
  "react-router": "8.3.0",
40
- "@public-ui/components": "4.3.0"
40
+ "@public-ui/components": "4.3.1-rc.0"
41
41
  },
42
42
  "files": [
43
43
  "assets",
package/src/App.tsx CHANGED
@@ -40,15 +40,17 @@ export const App: FC<Props> = ({ customThemes }) => {
40
40
 
41
41
  useEffect(() => {
42
42
  let isActive = true;
43
- if (sampleAppDataService.isInitialized(themes)) {
44
- setIsSampleAppDataInitialized(true);
43
+ const isInit = sampleAppDataService.isInitialized(themes);
44
+ const updateState = (value: boolean) => {
45
+ if (isActive) setIsSampleAppDataInitialized(value);
46
+ };
47
+ if (isInit) {
48
+ setTimeout(() => updateState(true), 0);
45
49
  return;
46
50
  }
47
- setIsSampleAppDataInitialized(false);
51
+ updateState(false);
48
52
  void sampleAppDataService.initialize(themes).then(() => {
49
- if (isActive) {
50
- setIsSampleAppDataInitialized(true);
51
- }
53
+ updateState(true);
52
54
  });
53
55
  return () => {
54
56
  isActive = false;
@@ -122,8 +124,8 @@ export const App: FC<Props> = ({ customThemes }) => {
122
124
  return tree;
123
125
  };
124
126
 
125
- const ROUTE_LIST = useMemo(() => getRouteList(ROUTES), [customThemes]);
126
- const ROUTE_TREE = useMemo(() => getRouteTree(ROUTES), [customThemes]);
127
+ const ROUTE_LIST = useMemo(() => getRouteList(ROUTES), []);
128
+ const ROUTE_TREE = useMemo(() => getRouteTree(ROUTES), []);
127
129
 
128
130
  const componentList: Map<string, Option<string>> = new Map();
129
131
  ROUTE_LIST.forEach((route) => {
@@ -139,9 +141,11 @@ export const App: FC<Props> = ({ customThemes }) => {
139
141
  setTheme(theme); // set for `getTheme` usages within the application
140
142
  useSetCurrentLocation();
141
143
 
142
- document.title = `KoliBri-Handout - ${getThemeName(getTheme())} | v${PackageJson.version}`;
143
- document.body.setAttribute('class', theme);
144
- document.body.dataset.theme = theme;
144
+ useEffect(() => {
145
+ document.title = `KoliBri-Handout - ${getThemeName(getTheme())} | v${PackageJson.version}`;
146
+ document.body.setAttribute('class', theme);
147
+ document.body.dataset.theme = theme;
148
+ }, [theme]);
145
149
 
146
150
  const handleThemeChange = (theme: unknown) => {
147
151
  setSearchParams({ theme: theme as string });
@@ -1,6 +1,6 @@
1
1
  import { KolInputText, KolTree, KolTreeItem } from '@public-ui/react-v19';
2
2
  import * as React from 'react';
3
- import { useState } from 'react';
3
+ import { useMemo, useState } from 'react';
4
4
  import { useHref, useMatch, useResolvedPath } from 'react-router';
5
5
  import type { Route, Routes } from '../shares/types';
6
6
 
@@ -80,7 +80,7 @@ function TreeItem({ label, to, children, open }: any) {
80
80
 
81
81
  function Navigation({ routes }: NavigationProps): React.ReactNode {
82
82
  const [query, setQuery] = useState<string>('');
83
- let filteredRoutes = filterRoutes(routes, query);
83
+ const filteredRoutes = useMemo(() => filterRoutes(routes, query), [routes, query]);
84
84
 
85
85
  const buildSubTree = (parentName: string, children: Route) => {
86
86
  return Object.keys(children).map((childName) => {
@@ -106,7 +106,6 @@ function Navigation({ routes }: NavigationProps): React.ReactNode {
106
106
  onInput: (event: Event) => {
107
107
  const input = event.target as HTMLInputElement;
108
108
  setQuery(input.value);
109
- filteredRoutes = filterRoutes(routes, query);
110
109
  },
111
110
  }}
112
111
  />
@@ -17,13 +17,13 @@ export const SampleDescription: FC<PropsWithChildren> = (props) => {
17
17
  return paths[0] === 'scenarios'
18
18
  ? null // Scenarios are not a component and hence have no documentation.
19
19
  : `${PUBLIC_DOC_COMPONENT_URL}/${paths[0]}`;
20
- }, [location.hash]);
20
+ }, [paths]);
21
21
 
22
22
  const codeLink = useMemo(() => {
23
23
  return paths[0] === 'scenarios'
24
24
  ? null // Scenarios are not a component and hence have no documentation.
25
25
  : `${PUBLIC_CODE_COMPONENT_URL}/${paths[0]}/${paths[1]}.tsx`;
26
- }, [location.hash]);
26
+ }, [paths]);
27
27
 
28
28
  return (
29
29
  <>
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+
3
+ import { KolCard } from '@public-ui/react-v19';
4
+
5
+ import type { FC } from 'react';
6
+ import { SampleDescription } from '../SampleDescription';
7
+
8
+ export const CardLinked: FC = () => {
9
+ return (
10
+ <>
11
+ <SampleDescription>
12
+ <p>This sample shows KolCards with links. The headline is wrapped with an a-tag. The whole card is clickable.</p>
13
+ </SampleDescription>
14
+
15
+ <div className="w-full grid grid-cols-2 gap-4">
16
+ <KolCard _label="Card with title and content" _href="#/back-page">
17
+ <p>This card has a link.</p>
18
+ </KolCard>
19
+
20
+ <KolCard _label="Card with title and content" _href="#/back-page" _target="_blank">
21
+ <p>This card has a link and a target.</p>
22
+ </KolCard>
23
+ </div>
24
+ </>
25
+ );
26
+ };
@@ -1,10 +1,12 @@
1
1
  import type { Routes } from '../../shares/types';
2
2
  import { CardBasic } from './basic';
3
3
  import { CardHeadlines } from './headlines';
4
+ import { CardLinked } from './linked';
4
5
 
5
6
  export const CARD_ROUTES: Routes = {
6
7
  card: {
7
8
  basic: CardBasic,
8
9
  headlines: CardHeadlines,
10
+ linked: CardLinked,
9
11
  },
10
12
  };
@@ -28,7 +28,7 @@ export const DialogBasic: FC = () => {
28
28
  blankRef.current?.openModal();
29
29
  cardRef.current?.openModal();
30
30
  }
31
- }, []);
31
+ }, [showDialog]);
32
32
 
33
33
  return (
34
34
  <>
@@ -7,7 +7,7 @@ import { SampleDescription } from '../SampleDescription';
7
7
  export const InputTextBasic: FC = () => (
8
8
  <>
9
9
  <SampleDescription>
10
- <p>This story showcases the most important InputText variants: default, required, validation error, disabled, read-only, and with icons.</p>
10
+ <p>This story showcases the most important InputText variants: default, required, validation error, disabled, read-only and with icons.</p>
11
11
  </SampleDescription>
12
12
 
13
13
  <div className="grid gap-4">
@@ -32,7 +32,18 @@ export const InputTextBasic: FC = () => (
32
32
  <KolInputText _label="Name" _required _msg={{ _type: 'error', _description: 'Please enter your name' }} _touched />
33
33
  <KolInputText _label="Name" _required _hint="Enter your surname" />
34
34
  <KolInputText _label="Name" _value="Anderson-Clark" _disabled />
35
- <KolInputText _label="Name" _readOnly _value="Anderson-Clark" />
35
+ <KolInputText
36
+ _label="Name"
37
+ _required
38
+ _value="Anderson-Clark"
39
+ _infoPopover={{ _label: 'hint', _content: 'Ich bin ein Hinweis.', _icons: 'kolicon-alert-info' }}
40
+ />
41
+ <KolInputText
42
+ _label="Name"
43
+ _readOnly
44
+ _value="Anderson-Clark"
45
+ _infoPopover={{ _label: 'hint', _content: 'Ich bin ein Hinweis.', _icons: 'kolicon-alert-info' }}
46
+ />
36
47
  <KolInputText _label="Name" _icons="kolicon-house" _value="Anderson-Clark" />
37
48
  </div>
38
49
  </>
@@ -1,5 +1,5 @@
1
1
  import type { FC } from 'react';
2
- import React, { useEffect, useState } from 'react';
2
+ import React, { useEffect, useRef, useState } from 'react';
3
3
 
4
4
  import type { KoliBriTableHeaderCellWithLogic, KoliBriTableHeaders, KoliBriTableSelection } from '@public-ui/components';
5
5
  import { KolHeading, KolTableStateful } from '@public-ui/react-v19';
@@ -30,7 +30,7 @@ export const TableBig: FC = () => {
30
30
  const [searchParams, setSearchParams] = useSearchParams();
31
31
  const [rows, setRows] = useState<number>(50);
32
32
  const [fixedCols, setFixedCols] = useState<[number, number]>([0, 0]);
33
- var loaded = false;
33
+ const loaded = useRef(false);
34
34
 
35
35
  const [headers, setHeaders] = useState<KoliBriTableHeaderCellWithLogic[]>(defaultHeaders);
36
36
 
@@ -39,7 +39,7 @@ export const TableBig: FC = () => {
39
39
  };
40
40
 
41
41
  function defineTable() {
42
- if (loaded) {
42
+ if (loaded.current) {
43
43
  return;
44
44
  }
45
45
 
@@ -82,7 +82,7 @@ export const TableBig: FC = () => {
82
82
  });
83
83
  }
84
84
 
85
- loaded = true;
85
+ loaded.current = true;
86
86
  }
87
87
 
88
88
  useEffect(() => defineTable(), [searchParams]);
@@ -1,7 +1,7 @@
1
1
  import type { KoliBriTableCell, KoliBriTableSelection, KoliBriTableSelectionKeys } from '@public-ui/components';
2
2
  import { createReactRenderElement, KolButton, KolTableStateless } from '@public-ui/react-v19';
3
3
  import type { FC } from 'react';
4
- import React, { useEffect, useRef, useState } from 'react';
4
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
5
5
  import { useToasterService } from '../../hooks/useToasterService';
6
6
  import { getRoot } from '../../shares/react-roots';
7
7
  import { SampleDescription } from '../SampleDescription';
@@ -42,13 +42,13 @@ export const TableStatelessWithSingleSelection: FC = () => {
42
42
 
43
43
  const kolTableStatelessRef = useRef<HTMLKolTableStatelessElement>(null);
44
44
 
45
- const handleSelectionChangeEvent = ({ detail: selection }: CustomEvent<SelectionValue[]>) => {
45
+ const handleSelectionChangeEvent = useCallback(({ detail: selection }: CustomEvent<SelectionValue[]>) => {
46
46
  console.log('Selection change via event', selection);
47
- };
48
- const handleSelectionChangeCallback = (_event: Event, selection: SelectionValue[]) => {
47
+ }, []);
48
+ const handleSelectionChangeCallback = useCallback((_event: Event, selection: SelectionValue[]) => {
49
49
  console.log('Selection change via callback', selection);
50
50
  setSelectedKeys(selection);
51
- };
51
+ }, []);
52
52
 
53
53
  useEffect(() => {
54
54
  const tableElement = kolTableStatelessRef.current as unknown as KolTableStatelessElement | null;
@@ -58,13 +58,13 @@ export const TableStatelessWithSingleSelection: FC = () => {
58
58
  return () => {
59
59
  tableElement?.removeEventListener(selectionChangeEvent, handleSelectionChangeEvent);
60
60
  };
61
- }, [kolTableStatelessRef]);
61
+ }, [kolTableStatelessRef, handleSelectionChangeEvent]);
62
62
 
63
- const renderButton = (element: HTMLElement, cell: KoliBriTableCell) => {
63
+ const renderButton = useCallback((element: HTMLElement, cell: KoliBriTableCell) => {
64
64
  const data = (cell as { data?: Data }).data;
65
65
  const id = data?.id;
66
66
  getRoot(createReactRenderElement(element)).render(<KolButtonWrapper label={`Click ${id}`} />);
67
- };
67
+ }, []);
68
68
 
69
69
  return (
70
70
  <>
@@ -4,12 +4,10 @@ export function useMobile(): boolean {
4
4
  const mediaQuery = matchMedia('(max-width: 1023px)');
5
5
  const [matches, setMatches] = useState<boolean>(mediaQuery.matches);
6
6
 
7
- const handleChange = () => {
8
- setMatches(mediaQuery.matches);
9
- };
10
-
11
7
  useEffect(() => {
12
- handleChange(); // handle initial value
8
+ const handleChange = (e: MediaQueryListEvent) => {
9
+ setMatches(e.matches);
10
+ };
13
11
  mediaQuery.addEventListener('change', handleChange);
14
12
 
15
13
  return () => {
@@ -5,6 +5,7 @@ import {
5
5
  KolBadge,
6
6
  KolButton,
7
7
  KolButtonLink,
8
+ KolCard,
8
9
  KolCombobox,
9
10
  KolDetails,
10
11
  KolHeading,
@@ -52,6 +53,7 @@ const getFocusElements = () => {
52
53
  ));
53
54
  focusElements.set('button', (_, ref) => <KolButton _label="Button here" ref={ref} />);
54
55
  focusElements.set('buttonLink', (_, ref) => <KolButtonLink _label="ButtonLink here" ref={ref} />);
56
+ focusElements.set('card', (_, ref) => <KolCard className="w-full" _label="Card with link here" _href="#" ref={ref} />);
55
57
  focusElements.set('combobox', (_, ref) => <KolCombobox className="w-full" _label="KolCombobox here" _suggestions={[]} ref={ref} />);
56
58
  focusElements.set('details', (_, ref) => (
57
59
  <KolDetails className="w-full" _label="Details here" ref={ref}>
@@ -1,5 +1,5 @@
1
1
  import type { FC } from 'react';
2
- import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
2
+ import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
3
3
 
4
4
  import {
5
5
  KolButton,
@@ -44,10 +44,10 @@ const Scenario = (props: Props) => {
44
44
  const eventTarget = useContext(EventTargetContext);
45
45
  const eventLoggerActive = useContext(EventLoggerActiveContext);
46
46
 
47
- const handleButtonClick = async () => {
47
+ const handleButtonClick = useCallback(async () => {
48
48
  const value = await ref.current?.getValue();
49
49
  setDisplayValue(value);
50
- };
50
+ }, []);
51
51
 
52
52
  useEffect(() => {
53
53
  const handleRunAll = () => {
@@ -58,7 +58,7 @@ const Scenario = (props: Props) => {
58
58
  return () => {
59
59
  eventTarget?.removeEventListener('runAll', handleRunAll);
60
60
  };
61
- }, [eventTarget]);
61
+ }, [eventTarget, handleButtonClick]);
62
62
 
63
63
  const eventListeners = Object.fromEntries(
64
64
  ['onInput', 'onChange', 'onBlur', 'onClick', 'onFocus', 'onMouseDown'].map((eventName) => [
@@ -1,34 +1,25 @@
1
1
  import type { ToolbarItemsPropType } from '@public-ui/components';
2
2
  import { KolHeading, KolToolbar } from '@public-ui/react-v19';
3
3
  import type { FC } from 'react';
4
- import React, { useEffect, useMemo, useState } from 'react';
4
+ import React, { useMemo, useState } from 'react';
5
5
 
6
6
  export const ToolbarItemOrder: FC = () => {
7
7
  const [isSubmitting, setIsSubmitting] = useState(false);
8
8
  const [isSubmitting2, setIsSubmitting2] = useState(false);
9
9
 
10
- useEffect(() => {
11
- let timer: ReturnType<typeof setTimeout>;
12
- if (isSubmitting) {
13
- timer = setTimeout(() => {
14
- setIsSubmitting(false);
15
- }, 2000);
16
- }
17
- return () => clearTimeout(timer);
18
- }, [isSubmitting]);
19
-
20
- useEffect(() => {
21
- let timer: ReturnType<typeof setTimeout>;
22
- if (isSubmitting2) {
23
- timer = setTimeout(() => {
24
- setIsSubmitting2(false);
25
- }, 2000);
26
- }
27
- return () => clearTimeout(timer);
28
- }, [isSubmitting2]);
29
-
30
- const handleSubmit = () => setIsSubmitting(true);
31
- const handleSubmit2 = () => setIsSubmitting2(true);
10
+ const handleSubmit = () => {
11
+ setIsSubmitting(true);
12
+ setTimeout(() => {
13
+ setIsSubmitting(false);
14
+ }, 2000);
15
+ };
16
+
17
+ const handleSubmit2 = () => {
18
+ setIsSubmitting2(true);
19
+ setTimeout(() => {
20
+ setIsSubmitting2(false);
21
+ }, 2000);
22
+ };
32
23
 
33
24
  const toolbarItems = useMemo(() => {
34
25
  const items: ToolbarItemsPropType = Array.from({ length: 5 }, (_item, index) => ({