@douglasneuroinformatics/libui 4.1.1 → 4.2.1

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/dist/hooks.d.ts CHANGED
@@ -3,7 +3,7 @@ export { D as DEFAULT_THEME, a as SYS_DARK_MEDIA_QUERY, S as StorageName, b as T
3
3
  import { Promisable } from 'type-fest';
4
4
  import { RefObject, useEffect, Dispatch, SetStateAction } from 'react';
5
5
  import * as zustand from 'zustand';
6
- import { T as TranslationNamespace, L as Language, a as TranslateFunction } from './types-Dm7os_cB.js';
6
+ import { T as TranslationNamespace, L as Language, a as TranslateFunction } from './types-DHTtLrqP.js';
7
7
 
8
8
  declare function useChart(): {
9
9
  config: ChartConfig;
@@ -64,7 +64,7 @@ type NotificationsStore = {
64
64
  declare const useNotificationsStore: zustand.UseBoundStore<zustand.StoreApi<NotificationsStore>>;
65
65
 
66
66
  type Handler = (event: MouseEvent) => void;
67
- declare function useOnClickOutside<T extends HTMLElement = HTMLElement>(ref: RefObject<T>, handler: Handler, mouseEvent?: 'mousedown' | 'mouseup'): void;
67
+ declare function useOnClickOutside<T extends HTMLElement | null = HTMLElement>(ref: RefObject<T>, handler: Handler, mouseEvent?: 'mousedown' | 'mouseup'): void;
68
68
 
69
69
  /** Custom hook that uses local storage to persist state across page reloads */
70
70
  declare function useLocalStorage<T>(key: string, initialValue: (() => T) | T, options?: UseStorageOptions<T>): [T, Dispatch<SetStateAction<T>>];
package/dist/hooks.js CHANGED
@@ -19,8 +19,8 @@ import {
19
19
  useTheme,
20
20
  useTranslation,
21
21
  useWindowSize
22
- } from "./chunk-KI6BSSS6.js";
23
- import "./chunk-655XRTXX.js";
22
+ } from "./chunk-6C6CJESI.js";
23
+ import "./chunk-ARKHRGTL.js";
24
24
  import "./chunk-HCQE34RL.js";
25
25
  export {
26
26
  DEFAULT_THEME,
package/dist/i18n.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as zustand from 'zustand';
2
2
  import { SetOptional } from 'type-fest';
3
- import { L as Language, b as Translations, a as TranslateFunction } from './types-Dm7os_cB.js';
4
- export { E as ExtractTranslationKey, c as LanguageOptions, d as TranslationKey, T as TranslationNamespace, U as UserConfig } from './types-Dm7os_cB.js';
3
+ import { L as Language, b as Translations, a as TranslateFunction } from './types-DHTtLrqP.js';
4
+ export { E as ExtractTranslationKey, c as LanguageOptions, d as TranslationKey, T as TranslationNamespace, U as UserConfig } from './types-DHTtLrqP.js';
5
5
 
6
6
  type InitOptions = {
7
7
  defaultLanguage?: Language;
package/dist/i18n.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  i18n,
4
4
  translationStore
5
- } from "./chunk-655XRTXX.js";
5
+ } from "./chunk-ARKHRGTL.js";
6
6
  export {
7
7
  i18n,
8
8
  translationStore
@@ -181,6 +181,11 @@
181
181
 
182
182
  --radius: 0.5rem;
183
183
  }
184
+
185
+ button:not(:disabled),
186
+ [role='button']:not(:disabled) {
187
+ cursor: pointer;
188
+ }
184
189
  }
185
190
 
186
191
  @layer base {
@@ -132,11 +132,25 @@ var notifications = {
132
132
  }
133
133
  }
134
134
  };
