@douglasneuroinformatics/libui 4.0.2 → 4.1.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/dist/components.d.ts +12 -2
- package/dist/components.js +54 -33
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/i18n.d.ts +2 -2
- package/dist/tailwind/globals.css +0 -18
- package/dist/{types-CMuti1SJ.d.ts → types-Dm7os_cB.d.ts} +1 -1
- package/package.json +2 -2
- package/src/components/Button/Button.tsx +1 -1
- package/src/components/Dialog/DialogContent.tsx +1 -1
- package/src/components/Dialog/DialogOverlay.tsx +1 -1
- package/src/components/ErrorFallback/ErrorFallback.spec.tsx +14 -0
- package/src/components/ErrorFallback/ErrorFallback.tsx +6 -3
- package/src/components/Form/ErrorMessage.tsx +5 -3
- package/src/components/Form/Form.stories.tsx +24 -0
- package/src/components/Form/Form.test.tsx +65 -0
- package/src/components/Form/Form.tsx +23 -5
- package/src/components/Form/NumberField/NumberFieldInput.tsx +4 -2
- package/src/i18n/types.ts +1 -1
- package/src/tailwind/globals.css +0 -18
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-
|
|
6
|
+
import { T as TranslationNamespace, L as Language, a as TranslateFunction } from './types-Dm7os_cB.js';
|
|
7
7
|
|
|
8
8
|
declare function useChart(): {
|
|
9
9
|
config: ChartConfig;
|
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-
|
|
4
|
-
export { E as ExtractTranslationKey, c as LanguageOptions, d as TranslationKey, T as TranslationNamespace, U as UserConfig } from './types-
|
|
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';
|
|
5
5
|
|
|
6
6
|
type InitOptions = {
|
|
7
7
|
defaultLanguage?: Language;
|
|
@@ -118,24 +118,6 @@
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
/*
|
|
122
|
-
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
|
123
|
-
so we've added these compatibility styles to make sure everything still
|
|
124
|
-
looks the same as it did with Tailwind CSS v3.
|
|
125
|
-
|
|
126
|
-
If we ever want to remove these styles, we need to add an explicit border
|
|
127
|
-
color utility to any element that depends on these defaults.
|
|
128
|
-
*/
|
|
129
|
-
@layer base {
|
|
130
|
-
*,
|
|
131
|
-
::after,
|
|
132
|
-
::before,
|
|
133
|
-
::backdrop,
|
|
134
|
-
::file-selector-button {
|
|
135
|
-
border-color: var(--color-gray-200, currentColor);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
121
|
@layer base {
|
|
140
122
|
:root {
|
|
141
123
|
--background: var(--color-slate-100);
|
|
@@ -201,7 +201,7 @@ type TranslationKey<TNamespace> = TNamespace extends TranslationNamespace ? Extr
|
|
|
201
201
|
interface TranslateFunction<TNamespace = undefined> {
|
|
202
202
|
(key: TranslationKey<TNamespace>, ...args: Exclude<Primitive, symbol>[]): string;
|
|
203
203
|
(translations: {
|
|
204
|
-
[L in Language]
|
|
204
|
+
[L in Language]?: string;
|
|
205
205
|
}, ...args: Exclude<Primitive, symbol>[]): string;
|
|
206
206
|
}
|
|
207
207
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@douglasneuroinformatics/libui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.0
|
|
4
|
+
"version": "4.1.0",
|
|
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",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"zod": "^3.23.6"
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
|
-
"@douglasneuroinformatics/libjs": "^2.
|
|
71
|
+
"@douglasneuroinformatics/libjs": "^2.8.0",
|
|
72
72
|
"@douglasneuroinformatics/libui-form-types": "^0.11.0",
|
|
73
73
|
"@radix-ui/react-accordion": "^1.2.3",
|
|
74
74
|
"@radix-ui/react-alert-dialog": "^1.1.6",
|
|
@@ -14,7 +14,7 @@ export const BUTTON_ICON_SIZE = {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export const buttonVariants = cva(
|
|
17
|
-
'flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
|
|
17
|
+
'flex items-center justify-center whitespace-nowrap cursor-pointer rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
|
|
18
18
|
{
|
|
19
19
|
defaultVariants: {
|
|
20
20
|
size: 'md',
|
|
@@ -16,7 +16,7 @@ export const DialogContent = forwardRef<
|
|
|
16
16
|
<DialogOverlay />
|
|
17
17
|
<Content
|
|
18
18
|
className={cn(
|
|
19
|
-
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
|
|
19
|
+
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
|
|
20
20
|
className
|
|
21
21
|
)}
|
|
22
22
|
ref={ref}
|
|
@@ -11,7 +11,7 @@ export const DialogOverlay = forwardRef<
|
|
|
11
11
|
return (
|
|
12
12
|
<Overlay
|
|
13
13
|
className={cn(
|
|
14
|
-
'
|
|
14
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80 duration-200',
|
|
15
15
|
className
|
|
16
16
|
)}
|
|
17
17
|
ref={ref}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { ErrorFallback } from './ErrorFallback';
|
|
5
|
+
|
|
6
|
+
const TEST_ID = 'error-fallback';
|
|
7
|
+
|
|
8
|
+
describe('ErrorFallback', () => {
|
|
9
|
+
it('should render', () => {
|
|
10
|
+
const error = new Error('Something went wrong');
|
|
11
|
+
render(<ErrorFallback error={error} />);
|
|
12
|
+
expect(screen.getByTestId(TEST_ID)).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -12,10 +12,13 @@ export const ErrorFallback = ({ error }: ErrorFallbackProps) => {
|
|
|
12
12
|
}, [error]);
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
|
-
<div
|
|
16
|
-
|
|
15
|
+
<div
|
|
16
|
+
className="flex min-h-screen flex-col items-center justify-center gap-1 p-3 text-center"
|
|
17
|
+
data-testid="error-fallback"
|
|
18
|
+
>
|
|
19
|
+
<h1 className="text-muted-foreground text-sm font-semibold tracking-wide uppercase">Unexpected Error</h1>
|
|
17
20
|
<h3 className="text-3xl font-extrabold tracking-tight sm:text-4xl md:text-5xl">Something Went Wrong</h3>
|
|
18
|
-
<p className="mt-2 max-w-prose text-sm
|
|
21
|
+
<p className="text-muted-foreground mt-2 max-w-prose text-sm sm:text-base">
|
|
19
22
|
We apologize for the inconvenience. Please contact us for further assistance.
|
|
20
23
|
</p>
|
|
21
24
|
<div className="mt-6">
|
|
@@ -2,13 +2,15 @@ import * as React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { CircleAlertIcon } from 'lucide-react';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { cn } from '@/utils';
|
|
6
|
+
|
|
7
|
+
export const ErrorMessage: React.FC<{ className?: string; error?: null | string[] }> = ({ className, error }) => {
|
|
6
8
|
return error ? (
|
|
7
9
|
<div className="space-y-1.5">
|
|
8
10
|
{error.map((message) => (
|
|
9
|
-
<div className=
|
|
11
|
+
<div className={cn('text-destructive flex w-full items-center text-sm font-medium', className)} key={message}>
|
|
10
12
|
<CircleAlertIcon className="mr-1" style={{ strokeWidth: '2px' }} />
|
|
11
|
-
<span>{message}</span>
|
|
13
|
+
<span data-testid="error-message-text">{message}</span>
|
|
12
14
|
</div>
|
|
13
15
|
)) ?? null}
|
|
14
16
|
</div>
|
|
@@ -530,3 +530,27 @@ export const WithSuspend: StoryObj<typeof Form<z.ZodType<FormTypes.Data>, { dela
|
|
|
530
530
|
})
|
|
531
531
|
}
|
|
532
532
|
};
|
|
533
|
+
|
|
534
|
+
export const WithError: StoryObj<typeof Form> = {
|
|
535
|
+
args: {
|
|
536
|
+
content: {
|
|
537
|
+
name: {
|
|
538
|
+
kind: 'string',
|
|
539
|
+
label: 'Name',
|
|
540
|
+
variant: 'input'
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
beforeSubmit: (data) => {
|
|
544
|
+
if (data.name === 'Winston') {
|
|
545
|
+
return { success: true };
|
|
546
|
+
}
|
|
547
|
+
return { success: false, errorMessage: "Name must be 'Winston'" };
|
|
548
|
+
},
|
|
549
|
+
onSubmit: () => {
|
|
550
|
+
alert('Success!');
|
|
551
|
+
},
|
|
552
|
+
validationSchema: $SimpleExampleFormData.extend({
|
|
553
|
+
name: z.string().min(3)
|
|
554
|
+
})
|
|
555
|
+
}
|
|
556
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import { userEvent } from '@testing-library/user-event';
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import type { Mock } from 'vitest';
|
|
4
5
|
import { z } from 'zod';
|
|
5
6
|
|
|
6
7
|
import { Form } from './Form';
|
|
@@ -114,4 +115,68 @@ describe('Form', () => {
|
|
|
114
115
|
expect(onSubmit.mock.lastCall?.[0].b).toBeUndefined();
|
|
115
116
|
});
|
|
116
117
|
});
|
|
118
|
+
|
|
119
|
+
describe('custom beforeSubmit error', () => {
|
|
120
|
+
let beforeSubmit: Mock;
|
|
121
|
+
|
|
122
|
+
beforeEach(() => {
|
|
123
|
+
beforeSubmit = vi.fn();
|
|
124
|
+
render(
|
|
125
|
+
<Form
|
|
126
|
+
beforeSubmit={beforeSubmit}
|
|
127
|
+
content={{
|
|
128
|
+
value: {
|
|
129
|
+
kind: 'number',
|
|
130
|
+
label: 'Value',
|
|
131
|
+
variant: 'input'
|
|
132
|
+
}
|
|
133
|
+
}}
|
|
134
|
+
data-testid={testid}
|
|
135
|
+
validationSchema={z.object({
|
|
136
|
+
value: z.number({ message: 'Please enter a number' })
|
|
137
|
+
})}
|
|
138
|
+
onError={onError}
|
|
139
|
+
onSubmit={onSubmit}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
afterEach(() => {
|
|
145
|
+
vi.clearAllMocks();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should render', () => {
|
|
149
|
+
expect(screen.getByTestId(testid)).toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should not allow submitting the form with a zod error', async () => {
|
|
153
|
+
fireEvent.submit(screen.getByTestId(testid));
|
|
154
|
+
await waitFor(() =>
|
|
155
|
+
expect(screen.getAllByTestId('error-message-text').map((e) => e.innerHTML)).toMatchObject([
|
|
156
|
+
'Please enter a number'
|
|
157
|
+
])
|
|
158
|
+
);
|
|
159
|
+
expect(beforeSubmit).not.toHaveBeenCalled();
|
|
160
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should not allow submitting the form with the beforeSubmit error', async () => {
|
|
164
|
+
beforeSubmit.mockResolvedValueOnce({ errorMessage: 'Invalid!', success: false });
|
|
165
|
+
const field: HTMLInputElement = screen.getByLabelText('Value');
|
|
166
|
+
await userEvent.type(field, '-1');
|
|
167
|
+
fireEvent.submit(screen.getByTestId(testid));
|
|
168
|
+
await waitFor(() =>
|
|
169
|
+
expect(screen.getAllByTestId('error-message-text').map((e) => e.innerHTML)).toMatchObject(['Invalid!'])
|
|
170
|
+
);
|
|
171
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should allow submitting the form if beforeSubmit returns true', async () => {
|
|
175
|
+
beforeSubmit.mockResolvedValueOnce({ success: true });
|
|
176
|
+
const field: HTMLInputElement = screen.getByLabelText('Value');
|
|
177
|
+
await userEvent.type(field, '-1');
|
|
178
|
+
fireEvent.submit(screen.getByTestId(testid));
|
|
179
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
|
|
180
|
+
});
|
|
181
|
+
});
|
|
117
182
|
});
|
|
@@ -29,8 +29,13 @@ type FormProps<TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<T
|
|
|
29
29
|
left?: React.ReactNode;
|
|
30
30
|
right?: React.ReactNode;
|
|
31
31
|
};
|
|
32
|
+
beforeSubmit?: (data: NoInfer<TData>) => Promisable<{ errorMessage: string; success: false } | { success: true }>;
|
|
32
33
|
className?: string;
|
|
33
34
|
content: FormContent<TData>;
|
|
35
|
+
customStyles?: {
|
|
36
|
+
resetBtn?: string;
|
|
37
|
+
submitBtn?: string;
|
|
38
|
+
};
|
|
34
39
|
fieldsFooter?: React.ReactNode;
|
|
35
40
|
id?: string;
|
|
36
41
|
initialValues?: PartialNullableFormDataType<NoInfer<TData>>;
|
|
@@ -47,8 +52,10 @@ type FormProps<TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<T
|
|
|
47
52
|
|
|
48
53
|
const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TSchema> = z.TypeOf<TSchema>>({
|
|
49
54
|
additionalButtons,
|
|
55
|
+
beforeSubmit,
|
|
50
56
|
className,
|
|
51
57
|
content,
|
|
58
|
+
customStyles,
|
|
52
59
|
fieldsFooter,
|
|
53
60
|
id,
|
|
54
61
|
initialValues,
|
|
@@ -73,6 +80,7 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
73
80
|
|
|
74
81
|
const handleError = (error: z.ZodError<TData>) => {
|
|
75
82
|
const fieldErrors: FormErrors<TData> = {};
|
|
83
|
+
const rootErrors: string[] = [];
|
|
76
84
|
for (const issue of error.issues) {
|
|
77
85
|
if (issue.path.length > 0) {
|
|
78
86
|
const current = get(fieldErrors, issue.path) as string[] | undefined;
|
|
@@ -82,10 +90,11 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
82
90
|
set(fieldErrors, issue.path, [issue.message]);
|
|
83
91
|
}
|
|
84
92
|
} else {
|
|
85
|
-
|
|
93
|
+
rootErrors.push(issue.message);
|
|
86
94
|
}
|
|
87
95
|
}
|
|
88
96
|
setErrors(fieldErrors);
|
|
97
|
+
setRootErrors(rootErrors);
|
|
89
98
|
if (onError) {
|
|
90
99
|
onError(error);
|
|
91
100
|
}
|
|
@@ -107,6 +116,15 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
107
116
|
handleError(result.error);
|
|
108
117
|
return;
|
|
109
118
|
}
|
|
119
|
+
if (beforeSubmit) {
|
|
120
|
+
const beforeSubmitResult = await beforeSubmit(result.data);
|
|
121
|
+
if (!beforeSubmitResult.success) {
|
|
122
|
+
setErrors({});
|
|
123
|
+
setRootErrors([beforeSubmitResult.errorMessage]);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
110
128
|
try {
|
|
111
129
|
setIsSubmitting(true);
|
|
112
130
|
await Promise.all([
|
|
@@ -164,7 +182,7 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
164
182
|
</Heading>
|
|
165
183
|
)}
|
|
166
184
|
{fieldGroup.description && (
|
|
167
|
-
<p className="text-sm
|
|
185
|
+
<p className="text-muted-foreground text-sm leading-tight italic">{fieldGroup.description}</p>
|
|
168
186
|
)}
|
|
169
187
|
</div>
|
|
170
188
|
<FieldsComponent
|
|
@@ -188,13 +206,14 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
188
206
|
values={values}
|
|
189
207
|
/>
|
|
190
208
|
)}
|
|
209
|
+
{Boolean(rootErrors.length) && <ErrorMessage className="-mt-3" error={rootErrors} />}
|
|
191
210
|
{fieldsFooter}
|
|
192
211
|
<div className="flex w-full gap-3">
|
|
193
212
|
{additionalButtons?.left}
|
|
194
213
|
{/** Note - aria-label is used for testing in downstream packages */}
|
|
195
214
|
<Button
|
|
196
215
|
aria-label="Submit"
|
|
197
|
-
className=
|
|
216
|
+
className={cn('flex w-full items-center justify-center gap-2', customStyles?.submitBtn)}
|
|
198
217
|
disabled={readOnly || isSuspended}
|
|
199
218
|
type="submit"
|
|
200
219
|
variant="primary"
|
|
@@ -218,7 +237,7 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
218
237
|
{resetBtn && (
|
|
219
238
|
<Button
|
|
220
239
|
aria-label="Reset"
|
|
221
|
-
className=
|
|
240
|
+
className={cn('block w-full', customStyles?.resetBtn)}
|
|
222
241
|
disabled={readOnly}
|
|
223
242
|
type="button"
|
|
224
243
|
variant="secondary"
|
|
@@ -229,7 +248,6 @@ const Form = <TSchema extends z.ZodType<FormDataType>, TData extends z.TypeOf<TS
|
|
|
229
248
|
)}
|
|
230
249
|
{additionalButtons?.right}
|
|
231
250
|
</div>
|
|
232
|
-
{Boolean(rootErrors.length) && <ErrorMessage error={rootErrors} />}
|
|
233
251
|
</form>
|
|
234
252
|
);
|
|
235
253
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { useEffect, useId, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { parseNumber } from '@douglasneuroinformatics/libjs';
|
|
4
4
|
import type { NumberFormField } from '@douglasneuroinformatics/libui-form-types';
|
|
@@ -27,6 +27,7 @@ export const NumberFieldInput = ({
|
|
|
27
27
|
setValue,
|
|
28
28
|
value
|
|
29
29
|
}: NumberFieldInputProps) => {
|
|
30
|
+
const id = useId();
|
|
30
31
|
const [inputValue, setInputValue] = useState(value?.toString() ?? '');
|
|
31
32
|
const valueRef = useRef<number | undefined>(value);
|
|
32
33
|
|
|
@@ -65,11 +66,12 @@ export const NumberFieldInput = ({
|
|
|
65
66
|
return (
|
|
66
67
|
<FieldGroup name={name}>
|
|
67
68
|
<FieldGroup.Row>
|
|
68
|
-
<Label>{label}</Label>
|
|
69
|
+
<Label htmlFor={id}>{label}</Label>
|
|
69
70
|
<FieldGroup.Description description={description} />
|
|
70
71
|
</FieldGroup.Row>
|
|
71
72
|
<Input
|
|
72
73
|
disabled={disabled || readOnly}
|
|
74
|
+
id={id}
|
|
73
75
|
max={max}
|
|
74
76
|
min={min}
|
|
75
77
|
name={name}
|
package/src/i18n/types.ts
CHANGED
|
@@ -47,5 +47,5 @@ export type TranslationKey<TNamespace> = TNamespace extends TranslationNamespace
|
|
|
47
47
|
|
|
48
48
|
export interface TranslateFunction<TNamespace = undefined> {
|
|
49
49
|
(key: TranslationKey<TNamespace>, ...args: Exclude<Primitive, symbol>[]): string;
|
|
50
|
-
(translations: { [L in Language]
|
|
50
|
+
(translations: { [L in Language]?: string }, ...args: Exclude<Primitive, symbol>[]): string;
|
|
51
51
|
}
|
package/src/tailwind/globals.css
CHANGED
|
@@ -118,24 +118,6 @@
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
/*
|
|
122
|
-
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
|
123
|
-
so we've added these compatibility styles to make sure everything still
|
|
124
|
-
looks the same as it did with Tailwind CSS v3.
|
|
125
|
-
|
|
126
|
-
If we ever want to remove these styles, we need to add an explicit border
|
|
127
|
-
color utility to any element that depends on these defaults.
|
|
128
|
-
*/
|
|
129
|
-
@layer base {
|
|
130
|
-
*,
|
|
131
|
-
::after,
|
|
132
|
-
::before,
|
|
133
|
-
::backdrop,
|
|
134
|
-
::file-selector-button {
|
|
135
|
-
border-color: var(--color-gray-200, currentColor);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
121
|
@layer base {
|
|
140
122
|
:root {
|
|
141
123
|
--background: var(--color-slate-100);
|