135
+ var oneTimePasswordInput = {
136
+ invalidCodeFormat: {
137
+ en: "Invalid code format",
138
+ fr: "Format de code invalide"
139
+ }
140
+ };
135
141
  var pagination = {
142
+ firstPage: {
143
+ en: "<< First",
144
+ fr: "<< Première"
145
+ },
136
146
  info: {
137
147
  en: "Showing {{first}} to {{last}} of {{total}} results",
138
148
  fr: "Affichage de {{first}} à {{last}} sur {{total}} résultats"
139
149
  },
150
+ lastPage: {
151
+ en: "Last >>",
152
+ fr: "Dernière >>"
153
+ },
140
154
  next: {
141
155
  en: "Next",
142
156
  fr: "Suivant"
@@ -144,14 +158,6 @@ var pagination = {
144
158
  previous: {
145
159
  en: "Previous",
146
160
  fr: "Précédent"
147
- },
148
- firstPage: {
149
- en: "<< First",
150
- fr: "<< Première"
151
- },
152
- lastPage: {
153
- en: "Last >>",
154
- fr: "Dernière >>"
155
161
  }
156
162
  };
157
163
  var searchBar = {
@@ -165,6 +171,7 @@ var libuiTranslations = {
165
171
  form: form,
166
172
  months: months,
167
173
  notifications: notifications,
174
+ oneTimePasswordInput: oneTimePasswordInput,
168
175
  pagination: pagination,
169
176
  searchBar: searchBar
170
177
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@douglasneuroinformatics/libui",
3
3
  "type": "module",
4
- "version": "4.1.1",
4
+ "version": "4.2.1",
5
5
  "packageManager": "pnpm@10.7.1",
6
6
  "description": "Generic UI components for DNP projects, built using React and Tailwind CSS",
7
7
  "author": "Joshua Unrau",
@@ -53,7 +53,7 @@
53
53
  "scripts": {
54
54
  "build": "rm -rf dist && tsup --config tsup.config.mts",
55
55
  "format": "prettier --write src",
56
- "format:translations": "find src/translations -name '*.json' -exec pnpm exec sort-json {} \\;",
56
+ "format:translations": "find src/i18n/translations -name '*.json' -exec pnpm exec sort-json {} \\;",
57
57
  "lint": "tsc && eslint --fix src",
58
58
  "prepare": "husky",
59
59
  "storybook": "storybook dev --no-open -p 6006",
@@ -6,7 +6,7 @@ import { cn } from '@/utils';
6
6
 
7
7
  export const ErrorMessage: React.FC<{ className?: string; error?: null | string[] }> = ({ className, error }) => {
8
8
  return error ? (
9
- <div className="space-y-1.5">
9
+ <div className="flex flex-col gap-1.5">
10
10
  {error.map((message) => (
11
11
  <div className={cn('text-destructive flex w-full items-center text-sm font-medium', className)} key={message}>
12
12
  <CircleAlertIcon className="mr-1" style={{ strokeWidth: '2px' }} />
@@ -302,6 +302,9 @@ const ungroupedContent = {
302
302
  } as const;
303
303
 
304
304
  export default {
305
+ args: {
306
+ onBeforeSubmit: null
307
+ },
305
308
  component: Form,
306
309
  decorators: [
307
310
  (Story) => (
@@ -17,6 +17,7 @@ import { cn } from '@/utils';
17
17
 
18
18
  import { Button } from '../Button';
19
19
  import { Heading } from '../Heading';
20
+ import { Separator } from '../Separator';
20
21
  import { ErrorMessage } from './ErrorMessage';
21
22
  import { FieldsComponent } from './FieldsComponent';
22
23
  import { getInitialValues } from './utils';
@@ -38,7 +39,9 @@ type FormProps<TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<T
38
39
  fieldsFooter?: React.ReactNode;
39
40
  id?: string;
40
41
  initialValues?: PartialNullableFormDataType<NoInfer<TData>>;
41
- onBeforeSubmit?: (data: NoInfer<TData>) => Promisable<{ errorMessage: string; success: false } | { success: true }>;
42
+ onBeforeSubmit?:
43
+ | ((data: NoInfer<TData>) => Promisable<{ errorMessage: string; success: false } | { success: true }>)
44
+ | null;
42
45
  onError?: (error: z.ZodError<NoInfer<TData>>) => void;
43
46
  onSubmit: (data: NoInfer<TData>) => Promisable<void>;
44
47
  preventResetValuesOnReset?: boolean;
@@ -142,7 +145,7 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
142
145
  const isGrouped = Array.isArray(content);
143
146
 
144
147
  const revalidate = () => {
145
- const hasErrors = Object.keys(errors).length > 0;
148
+ const hasErrors = Object.keys(errors).length > 0 || rootErrors.length;
146
149
  if (hasErrors) {
147
150
  validationSchema
148
151
  .safeParseAsync(values)
@@ -156,7 +159,8 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
156
159
  };
157
160
 
158
161
  useEffect(() => {
159
- revalidate();
162
+ setErrors({});
163
+ setRootErrors([]);
160
164
  }, [resolvedLanguage]);
161
165
 
162
166
  const isSuspended = Boolean(suspendWhileSubmitting && isSubmitting);
@@ -164,7 +168,7 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
164
168
  return (
165
169
  <form
166
170
  autoComplete="off"
167
- className={twMerge('relative w-full', isGrouped ? 'space-y-8 divide-y' : 'space-y-6', className)}
171
+ className={twMerge('relative flex w-full flex-col', !isGrouped && 'gap-6', className)}
168
172
  id={id}
169
173
  onBlur={revalidateOnBlur ? revalidate : undefined}
170
174
  onSubmit={(event) => void handleSubmit(event)}
@@ -174,26 +178,29 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
174
178
  {isGrouped ? (
175
179
  content.map((fieldGroup, i) => {
176
180
  return (
177
- <div className="flex flex-col space-y-6 [&:not(:first-child)]:pt-8" key={i}>
178
- <div className="space-y-1">
179
- {fieldGroup.title && (
180
- <Heading className="text-base" variant="h4">
181
- {fieldGroup.title}
182
- </Heading>
183
- )}
184
- {fieldGroup.description && (
185
- <p className="text-muted-foreground text-sm leading-tight italic">{fieldGroup.description}</p>
186
- )}
181
+ <>
182
+ <div className="flex flex-col gap-6 [&:not(:first-child)]:pt-8" key={i}>
183
+ <div className="flex flex-col gap-1">
184
+ {fieldGroup.title && (
185
+ <Heading className="text-base" variant="h4">
186
+ {fieldGroup.title}
187
+ </Heading>
188
+ )}
189
+ {fieldGroup.description && (
190
+ <p className="text-muted-foreground text-sm leading-tight italic">{fieldGroup.description}</p>
191
+ )}
192
+ </div>
193
+ <FieldsComponent
194
+ errors={errors}
195
+ fields={fieldGroup.fields as FormFields<TData>}
196
+ readOnly={readOnly}
197
+ setErrors={setErrors}
198
+ setValues={setValues}
199
+ values={values}
200
+ />
187
201
  </div>
188
- <FieldsComponent
189
- errors={errors}
190
- fields={fieldGroup.fields as FormFields<TData>}
191
- readOnly={readOnly}
192
- setErrors={setErrors}
193
- setValues={setValues}
194
- values={values}
195
- />
196
- </div>
202
+ <Separator className="mt-8" />
203
+ </>
197
204
  );
198
205
  })
199
206
  ) : (
@@ -47,11 +47,11 @@ export const NumberRecordField = <T extends NumberRecordFieldValue = NumberRecor
47
47
  }
48
48
 
49
49
  return (
50
- <div className="space-y-4">
50
+ <div className="flex flex-col gap-4">
51
51
  <Heading className="font-medium" variant="h5">
52
52
  {label}
53
53
  </Heading>
54
- <div className="space-y-6">
54
+ <div className="flex flex-col gap-6">
55
55
  {Object.keys(items).map((name) => {
56
56
  const item = items[name]!;
57
57
  return (
@@ -59,13 +59,13 @@ export const RecordArrayField = memo(function RecordArrayField({
59
59
  };
60
60
 
61
61
  return (
62
- <div className="space-y-4">
62
+ <div className="flex flex-col gap-4">
63
63
  <Heading className="font-medium" variant="h5">
64
64
  {label}
65
65
  </Heading>
66
- <div className="space-y-6">
66
+ <div className="flex flex-col gap-6">
67
67
  {arrayValue.map((fields, i) => (
68
- <div className="space-y-4" key={i}>
68
+ <div className="flex flex-col gap-4" key={i}>
69
69
  <Label className="font-semibold italic">{label + ' ' + (i + 1)}</Label>
70
70
  {Object.keys(fields).map((name) => {
71
71
  const field = fieldset[name];
@@ -0,0 +1,19 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import { OneTimePasswordInput } from './OneTimePasswordInput';
5
+
6
+ type Props = React.ComponentPropsWithoutRef<typeof OneTimePasswordInput>;
7
+
8
+ const TEST_ID = 'OneTimePasswordInput';
9
+
10
+ const TestOneTimePasswordInput: React.FC<Partial<Props>> = (props) => {
11
+ return <OneTimePasswordInput data-testid={TEST_ID} {...(props as Props)} />;
12
+ };
13
+
14
+ describe('OneTimePasswordInput', () => {
15
+ it('should render', () => {
16
+ render(<TestOneTimePasswordInput />);
17
+ expect(screen.getByTestId(TEST_ID)).toBeInTheDocument();
18
+ });
19
+ });
@@ -0,0 +1,27 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+
3
+ import { NotificationHub } from '../NotificationHub';
4
+ import { OneTimePasswordInput } from './OneTimePasswordInput';
5
+
6
+ type Story = StoryObj<typeof OneTimePasswordInput>;
7
+
8
+ export default {
9
+ args: {
10
+ onComplete: (code) => {
11
+ alert(`Code: ${code}`);
12
+ }
13
+ },
14
+ component: OneTimePasswordInput,
15
+ decorators: [
16
+ (Story) => {
17
+ return (
18
+ <>
19
+ <NotificationHub />
20
+ <Story />
21
+ </>
22
+ );
23
+ }
24
+ ]
25
+ } as Meta<typeof OneTimePasswordInput>;
26
+
27
+ export const Default: Story = {};
@@ -0,0 +1,109 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import type { ChangeEvent, ClipboardEvent, KeyboardEvent } from 'react';
3
+
4
+ import type { Promisable } from 'type-fest';
5
+
6
+ import { useNotificationsStore, useTranslation } from '@/hooks';
7
+ import { cn } from '@/utils';
8
+
9
+ const CODE_LENGTH = 6;
10
+
11
+ const EMPTY_CODE = Object.freeze(Array<null>(CODE_LENGTH).fill(null));
12
+
13
+ type OneTimePasswordInputProps = {
14
+ [key: `data-${string}`]: unknown;
15
+ className?: string;
16
+ onComplete: (code: number) => Promisable<void>;
17
+ };
18
+
19
+ function getUpdatedDigits(digits: (null | number)[], index: number, value: null | number) {
20
+ const updatedDigits = [...digits];
21
+ updatedDigits[index] = value;
22
+ return updatedDigits;
23
+ }
24
+
25
+ export const OneTimePasswordInput = ({ className, onComplete, ...props }: OneTimePasswordInputProps) => {
26
+ const notifications = useNotificationsStore();
27
+ const { t } = useTranslation('libui');
28
+ const [digits, setDigits] = useState<(null | number)[]>([...EMPTY_CODE]);
29
+ const inputRefs = digits.map(() => useRef<HTMLInputElement>(null));
30
+
31
+ useEffect(() => {
32
+ const isComplete = digits.every((value) => Number.isInteger(value));
33
+ if (isComplete) {
34
+ void onComplete(parseInt(digits.join('')));
35
+ setDigits([...EMPTY_CODE]);
36
+ }
37
+ }, [digits]);
38
+
39
+ const focusNext = (index: number) => inputRefs[index + 1 === digits.length ? 0 : index + 1]?.current?.focus();
40
+
41
+ const focusPrev = (index: number) => inputRefs[index - 1 >= 0 ? index - 1 : digits.length - 1]?.current?.focus();
42
+
43
+ const handleChange = (e: ChangeEvent<HTMLInputElement>, index: number) => {
44
+ let value: null | number;
45
+ if (e.target.value === '') {
46
+ value = null;
47
+ } else if (Number.isInteger(parseInt(e.target.value))) {
48
+ value = parseInt(e.target.value);
49
+ } else {
50
+ return;
51
+ }
52
+ setDigits((prevDigits) => getUpdatedDigits(prevDigits, index, value));
53
+ focusNext(index);
54
+ };
55
+
56
+ const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>, index: number) => {
57
+ switch (e.key) {
58
+ case 'ArrowLeft':
59
+ focusPrev(index);
60
+ break;
61
+ case 'ArrowRight':
62
+ focusNext(index);
63
+ break;
64
+ case 'Backspace':
65
+ setDigits((prevDigits) => getUpdatedDigits(prevDigits, index - 1, null));
66
+ focusPrev(index);
67
+ }
68
+ };
69
+
70
+ const handlePaste = (e: ClipboardEvent<HTMLInputElement>) => {
71
+ e.preventDefault();
72
+ const pastedDigits = e.clipboardData
73
+ .getData('text/plain')
74
+ .split('')
75
+ .slice(0, CODE_LENGTH)
76
+ .map((value) => parseInt(value));
77
+ const isValid = pastedDigits.length === CODE_LENGTH && pastedDigits.every((value) => Number.isInteger(value));
78
+ if (isValid) {
79
+ setDigits(pastedDigits);
80
+ } else {
81
+ notifications.addNotification({
82
+ message: t('oneTimePasswordInput.invalidCodeFormat'),
83
+ type: 'warning'
84
+ });
85
+ }
86
+ };
87
+
88
+ return (
89
+ <div className={cn('flex gap-2', className)} {...props}>
90
+ {digits.map((_, index) => (
91
+ <input
92
+ className="w-1/6 rounded-md border border-slate-300 bg-transparent p-2 text-center shadow-xs hover:border-slate-300 focus:border-sky-800 focus:outline-hidden dark:border-slate-600 dark:hover:border-slate-400 dark:focus:border-sky-500"
93
+ key={index}
94
+ maxLength={1}
95
+ ref={inputRefs[index]}
96
+ type="text"
97
+ value={digits[index] ?? ''}
98
+ onChange={(e) => {
99
+ handleChange(e, index);
100
+ }}
101
+ onKeyDown={(e) => {
102
+ handleKeyDown(e, index);
103
+ }}
104
+ onPaste={handlePaste}
105
+ />
106
+ ))}
107
+ </div>
108
+ );
109
+ };
@@ -0,0 +1 @@
1
+ export * from './OneTimePasswordInput';
@@ -32,6 +32,7 @@ export * from './LineGraph';
32
32
  export * from './ListboxDropdown';
33
33
  export * from './MenuBar';
34
34
  export * from './NotificationHub';
35
+ export * from './OneTimePasswordInput';
35
36
  export * from './Pagination';
36
37
  export * from './Popover';
37
38
  export * from './Progress';
@@ -4,7 +4,7 @@ import { useEventListener } from '../useEventListener';
4
4
 
5
5
  type Handler = (event: MouseEvent) => void;
6
6
 
7
- export function useOnClickOutside<T extends HTMLElement = HTMLElement>(
7
+ export function useOnClickOutside<T extends HTMLElement | null = HTMLElement>(
8
8
  ref: RefObject<T>,
9
9
  handler: Handler,
10
10
  mouseEvent: 'mousedown' | 'mouseup' = 'mousedown'
@@ -131,11 +131,25 @@
131
131
  }
132
132
  }
133
133
  },
134
+ "oneTimePasswordInput": {
135
+ "invalidCodeFormat": {
136
+ "en": "Invalid code format",
137
+ "fr": "Format de code invalide"
138
+ }
139
+ },
134
140
  "pagination": {
141
+ "firstPage": {
142
+ "en": "<< First",
143
+ "fr": "<< Première"
144
+ },
135
145
  "info": {
136
146
  "en": "Showing {{first}} to {{last}} of {{total}} results",
137
147
  "fr": "Affichage de {{first}} à {{last}} sur {{total}} résultats"
138
148
  },
149
+ "lastPage": {
150
+ "en": "Last >>",
151
+ "fr": "Dernière >>"
152
+ },
139
153
  "next": {
140
154
  "en": "Next",
141
155
  "fr": "Suivant"
@@ -143,14 +157,6 @@
143
157
  "previous": {
144
158
  "en": "Previous",
145
159
  "fr": "Précédent"
146
- },
147
- "firstPage": {
148
- "en": "<< First",
149
- "fr": "<< Première"
150
- },
151
- "lastPage": {
152
- "en": "Last >>",
153
- "fr": "Dernière >>"
154
160
  }
155
161
  },
156
162
  "searchBar": {
@@ -181,6 +181,11 @@
181
181
 
182
182
  --radius: 0.5rem;
183
183
  }
184
+
185
+ button:not(:disabled),
186
+ [role='button']:not(:disabled) {
187
+ cursor: pointer;
188
+ }
184
189
  }
185
190
 
186
191
  @layer base {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/useChart/useChart.ts","../src/context/ChartContext.tsx","../src/hooks/useDownload/useDownload.ts","../src/hooks/useNotificationsStore/useNotificationsStore.ts","../src/hooks/useEventCallback/useEventCallback.ts","../src/hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts","../src/hooks/useEventListener/useEventListener.ts","../src/hooks/useInterval/useInterval.ts","../src/hooks/useMediaQuery/useMediaQuery.ts","../src/hooks/useOnClickOutside/useOnClickOutside.ts","../src/hooks/useStorage/useStorage.ts","../src/hooks/useStorage/useLocalStorage.ts","../src/hooks/useStorage/useSessionStorage.ts","../src/hooks/useTheme/useTheme.ts","../src/hooks/useTranslation/useTranslation.ts","../src/hooks/useWindowSize/useWindowSize.ts"],"sourcesContent":["import { useContext } from 'react';\n\nimport { ChartContext } from '@/context/ChartContext';\n\nexport function useChart() {\n const context = useContext(ChartContext);\n if (!context) {\n throw new Error('useChart must be used within a <ChartContainer />');\n }\n return context;\n}\n","import { createContext } from 'react';\n\nimport type { ChartConfig } from '@/components';\n\ntype ChartContextProps = {\n config: ChartConfig;\n};\n\nexport const ChartContext = createContext<ChartContextProps | null>(null);\n","import { useEffect, useState } from 'react';\n\nimport type { Promisable } from 'type-fest';\n\nimport { useNotificationsStore } from '../useNotificationsStore';\n\ntype DownloadTextOptions = {\n blobType: 'text/csv' | 'text/plain';\n};\n\ntype DownloadBlobOptions = {\n blobType: 'application/zip' | 'image/jpeg' | 'image/png' | 'image/webp';\n};\n\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface DownloadFunction {\n (filename: string, data: Blob, options: DownloadBlobOptions): Promise<void>;\n (filename: string, data: () => Promisable<Blob>, options: DownloadBlobOptions): Promise<void>;\n (filename: string, data: string, options?: DownloadTextOptions): Promise<void>;\n (filename: string, data: () => Promisable<string>, options?: DownloadTextOptions): Promise<void>;\n}\n\ntype Downloadable = {\n blobType: string;\n data: Blob | string;\n filename: string;\n id: string;\n};\n\n/**\n * Used to trigger downloads of arbitrary data to the client\n * @returns A function to invoke the download\n */\nexport function useDownload(): DownloadFunction {\n const notifications = useNotificationsStore();\n const [downloads, setDownloads] = useState<Downloadable[]>([]);\n\n useEffect(() => {\n if (downloads.length) {\n const { blobType, data, filename, id } = downloads.at(-1)!;\n const anchor = document.createElement('a');\n document.body.appendChild(anchor);\n const blob = new Blob([data], { type: blobType });\n const url = URL.createObjectURL(blob);\n anchor.href = url;\n anchor.download = filename;\n anchor.click();\n URL.revokeObjectURL(url);\n anchor.remove();\n setDownloads((prevDownloads) => prevDownloads.filter((item) => item.id !== id));\n }\n }, [downloads]);\n\n return async (filename, _data, options) => {\n try {\n const data = typeof _data === 'function' ? await _data() : _data;\n if (typeof data !== 'string' && !options?.blobType) {\n throw new Error(\"argument 'blobType' must be defined when download is called with a Blob object\");\n }\n setDownloads((prevDownloads) => [\n ...prevDownloads,\n { blobType: options?.blobType ?? 'text/plain', data, filename, id: crypto.randomUUID() }\n ]);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'An unknown error occurred';\n notifications.addNotification({\n message,\n title: 'Error',\n type: 'error'\n });\n }\n };\n}\n","import { create } from 'zustand';\n\nexport type NotificationInterface = {\n id: number;\n message?: string;\n title?: string;\n type: 'error' | 'info' | 'success' | 'warning';\n variant?: 'critical' | 'standard';\n};\n\nexport type NotificationsStore = {\n addNotification: (notification: Omit<NotificationInterface, 'id'>) => void;\n dismissNotification: (id: number) => void;\n notifications: NotificationInterface[];\n};\n\nexport const useNotificationsStore = create<NotificationsStore>((set) => ({\n addNotification: (notification) => {\n set((state) => ({\n notifications: [...state.notifications, { id: Date.now(), ...notification }]\n }));\n },\n dismissNotification: (id) => {\n set((state) => ({\n notifications: state.notifications.filter((notification) => notification.id !== id)\n }));\n },\n notifications: []\n}));\n","import { useCallback, useRef } from 'react';\n\nimport { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';\n\nexport function useEventCallback<Args extends unknown[], R>(fn: (...args: Args) => R) {\n const ref = useRef<typeof fn>(() => {\n throw new Error('Cannot call an event handler while rendering.');\n });\n\n useIsomorphicLayoutEffect(() => {\n ref.current = fn;\n }, [fn]);\n\n return useCallback((...args: Args) => ref.current(...args), [ref]);\n}\n","import { useEffect, useLayoutEffect } from 'react';\n\nimport { isBrowser } from '@/utils';\n\nexport const useIsomorphicLayoutEffect = isBrowser() ? useLayoutEffect : useEffect;\n","import { useEffect, useRef } from 'react';\nimport type { RefObject } from 'react';\n\nimport { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';\n\n// MediaQueryList Event based useEventListener interface\nfunction useEventListener<K extends keyof MediaQueryListEventMap>(\n eventName: K,\n handler: (event: MediaQueryListEventMap[K]) => void,\n element: RefObject<MediaQueryList>,\n options?: AddEventListenerOptions | boolean\n): void;\n\n// Window Event based useEventListener interface\nfunction useEventListener<K extends keyof WindowEventMap>(\n eventName: K,\n handler: (event: WindowEventMap[K]) => void,\n element?: undefined,\n options?: AddEventListenerOptions | boolean\n): void;\n\n// Element Event based useEventListener interface\nfunction useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement = HTMLDivElement>(\n eventName: K,\n handler: (event: HTMLElementEventMap[K]) => void,\n element: RefObject<T>,\n options?: AddEventListenerOptions | boolean\n): void;\n\n// Document Event based useEventListener interface\nfunction useEventListener<K extends keyof DocumentEventMap>(\n eventName: K,\n handler: (event: DocumentEventMap[K]) => void,\n element: RefObject<Document>,\n options?: AddEventListenerOptions | boolean\n): void;\n\nfunction useEventListener<\n KW extends keyof WindowEventMap,\n KH extends keyof HTMLElementEventMap,\n KM extends keyof MediaQueryListEventMap,\n T extends HTMLElement | MediaQueryList | void = void\n>(\n eventName: KH | KM | KW,\n handler: (event: Event | HTMLElementEventMap[KH] | MediaQueryListEventMap[KM] | WindowEventMap[KW]) => void,\n element?: RefObject<T>,\n options?: AddEventListenerOptions | boolean\n) {\n // Create a ref that stores handler\n const savedHandler = useRef(handler);\n\n useIsomorphicLayoutEffect(() => {\n savedHandler.current = handler;\n }, [handler]);\n\n useEffect(() => {\n // Define the listening target\n const targetElement: T | Window = element?.current ?? window;\n\n if (!(targetElement && targetElement.addEventListener)) return;\n\n // Create event listener that calls handler function stored in ref\n const listener: typeof handler = (event) => savedHandler.current(event);\n\n targetElement.addEventListener(eventName, listener, options);\n\n // Remove event listener on cleanup\n return () => {\n targetElement.removeEventListener(eventName, listener, options);\n };\n }, [eventName, element, options]);\n}\n\nexport { useEventListener };\n","import { useEffect, useRef } from 'react';\n\nimport { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';\n\nexport function useInterval(callback: () => void, delay: null | number) {\n const savedCallback = useRef(callback);\n\n // Remember the latest callback if it changes.\n useIsomorphicLayoutEffect(() => {\n savedCallback.current = callback;\n }, [callback]);\n\n // Set up the interval.\n useEffect(() => {\n // Don't schedule if no delay is specified.\n // Note: 0 is a valid value for delay.\n if (!delay && delay !== 0) {\n return;\n }\n\n const id = setInterval(() => savedCallback.current(), delay);\n\n return () => clearInterval(id);\n }, [delay]);\n}\n","import { useEffect, useState } from 'react';\n\nimport { isBrowser } from '@/utils';\n\n/**\n * Get the result of an arbitrary CSS media query\n *\n * @param query - the CSS media query\n * @returns a boolean indicating the result of the query\n * @example\n * // true if the viewport is at least 768px wide\n * const matches = useMediaQuery('(min-width: 768px)')\n */\nexport function useMediaQuery(query: string): boolean {\n const getMatches = (query: string): boolean => {\n // Prevents SSR issues\n if (isBrowser()) {\n return window.matchMedia(query).matches;\n }\n return false;\n };\n\n const [matches, setMatches] = useState<boolean>(getMatches(query));\n\n function handleChange() {\n setMatches(getMatches(query));\n }\n\n useEffect(() => {\n const matchMedia = window.matchMedia(query);\n\n // Triggered at the first client-side load and if query changes\n handleChange();\n\n matchMedia.addEventListener('change', handleChange);\n\n return () => {\n matchMedia.removeEventListener('change', handleChange);\n };\n }, [query]);\n\n return matches;\n}\n","import type { RefObject } from 'react';\n\nimport { useEventListener } from '../useEventListener';\n\ntype Handler = (event: MouseEvent) => void;\n\nexport function useOnClickOutside<T extends HTMLElement = HTMLElement>(\n ref: RefObject<T>,\n handler: Handler,\n mouseEvent: 'mousedown' | 'mouseup' = 'mousedown'\n): void {\n useEventListener(mouseEvent, (event) => {\n const el = ref.current;\n\n // Do nothing if clicking ref's element or descendent elements\n if (!el || el.contains(event.target as Node)) {\n return;\n }\n\n handler(event);\n });\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { Dispatch, SetStateAction } from 'react';\n\nimport { isBrowser } from '@/utils';\n\nimport { useEventCallback } from '../useEventCallback';\nimport { useEventListener } from '../useEventListener';\n\ntype StorageName = 'localStorage' | 'sessionStorage';\n\ntype StorageEventMap = {\n [K in StorageName]: CustomEvent;\n};\n\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions, @typescript-eslint/no-empty-object-type\n interface WindowEventMap extends StorageEventMap {}\n}\n\n/**\n * Represents the options for customizing the behavior of serialization and deserialization.\n * @template T - The type of the state to be stored in storage.\n */\ntype UseStorageOptions<T> = {\n /** A function to deserialize the stored value. */\n deserializer?: (value: string) => T;\n /**\n * If `true` (default), the hook will initialize reading the storage. In SSR, you should set it to `false`, returning the initial value initially.\n * @default true\n */\n initializeWithValue?: boolean;\n /** A function to serialize the value before storing it. */\n serializer?: (value: T) => string;\n};\n\n/**\n * Custom hook that uses local or session storage to persist state across page reloads.\n * @template T - The type of the state to be stored in storage.\n * @param key - The key under which the value will be stored in storage.\n * @param initialValue - The initial value of the state or a function that returns the initial value.\n * @param options - Options for customizing the behavior of serialization and deserialization (optional).\n * @returns A tuple containing the stored value and a function to set the value.\n * @public\n * @example\n * ```tsx\n * const [count, setCount] = useStorage('count', 0);\n * // Access the `count` value and the `setCount` function to update it.\n * ```\n */\nexport function useStorage<T>(\n key: string,\n initialValue: (() => T) | T,\n storageName: StorageName,\n options: UseStorageOptions<T> = {}\n): [T, Dispatch<SetStateAction<T>>] {\n const { initializeWithValue = true } = options;\n const storage = window[storageName];\n\n const serializer = useCallback<(value: T) => string>(\n (value) => {\n if (options.serializer) {\n return options.serializer(value);\n }\n return JSON.stringify(value);\n },\n [options]\n );\n\n const deserializer = useCallback<(value: string) => T>(\n (value) => {\n if (options.deserializer) {\n return options.deserializer(value);\n } else if (value === 'undefined') {\n return undefined as unknown as T;\n }\n const defaultValue = initialValue instanceof Function ? initialValue() : initialValue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch (err) {\n console.error(`Error parsing JSON: ${(err as Error).message}`);\n return defaultValue;\n }\n return parsed as T;\n },\n [options, initialValue]\n );\n\n const readValue = useCallback((): T => {\n const initialValueToUse = initialValue instanceof Function ? initialValue() : initialValue;\n if (!isBrowser()) {\n return initialValueToUse;\n }\n const raw = storage.getItem(key);\n return raw ? deserializer(raw) : initialValueToUse;\n }, [initialValue, key, deserializer]);\n\n const [storedValue, setStoredValue] = useState(() => {\n if (initializeWithValue) {\n return readValue();\n }\n return initialValue instanceof Function ? initialValue() : initialValue;\n });\n\n const setValue: Dispatch<SetStateAction<T>> = useEventCallback((value) => {\n if (!isBrowser()) {\n console.warn(`Tried setting storage key “${key}” even though environment is not a client`);\n }\n try {\n const newValue = value instanceof Function ? value(readValue()) : value;\n storage.setItem(key, serializer(newValue));\n setStoredValue(newValue);\n window.dispatchEvent(new StorageEvent(storageName, { key }));\n } catch (error) {\n console.warn(`Error setting storage key “${key}”:`, error);\n }\n });\n\n useEffect(() => {\n setStoredValue(readValue());\n }, [key]);\n\n const handleStorageChange = useCallback(\n (event: CustomEvent | StorageEvent) => {\n if ((event as StorageEvent).key && (event as StorageEvent).key !== key) {\n return;\n }\n setStoredValue(readValue());\n },\n [key, readValue]\n );\n\n // this only works for other documents, not the current one\n useEventListener('storage', handleStorageChange);\n\n useEventListener(storageName, handleStorageChange);\n\n return [storedValue, setValue];\n}\n\nexport type { StorageName, UseStorageOptions };\n","import type { Dispatch, SetStateAction } from 'react';\n\nimport { useStorage } from './useStorage';\n\nimport type { UseStorageOptions } from './useStorage';\n\n/** Custom hook that uses local storage to persist state across page reloads */\nexport function useLocalStorage<T>(\n key: string,\n initialValue: (() => T) | T,\n options: UseStorageOptions<T> = {}\n): [T, Dispatch<SetStateAction<T>>] {\n return useStorage(key, initialValue, 'localStorage', options);\n}\n","import type { Dispatch, SetStateAction } from 'react';\n\nimport { useStorage } from './useStorage';\n\nimport type { UseStorageOptions } from './useStorage';\n\n/** Custom hook that uses session storage to persist state across page reloads */\nexport function useSessionStorage<T>(\n key: string,\n initialValue: (() => T) | T,\n options: UseStorageOptions<T> = {}\n): [T, Dispatch<SetStateAction<T>>] {\n return useStorage(key, initialValue, 'sessionStorage', options);\n}\n","import { useEffect, useState } from 'react';\n\n// this is required since our storybook manager plugin cannot use vite aliases\nimport { isBrowser } from '../../utils';\n\ntype Theme = 'dark' | 'light';\n\ntype UpdateTheme = (theme: Theme) => void;\n\n/** @private */\nconst DEFAULT_THEME: Theme = 'light';\n\n/** @private */\nconst THEME_ATTRIBUTE = 'data-mode';\n\n/** @private */\nconst THEME_KEY = 'theme';\n\n/** @private */\nconst SYS_DARK_MEDIA_QUERY = '(prefers-color-scheme: dark)';\n\n/**\n * Returns the current theme and a function to update the current theme\n *\n * The reason the implementation of this hook is rather convoluted is for\n * cases where the theme is updated outside this hook\n */\nfunction useTheme(): readonly [Theme, UpdateTheme] {\n // Initial theme value is based on the value saved in local storage or the system theme\n const [theme, setTheme] = useState<Theme>(() => {\n if (!isBrowser()) {\n return DEFAULT_THEME;\n }\n const savedTheme = window.localStorage.getItem(THEME_KEY);\n let initialTheme: Theme;\n if (savedTheme === 'dark' || savedTheme === 'light') {\n initialTheme = savedTheme;\n } else {\n initialTheme = window.matchMedia(SYS_DARK_MEDIA_QUERY).matches ? 'dark' : 'light';\n }\n document.documentElement.setAttribute(THEME_ATTRIBUTE, initialTheme);\n return initialTheme;\n });\n\n useEffect(() => {\n const observer = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (mutation.attributeName === THEME_ATTRIBUTE) {\n const updatedTheme = (mutation.target as HTMLHtmlElement).getAttribute(THEME_ATTRIBUTE);\n if (updatedTheme === 'light' || updatedTheme === 'dark') {\n window.localStorage.setItem(THEME_KEY, updatedTheme);\n setTheme(updatedTheme);\n } else {\n console.error(`Unexpected value for 'data-mode' attribute: ${updatedTheme}`);\n }\n }\n });\n });\n observer.observe(document.documentElement, {\n attributes: true\n });\n return () => observer.disconnect();\n }, []);\n\n // When the user wants to change the theme\n const updateTheme = (theme: Theme) => {\n document.documentElement.setAttribute(THEME_ATTRIBUTE, theme);\n };\n\n return [theme, updateTheme] as const;\n}\n\nexport { DEFAULT_THEME, SYS_DARK_MEDIA_QUERY, type Theme, THEME_ATTRIBUTE, THEME_KEY, useTheme };\n","import { useCallback } from 'react';\n\nimport { useStore } from 'zustand';\n\nimport { translationStore } from '@/i18n';\nimport type { TranslateFunction, TranslationNamespace } from '@/i18n';\nimport { getTranslation } from '@/i18n/internal';\n\nexport function useTranslation<TNamespace extends TranslationNamespace | undefined = undefined>(\n namespace?: TNamespace\n) {\n const changeLanguage = useStore(translationStore, (store) => store.changeLanguage);\n const fallbackLanguage = useStore(translationStore, (store) => store.fallbackLanguage);\n const resolvedLanguage = useStore(translationStore, (store) => store.resolvedLanguage);\n const translations = useStore(translationStore, (store) => {\n if (namespace) {\n return store.translations[namespace];\n }\n return store.translations;\n });\n\n const t: TranslateFunction<TNamespace> = useCallback(\n (target, ...args) => {\n return getTranslation(target, { fallbackLanguage, resolvedLanguage, translations }, ...args);\n },\n [fallbackLanguage, resolvedLanguage, translations]\n );\n\n return { changeLanguage, resolvedLanguage, t };\n}\n","import { useState } from 'react';\n\nimport { useEventListener } from '../useEventListener';\nimport { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect';\n\nexport type WindowSize = {\n height: number;\n width: number;\n};\n\nexport function useWindowSize(): WindowSize {\n const [windowSize, setWindowSize] = useState<WindowSize>({\n height: 0,\n width: 0\n });\n\n const handleSize = () => {\n setWindowSize({\n height: window.innerHeight,\n width: window.innerWidth\n });\n };\n\n useEventListener('resize', handleSize);\n\n // Set size at the first client-side load\n useIsomorphicLayoutEffect(() => {\n handleSize();\n }, []);\n\n return windowSize;\n}\n"],"mappings":"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","names":["useEffect","useEffect","useEffect","useRef","useRef","useEffect","useEffect","useRef","useRef","useEffect","useEffect","useState","query","useState","useEffect","useCallback","useEffect","useState","useCallback","useState","useEffect","useEffect","useState","useState","useEffect","theme","useCallback","useCallback","useState","useState"]}