@obosbbl/grunnmuren-react 2.0.0-canary.4 → 2.0.0-canary.40
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/README.md +122 -10
- package/dist/index.d.mts +640 -29
- package/dist/index.mjs +786 -192
- package/package.json +5 -3
- package/dist/useClientLayoutEffect-client-2_5nawgR.js +0 -9
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,209 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
import { I18nProvider, RouterProvider, useLocale, useContextProps, Provider, Link, Button as Button$1, Text, CheckboxContext, Checkbox as Checkbox$1, Label as Label$1, FieldError, CheckboxGroup as CheckboxGroup$1, ListBoxItem as ListBoxItem$1, ListBox as ListBox$1, ComboBox, Group, Input, Popover, RadioGroup as RadioGroup$1, Radio as Radio$1, Select as Select$1, SelectValue, TextField as TextField$1, TextArea as TextArea$1, NumberField as NumberField$1, Breadcrumbs as Breadcrumbs$1, Breadcrumb as Breadcrumb$1 } from 'react-aria-components';
|
|
3
|
+
export { Form } from 'react-aria-components';
|
|
3
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { LoadingSpinner, Check,
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { createContext, forwardRef, Children, useId, useState } from 'react';
|
|
6
|
+
import { cx, cva, compose } from 'cva';
|
|
7
|
+
import { ChevronDown, LoadingSpinner, Check, Close, InfoCircle, CheckCircle, Warning, CloseCircle, ChevronRight, ChevronLeft } from '@obosbbl/grunnmuren-icons-react';
|
|
8
|
+
import { useLayoutEffect } from '@react-aria/utils';
|
|
9
|
+
import { useProgressBar, useDateFormatter } from 'react-aria';
|
|
10
|
+
|
|
11
|
+
function GrunnmurenProvider({ children, locale = 'nb', navigate, useHref }) {
|
|
12
|
+
return /*#__PURE__*/ jsx(I18nProvider, {
|
|
13
|
+
locale: locale,
|
|
14
|
+
children: navigate ? /*#__PURE__*/ jsx(RouterProvider, {
|
|
15
|
+
navigate: navigate,
|
|
16
|
+
useHref: useHref,
|
|
17
|
+
children: children
|
|
18
|
+
}) : children
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Returns the locale set in `<GrunnmurenProvider />`
|
|
24
|
+
*/ function _useLocale() {
|
|
25
|
+
// a small wrapper around react-arias useLocale with a simpler return type with only the locales that we actually support
|
|
26
|
+
const locale = useLocale();
|
|
27
|
+
return locale.locale;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const HeadingContext = /*#__PURE__*/ createContext({});
|
|
31
|
+
const Heading = (props, ref)=>{
|
|
32
|
+
[props, ref] = useContextProps(props, ref, HeadingContext);
|
|
33
|
+
const { children, level, className, _innerWrapper: innerWrapper, ...restProps } = props;
|
|
34
|
+
const Element = `h${level}`;
|
|
35
|
+
return /*#__PURE__*/ jsx(Element, {
|
|
36
|
+
...restProps,
|
|
37
|
+
className: className,
|
|
38
|
+
"data-slot": "heading",
|
|
39
|
+
children: innerWrapper ? innerWrapper(children) : children
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
const ContentContext = /*#__PURE__*/ createContext({});
|
|
43
|
+
const Content = (props, ref)=>{
|
|
44
|
+
[props, ref] = useContextProps(props, ref, ContentContext);
|
|
45
|
+
const { _outerWrapper: outerWrapper, ...restProps } = props;
|
|
46
|
+
const content = /*#__PURE__*/ jsx("div", {
|
|
47
|
+
...restProps,
|
|
48
|
+
"data-slot": "content"
|
|
49
|
+
});
|
|
50
|
+
return outerWrapper ? outerWrapper(content) : content;
|
|
51
|
+
};
|
|
52
|
+
const Media = (props)=>/*#__PURE__*/ jsx("div", {
|
|
53
|
+
...props,
|
|
54
|
+
"data-slot": "media"
|
|
55
|
+
});
|
|
56
|
+
const Footer = (props)=>/*#__PURE__*/ jsx("div", {
|
|
57
|
+
...props,
|
|
58
|
+
"data-slot": "footer"
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
function Accordion(props, ref) {
|
|
62
|
+
const { children, className, ...restProps } = props;
|
|
63
|
+
const childCount = Children.count(children);
|
|
64
|
+
return /*#__PURE__*/ jsx("div", {
|
|
65
|
+
...restProps,
|
|
66
|
+
ref: ref,
|
|
67
|
+
className: cx('rounded-lg bg-white', className),
|
|
68
|
+
children: Children.map(children, (child, index)=>/*#__PURE__*/ jsxs(Fragment, {
|
|
69
|
+
children: [
|
|
70
|
+
child,
|
|
71
|
+
index < childCount - 1 && // Margin is added to enable support for containers with a background color
|
|
72
|
+
/*#__PURE__*/ jsx("hr", {
|
|
73
|
+
className: "mx-2 border-gray-light",
|
|
74
|
+
"aria-hidden": true
|
|
75
|
+
})
|
|
76
|
+
]
|
|
77
|
+
}))
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function AccordionItem(props, ref) {
|
|
81
|
+
const { className, children, defaultOpen = false, isOpen: controlledIsOpen, onOpenChange, ...restProps } = props;
|
|
82
|
+
const contentId = useId();
|
|
83
|
+
const buttonId = useId();
|
|
84
|
+
const isControlled = controlledIsOpen != null;
|
|
85
|
+
// This component has internal state that controls whether it is open or not,
|
|
86
|
+
// regardless if we are controlled or uncontrolled.
|
|
87
|
+
// If we are controlled, we use a layout effect to sync the controlled state
|
|
88
|
+
// with the internal state.
|
|
89
|
+
//
|
|
90
|
+
const [isOpen, setIsOpen] = useState(// If we are controlled, use that open state, otherwise use the uncontrolled
|
|
91
|
+
isControlled ? controlledIsOpen : defaultOpen);
|
|
92
|
+
useLayoutEffect(()=>{
|
|
93
|
+
if (isControlled) {
|
|
94
|
+
setIsOpen(controlledIsOpen);
|
|
95
|
+
}
|
|
96
|
+
}, [
|
|
97
|
+
controlledIsOpen,
|
|
98
|
+
isControlled
|
|
99
|
+
]);
|
|
100
|
+
const handleOpenChange = ()=>{
|
|
101
|
+
const newOpenState = !isOpen;
|
|
102
|
+
if (!isControlled) {
|
|
103
|
+
setIsOpen(newOpenState);
|
|
104
|
+
}
|
|
105
|
+
// Always call the change handler, even if we're uncontrolled.
|
|
106
|
+
// Easier to add stuff such as tracking etc.
|
|
107
|
+
if (onOpenChange) {
|
|
108
|
+
onOpenChange(newOpenState);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
return /*#__PURE__*/ jsx("div", {
|
|
112
|
+
...restProps,
|
|
113
|
+
className: cx('relative px-2', className),
|
|
114
|
+
ref: ref,
|
|
115
|
+
"data-open": isOpen,
|
|
116
|
+
children: /*#__PURE__*/ jsx(Provider, {
|
|
117
|
+
values: [
|
|
118
|
+
[
|
|
119
|
+
HeadingContext,
|
|
120
|
+
{
|
|
121
|
+
// Negative margin to strech the button to the entire with of the accordion (to support containers with a background color)
|
|
122
|
+
className: 'font-semibold leading-7 -mx-2 text-base',
|
|
123
|
+
// Supply a default level here to make this typecheck ok. Will be overwritten with the consumers set heading level anyways
|
|
124
|
+
level: 3,
|
|
125
|
+
_innerWrapper: (children)=>/*#__PURE__*/ jsxs("button", {
|
|
126
|
+
"aria-controls": contentId,
|
|
127
|
+
"aria-expanded": isOpen,
|
|
128
|
+
// Use outline with offset as focus indicator, this does not cover the left mint border on the expanded content and works with or without a background color on the accordion container
|
|
129
|
+
className: "flex min-h-[44px] w-full items-center justify-between gap-1.5 rounded-lg px-2 py-3.5 text-left focus-visible:outline-focus focus-visible:outline-focus-inset",
|
|
130
|
+
id: buttonId,
|
|
131
|
+
onClick: handleOpenChange,
|
|
132
|
+
children: [
|
|
133
|
+
children,
|
|
134
|
+
/*#__PURE__*/ jsx(ChevronDown, {
|
|
135
|
+
className: cx('flex-none transition-transform duration-300 motion-reduce:transition-none', isOpen && 'rotate-180')
|
|
136
|
+
})
|
|
137
|
+
]
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
],
|
|
141
|
+
[
|
|
142
|
+
ContentContext,
|
|
143
|
+
{
|
|
144
|
+
className: // Uses pseudo element for vertical padding, since that doesn't affect the height when the accordion is closed
|
|
145
|
+
'text-sm font-light leading-6 px-3.5 relative overflow-hidden border-mint border-l-[3px] before:relative before:block before:h-1.5 after:relative after:block after:h-1.5',
|
|
146
|
+
role: 'region',
|
|
147
|
+
// @ts-expect-error TODO: remove this expect-error when we're on React 19 https://github.com/facebook/react/issues/17157#issuecomment-2003750544
|
|
148
|
+
inert: isOpen ? undefined : 'true',
|
|
149
|
+
'aria-labelledby': buttonId,
|
|
150
|
+
_outerWrapper: (children)=>/*#__PURE__*/ jsx("div", {
|
|
151
|
+
className: cx('grid transition-all duration-300 after:relative after:block after:h-0 after:transition-all after:duration-300 motion-reduce:transition-none', isOpen ? 'grid-rows-[1fr] after:h-3.5' : 'grid-rows-[0fr] '),
|
|
152
|
+
children: children
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
],
|
|
157
|
+
children: children
|
|
158
|
+
})
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const _Accordion = /*#__PURE__*/ forwardRef(Accordion);
|
|
162
|
+
const _AccordionItem = /*#__PURE__*/ forwardRef(AccordionItem);
|
|
163
|
+
|
|
164
|
+
const badgeVariants = cva({
|
|
165
|
+
base: [
|
|
166
|
+
'inline-flex w-fit items-center justify-center gap-1.5 rounded-lg [&_svg]:shrink-0'
|
|
167
|
+
],
|
|
168
|
+
variants: {
|
|
169
|
+
color: {
|
|
170
|
+
'gray-dark': 'bg-gray-dark text-white',
|
|
171
|
+
mint: 'bg-mint',
|
|
172
|
+
sky: 'bg-sky',
|
|
173
|
+
white: 'bg-white',
|
|
174
|
+
'blue-dark': 'bg-blue-dark text-white',
|
|
175
|
+
'green-dark': 'bg-green-dark text-white'
|
|
176
|
+
},
|
|
177
|
+
size: {
|
|
178
|
+
small: 'description px-2 py-0.5 [&_svg]:h-4 [&_svg]:w-4',
|
|
179
|
+
medium: 'description px-2.5 py-1.5 [&_svg]:h-4 [&_svg]:w-4',
|
|
180
|
+
large: 'paragraph px-3 py-2 [&_svg]:h-5 [&_svg]:w-5'
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
defaultVariants: {
|
|
184
|
+
size: 'medium'
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
function Badge(props, ref) {
|
|
188
|
+
const { className: _className, color, size, ...restProps } = props;
|
|
189
|
+
const className = badgeVariants({
|
|
190
|
+
className: _className,
|
|
191
|
+
color,
|
|
192
|
+
size
|
|
193
|
+
});
|
|
194
|
+
return /*#__PURE__*/ jsx("span", {
|
|
195
|
+
className: className,
|
|
196
|
+
...restProps,
|
|
197
|
+
ref: ref
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const _Badge = /*#__PURE__*/ forwardRef(Badge);
|
|
9
201
|
|
|
10
202
|
/**
|
|
11
203
|
* Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
|
|
12
204
|
*/ const buttonVariants = cva({
|
|
13
205
|
base: [
|
|
14
|
-
'inline-flex min-h-[44px] cursor-pointer items-center justify-center whitespace-nowrap rounded-lg font-medium transition-
|
|
206
|
+
'inline-flex min-h-[44px] cursor-pointer items-center justify-center whitespace-nowrap rounded-lg font-medium transition-colors duration-200 focus-visible:outline-focus-offset [&:not([data-focus-visible])]:outline-none'
|
|
15
207
|
],
|
|
16
208
|
variants: {
|
|
17
209
|
/**
|
|
@@ -27,17 +219,21 @@ import { u as useClientLayoutEffect } from './useClientLayoutEffect-client-2_5na
|
|
|
27
219
|
* Adjusts the color of the button for usage on different backgrounds.
|
|
28
220
|
* @default green
|
|
29
221
|
*/ color: {
|
|
30
|
-
green: 'focus-visible:
|
|
31
|
-
mint: 'focus-visible:
|
|
32
|
-
white: 'focus-visible:
|
|
222
|
+
green: 'data-[focus-visible]:outline-focus',
|
|
223
|
+
mint: 'data-[focus-visible]:outline-focus data-[focus-visible]:outline-mint',
|
|
224
|
+
white: 'data-[focus-visible]:outline-focus data-[focus-visible]:outline-white'
|
|
33
225
|
},
|
|
34
226
|
/**
|
|
35
227
|
* When the button is without text, but with a single icon.
|
|
36
228
|
* @default false
|
|
37
229
|
*/ isIconOnly: {
|
|
38
230
|
true: 'p-2 [&>svg]:h-7 [&>svg]:w-7',
|
|
39
|
-
false:
|
|
40
|
-
|
|
231
|
+
false: 'gap-2.5 px-4 py-2'
|
|
232
|
+
},
|
|
233
|
+
// Make the content of the button transparent to hide it's content, but keep the button width
|
|
234
|
+
isPending: {
|
|
235
|
+
true: 'relative !text-transparent',
|
|
236
|
+
false: null
|
|
41
237
|
}
|
|
42
238
|
},
|
|
43
239
|
compoundVariants: [
|
|
@@ -45,96 +241,103 @@ import { u as useClientLayoutEffect } from './useClientLayoutEffect-client-2_5na
|
|
|
45
241
|
color: 'green',
|
|
46
242
|
variant: 'primary',
|
|
47
243
|
// Darken bg by 20% on hover. The color is manually crafted
|
|
48
|
-
className: 'bg-green text-white hover:bg-green-dark active:bg-[#007352]'
|
|
244
|
+
className: 'bg-green text-white hover:bg-green-dark active:bg-[#007352] [&_[role="progressbar"]]:text-white'
|
|
49
245
|
},
|
|
50
246
|
{
|
|
51
247
|
color: 'green',
|
|
52
248
|
variant: 'secondary',
|
|
53
|
-
className: '
|
|
249
|
+
className: 'text-black shadow-green hover:bg-green hover:text-white active:bg-green [&:hover_[role="progressbar"]]:text-white [&_[role="progressbar"]]:text-black'
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
color: 'green',
|
|
253
|
+
variant: 'tertiary',
|
|
254
|
+
className: '[&_[role="progressbar"]]:text-black'
|
|
54
255
|
},
|
|
55
256
|
{
|
|
56
257
|
color: 'mint',
|
|
57
258
|
variant: 'primary',
|
|
58
259
|
// Darken bg by 20% on hover. The color is manually crafted
|
|
59
|
-
className: 'active:[#9ddac6] bg-mint text-black hover:bg-[#8dd4bd]'
|
|
260
|
+
className: 'active:[#9ddac6] bg-mint text-black hover:bg-[#8dd4bd] [&_[role="progressbar"]]:text-black'
|
|
60
261
|
},
|
|
61
262
|
{
|
|
62
263
|
color: 'mint',
|
|
63
264
|
variant: 'secondary',
|
|
64
|
-
className: 'text-mint shadow-mint hover:bg-mint hover:text-black'
|
|
265
|
+
className: 'text-mint shadow-mint hover:bg-mint hover:text-black [&:hover_[role="progressbar"]]:text-black [&_[role="progressbar"]]:text-mint'
|
|
65
266
|
},
|
|
66
267
|
{
|
|
67
268
|
color: 'mint',
|
|
68
269
|
variant: 'tertiary',
|
|
69
|
-
className: 'text-mint'
|
|
270
|
+
className: 'text-mint [&_[role="progressbar"]]:text-mint'
|
|
70
271
|
},
|
|
71
272
|
{
|
|
72
273
|
color: 'white',
|
|
73
274
|
variant: 'primary',
|
|
74
|
-
className: 'bg-white text-black hover:bg-sky active:bg-sky-light'
|
|
275
|
+
className: 'bg-white text-black hover:bg-sky active:bg-sky-light [&_[role="progressbar"]]:text-black'
|
|
75
276
|
},
|
|
76
277
|
{
|
|
77
278
|
color: 'white',
|
|
78
279
|
variant: 'secondary',
|
|
79
|
-
className: 'text-white shadow-white hover:bg-white hover:text-black'
|
|
280
|
+
className: 'text-white shadow-white hover:bg-white hover:text-black [&:hover_[role="progressbar"]]:text-black [&_[role="progressbar"]]:text-white'
|
|
80
281
|
},
|
|
81
282
|
{
|
|
82
283
|
color: 'white',
|
|
83
284
|
variant: 'tertiary',
|
|
84
|
-
className: 'text-white'
|
|
285
|
+
className: 'text-white [&_[role="progressbar"]]:text-white'
|
|
85
286
|
}
|
|
86
287
|
],
|
|
87
288
|
defaultVariants: {
|
|
88
289
|
variant: 'primary',
|
|
89
290
|
color: 'green',
|
|
90
|
-
isIconOnly: false
|
|
291
|
+
isIconOnly: false,
|
|
292
|
+
isPending: false
|
|
91
293
|
}
|
|
92
294
|
});
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
setWidthOverride(ownRef.current?.getBoundingClientRect()?.width);
|
|
102
|
-
});
|
|
103
|
-
return ()=>{
|
|
104
|
-
setWidthOverride(undefined);
|
|
105
|
-
cancelAnimationFrame(requestID);
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
}, [
|
|
109
|
-
isLoading,
|
|
110
|
-
children
|
|
111
|
-
]);
|
|
112
|
-
let Component = 'a';
|
|
113
|
-
if (props.href == null) {
|
|
114
|
-
// If we don't have a href, it's a button, and we add a fallback type button to prevent the button from accidentally submitting when in a form
|
|
115
|
-
Component = 'button';
|
|
116
|
-
restProps.type ??= 'button';
|
|
295
|
+
function isLinkProps$1(props) {
|
|
296
|
+
return !!props.href;
|
|
297
|
+
}
|
|
298
|
+
const translations$1 = {
|
|
299
|
+
pending: {
|
|
300
|
+
nb: 'venter',
|
|
301
|
+
sv: 'väntar',
|
|
302
|
+
en: 'pending'
|
|
117
303
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
304
|
+
};
|
|
305
|
+
function Button(props, ref) {
|
|
306
|
+
const { children: _children, color, isIconOnly, isLoading, variant, isPending: _isPending, ...restProps } = props;
|
|
307
|
+
const isPending = _isPending || isLoading;
|
|
308
|
+
const className = buttonVariants({
|
|
309
|
+
className: props.className,
|
|
310
|
+
color,
|
|
311
|
+
isIconOnly,
|
|
312
|
+
variant,
|
|
313
|
+
isPending
|
|
314
|
+
});
|
|
315
|
+
const locale = _useLocale();
|
|
316
|
+
const { progressBarProps } = useProgressBar({
|
|
317
|
+
isIndeterminate: true,
|
|
318
|
+
'aria-label': translations$1.pending[locale]
|
|
319
|
+
});
|
|
320
|
+
const children = isPending ? /*#__PURE__*/ jsxs(Fragment, {
|
|
321
|
+
children: [
|
|
322
|
+
_children,
|
|
323
|
+
/*#__PURE__*/ jsx(LoadingSpinner, {
|
|
324
|
+
className: "absolute m-auto motion-safe:animate-spin",
|
|
325
|
+
...progressBarProps
|
|
326
|
+
})
|
|
327
|
+
]
|
|
328
|
+
}) : _children;
|
|
329
|
+
return isLinkProps$1(restProps) ? /*#__PURE__*/ jsx(Link, {
|
|
330
|
+
...restProps,
|
|
331
|
+
className: className,
|
|
127
332
|
ref: ref,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
width: widthOverride
|
|
131
|
-
},
|
|
333
|
+
children: children
|
|
334
|
+
}) : /*#__PURE__*/ jsx(Button$1, {
|
|
132
335
|
...restProps,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
})
|
|
336
|
+
className: className,
|
|
337
|
+
isPending: isPending,
|
|
338
|
+
ref: ref,
|
|
339
|
+
children: children
|
|
340
|
+
});
|
|
138
341
|
}
|
|
139
342
|
const _Button = /*#__PURE__*/ forwardRef(Button);
|
|
140
343
|
|
|
@@ -142,20 +345,27 @@ const formField = cx('group flex flex-col gap-2');
|
|
|
142
345
|
const formFieldError = cx('w-fit rounded-sm bg-red-light px-2 py-1 text-sm leading-6 text-red');
|
|
143
346
|
const input = cva({
|
|
144
347
|
base: [
|
|
145
|
-
|
|
348
|
+
// All inputs should always have a white background (this also ensures that type="search" on Safri doesn't get a gray background)
|
|
349
|
+
'bg-white',
|
|
350
|
+
// Use box-content to enable auto width based on number of characters (size)
|
|
351
|
+
// Setting min-height to prevent the input from collapsing in Safari
|
|
352
|
+
// Combining these with a padding-y as base classes makes it easier to standardize the height (44px) of all inputs
|
|
353
|
+
'box-content min-h-6 py-2.5',
|
|
354
|
+
'rounded-md text-base font-normal leading-6 placeholder-[#727070] outline-none ring-1 ring-black',
|
|
146
355
|
// invalid styles
|
|
147
|
-
'group-data-[invalid]:ring-
|
|
356
|
+
'group-data-[invalid]:ring-focus group-data-[invalid]:ring-red',
|
|
357
|
+
// Fix invisible ring on safari: https://github.com/tailwindlabs/tailwindcss.com/issues/1135
|
|
358
|
+
'appearance-none'
|
|
148
359
|
],
|
|
149
360
|
variants: {
|
|
150
361
|
// Focus rings. Can either be :focus or :focus-visible based on the needs of the particular component.
|
|
151
362
|
focusModifier: {
|
|
152
|
-
focus: 'focus:ring-
|
|
153
|
-
visible: 'data-[focus-visible]:ring-
|
|
363
|
+
focus: 'focus:ring-focus group-data-[invalid]:focus:ring',
|
|
364
|
+
visible: 'data-[focus-visible]:ring-focus group-data-[invalid]:data-[focus-visible]:ring'
|
|
154
365
|
},
|
|
155
366
|
isGrouped: {
|
|
156
|
-
false: '',
|
|
157
|
-
|
|
158
|
-
true: 'flex-1 !ring-0 first:pl-0 last:pr-0'
|
|
367
|
+
false: 'px-3',
|
|
368
|
+
true: 'flex-1 !ring-0'
|
|
159
369
|
}
|
|
160
370
|
},
|
|
161
371
|
defaultVariants: {
|
|
@@ -163,10 +373,14 @@ const input = cva({
|
|
|
163
373
|
isGrouped: false
|
|
164
374
|
}
|
|
165
375
|
});
|
|
166
|
-
const inputGroup = cx(
|
|
376
|
+
const inputGroup = cx([
|
|
377
|
+
'inline-flex items-center gap-3 overflow-hidden rounded-md bg-white px-3 text-base ring-1 ring-black focus-within:ring-focus',
|
|
378
|
+
'group-data-[invalid]:ring-focus group-data-[invalid]:ring-red group-data-[invalid]:focus-within:ring'
|
|
379
|
+
]);
|
|
167
380
|
const dropdown = {
|
|
168
|
-
popover: cx('min-w-[--trigger-width] overflow-auto rounded-md border border-black bg-white shadow data-[entering]:animate-in data-[exiting]:animate-out data-[entering]:fade-in data-[exiting]:fade-out'),
|
|
169
|
-
|
|
381
|
+
popover: cx('min-w-[--trigger-width] overflow-y-auto rounded-md border border-black bg-white shadow data-[entering]:animate-in data-[exiting]:animate-out data-[entering]:fade-in data-[exiting]:fade-out'),
|
|
382
|
+
// overflow-x-hidden is needed to prevent visible vertical scrollbars from overflowing the border radius of the popover
|
|
383
|
+
listbox: cx('max-h-[25rem] overflow-x-hidden text-sm outline-none'),
|
|
170
384
|
chevronIcon: cx('text-base transition-transform duration-150 group-data-[open]:rotate-180 motion-reduce:transition-none')
|
|
171
385
|
};
|
|
172
386
|
|
|
@@ -180,22 +394,13 @@ function ErrorMessage(props) {
|
|
|
180
394
|
});
|
|
181
395
|
}
|
|
182
396
|
|
|
183
|
-
function Description(props) {
|
|
184
|
-
const { className, ...restProps } = props;
|
|
185
|
-
return /*#__PURE__*/ jsx(Text, {
|
|
186
|
-
...restProps,
|
|
187
|
-
className: cx(className, 'text-sm font-light leading-6'),
|
|
188
|
-
slot: "description"
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
397
|
const defaultClasses$1 = cx([
|
|
193
|
-
'group relative left-0 inline-flex max-w-fit cursor-pointer items-start gap-4
|
|
398
|
+
'group relative left-0 -mx-2.5 inline-flex max-w-fit cursor-pointer items-start gap-4 p-2.5 leading-7'
|
|
194
399
|
]);
|
|
195
400
|
// Pulling this out into it's own component. Will probably export it in the future
|
|
196
401
|
// so it can be used in other views, outside of an input of type checkbox, like in table rows.
|
|
197
402
|
function CheckmarkBox() {
|
|
198
|
-
return /*#__PURE__*/ jsx("
|
|
403
|
+
return /*#__PURE__*/ jsx("span", {
|
|
199
404
|
className: cx([
|
|
200
405
|
'relative left-0 grid flex-none place-content-center rounded-sm border-2 border-black text-white',
|
|
201
406
|
// to vertically align the radio we need to calculate the label's height, which is equal to it's font size multiplied by the line height.
|
|
@@ -206,7 +411,7 @@ function CheckmarkBox() {
|
|
|
206
411
|
// selected
|
|
207
412
|
'group-data-[selected]:!border-green group-data-[selected]:!bg-green',
|
|
208
413
|
// focus
|
|
209
|
-
'group-data-[focus-visible]:
|
|
414
|
+
'group-data-[focus-visible]:outline-focus-offset',
|
|
210
415
|
// hovered
|
|
211
416
|
'group-data-[hovered]:border-green group-data-[hovered]:group-data-[invalid]:border-red group-data-[hovered]:bg-green-lightest group-data-[hovered]:group-data-[invalid]:bg-red-light',
|
|
212
417
|
// invalid - The border is 1 px thicker when invalid. We don't actually want to change the border width, as that causes the element's size to change
|
|
@@ -223,7 +428,7 @@ function Checkbox(props, ref) {
|
|
|
223
428
|
const id = useId();
|
|
224
429
|
const descriptionId = 'desc' + id;
|
|
225
430
|
const errorMessageId = 'error' + id;
|
|
226
|
-
const isInvalid =
|
|
431
|
+
const isInvalid = errorMessage != null || _isInvalid;
|
|
227
432
|
return /*#__PURE__*/ jsx("div", {
|
|
228
433
|
children: /*#__PURE__*/ jsxs(CheckboxContext.Provider, {
|
|
229
434
|
value: {
|
|
@@ -237,16 +442,15 @@ function Checkbox(props, ref) {
|
|
|
237
442
|
isInvalid: isInvalid,
|
|
238
443
|
ref: ref,
|
|
239
444
|
children: [
|
|
240
|
-
/*#__PURE__*/ jsx("div", {
|
|
241
|
-
className: "absolute -left-2.5 top-0 z-10 h-11 w-11"
|
|
242
|
-
}),
|
|
243
445
|
/*#__PURE__*/ jsx(CheckmarkBox, {}),
|
|
244
446
|
children
|
|
245
447
|
]
|
|
246
448
|
}),
|
|
247
|
-
description &&
|
|
248
|
-
|
|
449
|
+
description && // {/* Use a div instead of the Description component to avoid infinite re-render loops in React until this bug in RAC is fixed: https://github.com/adobe/react-spectrum/issues/6229 */}
|
|
450
|
+
/*#__PURE__*/ jsx("div", {
|
|
249
451
|
id: descriptionId,
|
|
452
|
+
slot: "description",
|
|
453
|
+
className: "description block",
|
|
250
454
|
children: description
|
|
251
455
|
}),
|
|
252
456
|
errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
|
|
@@ -269,9 +473,31 @@ function Label(props) {
|
|
|
269
473
|
});
|
|
270
474
|
}
|
|
271
475
|
|
|
476
|
+
function Description(props) {
|
|
477
|
+
const { className, ...restProps } = props;
|
|
478
|
+
return /*#__PURE__*/ jsx(Text, {
|
|
479
|
+
...restProps,
|
|
480
|
+
className: cx(className, 'description'),
|
|
481
|
+
slot: "description"
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* This component handles renders a custom error message (if provided), otherwise it falls back to the browser's native validation.
|
|
487
|
+
* In other words, this handles controlled and uncontrolled form errors.
|
|
488
|
+
*/ function ErrorMessageOrFieldError({ errorMessage }) {
|
|
489
|
+
return errorMessage ? /*#__PURE__*/ jsx(ErrorMessage, {
|
|
490
|
+
children: errorMessage
|
|
491
|
+
}) : /*#__PURE__*/ jsx(FieldError, {
|
|
492
|
+
className: formFieldError
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
272
496
|
function CheckboxGroup(props, ref) {
|
|
273
497
|
const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
|
|
274
|
-
|
|
498
|
+
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
499
|
+
// which will override any built in validation
|
|
500
|
+
const isInvalid = errorMessage != null || _isInvalid;
|
|
275
501
|
return /*#__PURE__*/ jsxs(CheckboxGroup$1, {
|
|
276
502
|
...restProps,
|
|
277
503
|
className: cx(className, 'flex flex-col gap-2'),
|
|
@@ -286,28 +512,53 @@ function CheckboxGroup(props, ref) {
|
|
|
286
512
|
children: description
|
|
287
513
|
}),
|
|
288
514
|
children,
|
|
289
|
-
|
|
290
|
-
|
|
515
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
516
|
+
errorMessage: errorMessage
|
|
291
517
|
})
|
|
292
518
|
]
|
|
293
519
|
});
|
|
294
520
|
}
|
|
295
521
|
const _CheckboxGroup = /*#__PURE__*/ forwardRef(CheckboxGroup);
|
|
296
522
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
523
|
+
const ListBox = ({ className, ...restProps })=>/*#__PURE__*/ jsx(ListBox$1, {
|
|
524
|
+
...restProps,
|
|
525
|
+
className: cx(dropdown.listbox, className)
|
|
526
|
+
});
|
|
527
|
+
const ListBoxItem = (props)=>{
|
|
528
|
+
let textValue = props.textValue;
|
|
529
|
+
// When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
|
|
530
|
+
// Since we use a render function (to handle the selected state) the child is never a string.
|
|
531
|
+
// This condition adds back that behaviour
|
|
532
|
+
if (textValue == null && typeof props.children === 'string') {
|
|
533
|
+
textValue = props.children;
|
|
534
|
+
}
|
|
535
|
+
return /*#__PURE__*/ jsx(ListBoxItem$1, {
|
|
536
|
+
...props,
|
|
537
|
+
className: cx(props.className, 'flex cursor-pointer px-6 py-3 leading-6 outline-none data-[focused]:bg-sky-lightest'),
|
|
538
|
+
textValue: textValue,
|
|
539
|
+
children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
|
|
540
|
+
children: [
|
|
541
|
+
isSelected && /*#__PURE__*/ jsx(Check, {
|
|
542
|
+
className: "-ml-6 text-base"
|
|
543
|
+
}),
|
|
544
|
+
props.children
|
|
545
|
+
]
|
|
546
|
+
})
|
|
547
|
+
});
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
function InputAddonDivider() {
|
|
551
|
+
return /*#__PURE__*/ jsx("span", {
|
|
552
|
+
className: "block h-6 w-px flex-none bg-black"
|
|
305
553
|
});
|
|
306
554
|
}
|
|
307
555
|
|
|
308
556
|
function Combobox(props, ref) {
|
|
309
|
-
const { className, children, description, errorMessage, isLoading, label, isInvalid: _isInvalid, ...restProps } = props;
|
|
310
|
-
const
|
|
557
|
+
const { className, children, description, errorMessage, isLoading, isPending: _isPending, label, isInvalid: _isInvalid, ...restProps } = props;
|
|
558
|
+
const isPending = _isPending || isLoading;
|
|
559
|
+
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
560
|
+
// which will override any built in validation
|
|
561
|
+
const isInvalid = errorMessage != null || _isInvalid;
|
|
311
562
|
return /*#__PURE__*/ jsxs(ComboBox, {
|
|
312
563
|
...restProps,
|
|
313
564
|
className: cx(className, formField),
|
|
@@ -329,7 +580,7 @@ function Combobox(props, ref) {
|
|
|
329
580
|
ref: ref
|
|
330
581
|
}),
|
|
331
582
|
/*#__PURE__*/ jsx(Button$1, {
|
|
332
|
-
children:
|
|
583
|
+
children: isPending ? /*#__PURE__*/ jsx(LoadingSpinner, {
|
|
333
584
|
className: "animate-spin"
|
|
334
585
|
}) : /*#__PURE__*/ jsx(ChevronDown, {
|
|
335
586
|
className: dropdown.chevronIcon
|
|
@@ -355,33 +606,13 @@ function Combobox(props, ref) {
|
|
|
355
606
|
]
|
|
356
607
|
});
|
|
357
608
|
}
|
|
358
|
-
const ComboboxItem = (props)=>{
|
|
359
|
-
let textValue = props.textValue;
|
|
360
|
-
// When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
|
|
361
|
-
// Since we use a render function (to handle the selected state) the child is never a string.
|
|
362
|
-
// This condition adds back that behaviour
|
|
363
|
-
if (textValue == null && typeof props.children === 'string') {
|
|
364
|
-
textValue = props.children;
|
|
365
|
-
}
|
|
366
|
-
return /*#__PURE__*/ jsx(ListBoxItem, {
|
|
367
|
-
...props,
|
|
368
|
-
className: cx(props.className, 'flex cursor-default px-6 py-2 leading-6 outline-none data-[focused]:bg-sky-lightest'),
|
|
369
|
-
textValue: textValue,
|
|
370
|
-
children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
|
|
371
|
-
children: [
|
|
372
|
-
isSelected && /*#__PURE__*/ jsx(Check, {
|
|
373
|
-
className: "-ml-6 text-base"
|
|
374
|
-
}),
|
|
375
|
-
props.children
|
|
376
|
-
]
|
|
377
|
-
})
|
|
378
|
-
});
|
|
379
|
-
};
|
|
380
609
|
const _Combobox = /*#__PURE__*/ forwardRef(Combobox);
|
|
381
610
|
|
|
382
611
|
function RadioGroup(props, ref) {
|
|
383
612
|
const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
|
|
384
|
-
|
|
613
|
+
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
614
|
+
// which will override any built in validation
|
|
615
|
+
const isInvalid = errorMessage != null || _isInvalid;
|
|
385
616
|
return /*#__PURE__*/ jsxs(RadioGroup$1, {
|
|
386
617
|
...restProps,
|
|
387
618
|
className: cx(className, 'flex flex-col gap-2'),
|
|
@@ -396,8 +627,8 @@ function RadioGroup(props, ref) {
|
|
|
396
627
|
children: description
|
|
397
628
|
}),
|
|
398
629
|
children,
|
|
399
|
-
|
|
400
|
-
|
|
630
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
631
|
+
errorMessage: errorMessage
|
|
401
632
|
})
|
|
402
633
|
]
|
|
403
634
|
});
|
|
@@ -405,7 +636,7 @@ function RadioGroup(props, ref) {
|
|
|
405
636
|
const _RadioGroup = /*#__PURE__*/ forwardRef(RadioGroup);
|
|
406
637
|
|
|
407
638
|
const defaultClasses = cx([
|
|
408
|
-
'relative inline-flex max-w-fit cursor-pointer items-start gap-4 py-2 leading-7',
|
|
639
|
+
'relative -ml-2.5 inline-flex max-w-fit cursor-pointer items-start gap-4 py-2.5 pl-2.5 leading-7',
|
|
409
640
|
// the radio button itself
|
|
410
641
|
'before:flex-none before:rounded-full before:border-2 before:border-black',
|
|
411
642
|
// to vertically align the radio we need to calculate the label's height, which is equal to it's font size multiplied by the line height.
|
|
@@ -418,38 +649,35 @@ const defaultClasses = cx([
|
|
|
418
649
|
// hover
|
|
419
650
|
'data-[hovered]:before:border-green data-[hovered]:before:bg-green-lightest data-[hovered]:data-[invalid]:before:bg-red-light',
|
|
420
651
|
// focus
|
|
421
|
-
'data-[focus-visible]:before:ring
|
|
652
|
+
'data-[focus-visible]:before:ring-focus-offset',
|
|
422
653
|
// invalid - The border is 1 px thicker when invalid. We don't actually want to change the border width, as that causes the element's size to change
|
|
423
654
|
// so we use an inner outline to artifically pad the border
|
|
424
655
|
'data-[invalid]:before:outline-solid data-[invalid]:before:border-red data-[invalid]:data-[selected]:before:!bg-red data-[invalid]:before:outline data-[invalid]:before:outline-[3px] data-[invalid]:before:outline-offset-[-3px] data-[invalid]:before:outline-red'
|
|
425
656
|
]);
|
|
426
657
|
function Radio(props, ref) {
|
|
427
658
|
const { children, className, description, ...restProps } = props;
|
|
428
|
-
return /*#__PURE__*/
|
|
659
|
+
return /*#__PURE__*/ jsx(Radio$1, {
|
|
429
660
|
...restProps,
|
|
430
661
|
className: cx(className, defaultClasses),
|
|
431
662
|
ref: ref,
|
|
432
|
-
children:
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
children: description
|
|
442
|
-
})
|
|
443
|
-
]
|
|
444
|
-
})
|
|
445
|
-
]
|
|
663
|
+
children: /*#__PURE__*/ jsxs("div", {
|
|
664
|
+
children: [
|
|
665
|
+
children,
|
|
666
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
667
|
+
className: "mt-2 block",
|
|
668
|
+
children: description
|
|
669
|
+
})
|
|
670
|
+
]
|
|
671
|
+
})
|
|
446
672
|
});
|
|
447
673
|
}
|
|
448
674
|
const _Radio = /*#__PURE__*/ forwardRef(Radio);
|
|
449
675
|
|
|
450
676
|
function Select(props, ref) {
|
|
451
677
|
const { className, children, description, errorMessage, label, isInvalid: _isInvalid, ...restProps } = props;
|
|
452
|
-
|
|
678
|
+
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
679
|
+
// which will override any built in validation
|
|
680
|
+
const isInvalid = errorMessage != null || _isInvalid;
|
|
453
681
|
return /*#__PURE__*/ jsxs(Select$1, {
|
|
454
682
|
...restProps,
|
|
455
683
|
className: cx(className, formField),
|
|
@@ -490,33 +718,11 @@ function Select(props, ref) {
|
|
|
490
718
|
]
|
|
491
719
|
});
|
|
492
720
|
}
|
|
493
|
-
const SelectItem = (props)=>{
|
|
494
|
-
let textValue = props.textValue;
|
|
495
|
-
// When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
|
|
496
|
-
// Since we use a render function (to handle the selected state) the child is never a string.
|
|
497
|
-
// This condition adds back that behaviour
|
|
498
|
-
if (textValue == null && typeof props.children === 'string') {
|
|
499
|
-
textValue = props.children;
|
|
500
|
-
}
|
|
501
|
-
return /*#__PURE__*/ jsx(ListBoxItem, {
|
|
502
|
-
...props,
|
|
503
|
-
className: cx(props.className, 'flex cursor-default px-6 py-2 leading-6 outline-none data-[focused]:bg-sky-lightest'),
|
|
504
|
-
textValue: textValue,
|
|
505
|
-
children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
|
|
506
|
-
children: [
|
|
507
|
-
isSelected && /*#__PURE__*/ jsx(Check, {
|
|
508
|
-
className: "-ml-6 text-base"
|
|
509
|
-
}),
|
|
510
|
-
props.children
|
|
511
|
-
]
|
|
512
|
-
})
|
|
513
|
-
});
|
|
514
|
-
};
|
|
515
721
|
const _Select = /*#__PURE__*/ forwardRef(Select);
|
|
516
722
|
|
|
517
723
|
function TextArea(props, ref) {
|
|
518
724
|
const { className, description, errorMessage, label, isInvalid: _isInvalid, rows, ...restProps } = props;
|
|
519
|
-
const isInvalid =
|
|
725
|
+
const isInvalid = errorMessage != null || _isInvalid;
|
|
520
726
|
return /*#__PURE__*/ jsxs(TextField$1, {
|
|
521
727
|
...restProps,
|
|
522
728
|
className: cx(className, formField),
|
|
@@ -541,18 +747,24 @@ function TextArea(props, ref) {
|
|
|
541
747
|
}
|
|
542
748
|
const _TextArea = /*#__PURE__*/ forwardRef(TextArea);
|
|
543
749
|
|
|
544
|
-
const
|
|
750
|
+
const inputVariants$1 = compose(input, cva({
|
|
545
751
|
base: '',
|
|
546
752
|
variants: {
|
|
547
753
|
textAlign: {
|
|
548
754
|
right: 'text-right',
|
|
549
755
|
left: ''
|
|
756
|
+
},
|
|
757
|
+
autoWidth: {
|
|
758
|
+
true: 'max-w-fit',
|
|
759
|
+
false: ''
|
|
550
760
|
}
|
|
551
761
|
}
|
|
552
762
|
}));
|
|
553
763
|
function TextField(props, ref) {
|
|
554
|
-
const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, ...restProps } = props;
|
|
555
|
-
|
|
764
|
+
const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, size, ...restProps } = props;
|
|
765
|
+
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
766
|
+
// which will override any built in validation
|
|
767
|
+
const isInvalid = errorMessage != null || _isInvalid;
|
|
556
768
|
return /*#__PURE__*/ jsxs(TextField$1, {
|
|
557
769
|
...restProps,
|
|
558
770
|
className: cx(className, formField),
|
|
@@ -565,29 +777,96 @@ function TextField(props, ref) {
|
|
|
565
777
|
children: description
|
|
566
778
|
}),
|
|
567
779
|
leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
|
|
568
|
-
className: inputGroup,
|
|
780
|
+
className: cx(inputGroup, {
|
|
781
|
+
'w-fit': !!size
|
|
782
|
+
}),
|
|
569
783
|
children: [
|
|
570
784
|
leftAddon,
|
|
571
|
-
withAddonDivider && leftAddon && /*#__PURE__*/ jsx(
|
|
572
|
-
className: "ml-3"
|
|
573
|
-
}),
|
|
785
|
+
withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
574
786
|
/*#__PURE__*/ jsx(Input, {
|
|
575
|
-
className:
|
|
787
|
+
className: inputVariants$1({
|
|
576
788
|
textAlign,
|
|
577
|
-
isGrouped: true
|
|
789
|
+
isGrouped: true,
|
|
790
|
+
autoWidth: !!size
|
|
578
791
|
}),
|
|
579
|
-
ref: ref
|
|
792
|
+
ref: ref,
|
|
793
|
+
size: size
|
|
580
794
|
}),
|
|
581
|
-
withAddonDivider && rightAddon && /*#__PURE__*/ jsx(
|
|
582
|
-
|
|
795
|
+
withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
796
|
+
rightAddon
|
|
797
|
+
]
|
|
798
|
+
}) : /*#__PURE__*/ jsx(Input, {
|
|
799
|
+
className: inputVariants$1({
|
|
800
|
+
textAlign,
|
|
801
|
+
autoWidth: !!size
|
|
802
|
+
}),
|
|
803
|
+
ref: ref,
|
|
804
|
+
size: size
|
|
805
|
+
}),
|
|
806
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
807
|
+
errorMessage: errorMessage
|
|
808
|
+
})
|
|
809
|
+
]
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
const _TextField = /*#__PURE__*/ forwardRef(TextField);
|
|
813
|
+
|
|
814
|
+
// This component is based on a copy of ../textfield/TextField, refactoring is TBD: https://github.com/code-obos/grunnmuren/pull/722#issuecomment-1931478786
|
|
815
|
+
const inputVariants = compose(input, cva({
|
|
816
|
+
base: '',
|
|
817
|
+
variants: {
|
|
818
|
+
textAlign: {
|
|
819
|
+
right: 'text-right',
|
|
820
|
+
left: ''
|
|
821
|
+
},
|
|
822
|
+
autoWidth: {
|
|
823
|
+
true: 'max-w-fit',
|
|
824
|
+
false: ''
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}));
|
|
828
|
+
function NumberField(props, ref) {
|
|
829
|
+
const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, size, ...restProps } = props;
|
|
830
|
+
// the order of the conditions matter here, because providing a value for isInvalid makes the validation state "controlled",
|
|
831
|
+
// which will override any built in validation
|
|
832
|
+
const isInvalid = errorMessage != null || _isInvalid;
|
|
833
|
+
return /*#__PURE__*/ jsxs(NumberField$1, {
|
|
834
|
+
...restProps,
|
|
835
|
+
className: cx(className, formField),
|
|
836
|
+
isInvalid: isInvalid,
|
|
837
|
+
children: [
|
|
838
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
839
|
+
children: label
|
|
840
|
+
}),
|
|
841
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
842
|
+
children: description
|
|
843
|
+
}),
|
|
844
|
+
leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
|
|
845
|
+
className: cx(inputGroup, {
|
|
846
|
+
'w-fit': !!size
|
|
847
|
+
}),
|
|
848
|
+
children: [
|
|
849
|
+
leftAddon,
|
|
850
|
+
withAddonDivider && leftAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
851
|
+
/*#__PURE__*/ jsx(Input, {
|
|
852
|
+
className: inputVariants({
|
|
853
|
+
textAlign,
|
|
854
|
+
isGrouped: true,
|
|
855
|
+
autoWidth: !!size
|
|
856
|
+
}),
|
|
857
|
+
ref: ref,
|
|
858
|
+
size: size
|
|
583
859
|
}),
|
|
860
|
+
withAddonDivider && rightAddon && /*#__PURE__*/ jsx(InputAddonDivider, {}),
|
|
584
861
|
rightAddon
|
|
585
862
|
]
|
|
586
863
|
}) : /*#__PURE__*/ jsx(Input, {
|
|
587
|
-
className:
|
|
588
|
-
textAlign
|
|
864
|
+
className: inputVariants({
|
|
865
|
+
textAlign,
|
|
866
|
+
autoWidth: !!size
|
|
589
867
|
}),
|
|
590
|
-
ref: ref
|
|
868
|
+
ref: ref,
|
|
869
|
+
size: size
|
|
591
870
|
}),
|
|
592
871
|
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
593
872
|
errorMessage: errorMessage
|
|
@@ -595,11 +874,326 @@ function TextField(props, ref) {
|
|
|
595
874
|
]
|
|
596
875
|
});
|
|
597
876
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
877
|
+
const _NumberField = /*#__PURE__*/ forwardRef(NumberField);
|
|
878
|
+
|
|
879
|
+
// TODO: add new icons
|
|
880
|
+
const iconMap = {
|
|
881
|
+
info: InfoCircle,
|
|
882
|
+
success: CheckCircle,
|
|
883
|
+
warning: Warning,
|
|
884
|
+
danger: CloseCircle
|
|
885
|
+
};
|
|
886
|
+
const alertVariants = cva({
|
|
887
|
+
base: [
|
|
888
|
+
'grid grid-cols-[auto_1fr_auto] items-center gap-2 rounded-md border-2 px-3 py-2',
|
|
889
|
+
// Heading styles:
|
|
890
|
+
'[&_[data-slot="heading"]]:text-base [&_[data-slot="heading"]]:font-medium [&_[data-slot="heading"]]:leading-7',
|
|
891
|
+
// Content styles:
|
|
892
|
+
'[&:has([data-slot="heading"])_[data-slot="content"]]:col-span-full [&_[data-slot="content"]]:text-sm [&_[data-slot="content"]]:leading-6',
|
|
893
|
+
// Footer styles:
|
|
894
|
+
'[&_[data-slot="footer"]]:col-span-full [&_[data-slot="footer"]]:text-xs [&_[data-slot="footer"]]:font-light [&_[data-slot="footer"]]:leading-6'
|
|
895
|
+
],
|
|
896
|
+
variants: {
|
|
897
|
+
/**
|
|
898
|
+
* The variant of the alert
|
|
899
|
+
* @default info
|
|
900
|
+
*/ variant: {
|
|
901
|
+
info: 'border-[#1A7FA7] bg-sky-light',
|
|
902
|
+
success: 'border-[#0F9B6E] bg-mint-light',
|
|
903
|
+
warning: 'border-[#C57C13] bg-[#FFF2DE]',
|
|
904
|
+
danger: 'border-[#C0385D] bg-red-light'
|
|
905
|
+
}
|
|
906
|
+
},
|
|
907
|
+
defaultVariants: {
|
|
908
|
+
variant: 'info'
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
const translations = {
|
|
912
|
+
close: {
|
|
913
|
+
nb: 'Lukk',
|
|
914
|
+
sv: 'Stäng',
|
|
915
|
+
en: 'Close'
|
|
916
|
+
},
|
|
917
|
+
showMore: {
|
|
918
|
+
nb: 'Les mer',
|
|
919
|
+
sv: 'Läs mer',
|
|
920
|
+
en: 'Read more'
|
|
921
|
+
},
|
|
922
|
+
showLess: {
|
|
923
|
+
nb: 'Vis mindre',
|
|
924
|
+
sv: 'Dölj',
|
|
925
|
+
en: 'Show less'
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
const Alertbox = ({ children, role, className, variant = 'info', isDismissable = false, isDismissed, onDismiss, isExpandable })=>{
|
|
929
|
+
const Icon = iconMap[variant];
|
|
930
|
+
const locale = _useLocale();
|
|
931
|
+
const id = useId();
|
|
932
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
933
|
+
const isCollapsed = isExpandable && !isExpanded;
|
|
934
|
+
const [isUncontrolledVisible, setIsUncontrolledVisible] = useState(true);
|
|
935
|
+
const isVisible = isDismissed !== undefined ? !isDismissed : isUncontrolledVisible;
|
|
936
|
+
if (!isVisible) return;
|
|
937
|
+
const close = ()=>{
|
|
938
|
+
setIsUncontrolledVisible(false);
|
|
939
|
+
if (onDismiss) onDismiss();
|
|
940
|
+
};
|
|
941
|
+
const isInDevMode = process.env.NODE_ENV !== 'production';
|
|
942
|
+
if (isInDevMode && onDismiss && !isDismissable) {
|
|
943
|
+
console.warn('Passing an `onDismiss` callback without setting the `isDismissable` prop to `true` will not have any effect.');
|
|
944
|
+
}
|
|
945
|
+
if (isInDevMode && !children) {
|
|
946
|
+
console.error('`No children was passed to the <AlertBox/>` component.');
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const [firstChild, ...restChildren] = Children.toArray(children);
|
|
950
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
951
|
+
className: alertVariants({
|
|
952
|
+
className,
|
|
953
|
+
variant
|
|
954
|
+
}),
|
|
955
|
+
// The role prop is required to force consumers to consider and choose the appropriate alertbox role.
|
|
956
|
+
// role="none" will not have any effect on a div, so it can be omitted.
|
|
957
|
+
role: role === 'none' ? undefined : role,
|
|
958
|
+
children: [
|
|
959
|
+
/*#__PURE__*/ jsx(Icon, {}),
|
|
960
|
+
firstChild,
|
|
961
|
+
isDismissable && /*#__PURE__*/ jsx("button", {
|
|
962
|
+
className: cx('-m-2 grid h-11 w-11 place-items-center rounded-xl', 'focus-visible:outline-focus focus-visible:-outline-offset-8'),
|
|
963
|
+
onClick: close,
|
|
964
|
+
"aria-label": translations.close[locale],
|
|
965
|
+
children: /*#__PURE__*/ jsx(Close, {})
|
|
966
|
+
}),
|
|
967
|
+
isExpandable && /*#__PURE__*/ jsxs("button", {
|
|
968
|
+
className: cx('relative col-span-full row-start-2 -my-3 inline-flex max-w-fit cursor-pointer items-center gap-1 py-3 text-sm leading-6', // Focus styles:
|
|
969
|
+
'outline-none after:absolute after:bottom-3 after:left-0 after:right-0 after:h-0', 'focus-visible:after:h-[2px] focus-visible:after:bg-black'),
|
|
970
|
+
onClick: ()=>setIsExpanded((prevState)=>!prevState),
|
|
971
|
+
"aria-expanded": isExpanded,
|
|
972
|
+
"aria-controls": id,
|
|
973
|
+
children: [
|
|
974
|
+
isExpanded ? translations.showLess[locale] : translations.showMore[locale],
|
|
975
|
+
/*#__PURE__*/ jsx(ChevronDown, {
|
|
976
|
+
className: cx('transition-transform duration-150 motion-reduce:transition-none', isExpanded && 'rotate-180')
|
|
977
|
+
})
|
|
978
|
+
]
|
|
979
|
+
}),
|
|
980
|
+
restChildren?.length > 0 && /*#__PURE__*/ jsx("div", {
|
|
981
|
+
className: cx('col-span-full grid gap-y-4', isCollapsed && '[&>*:not([data-slot="footer"])]:hidden'),
|
|
982
|
+
id: id,
|
|
983
|
+
children: restChildren
|
|
984
|
+
})
|
|
985
|
+
]
|
|
986
|
+
});
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
function Breadcrumbs(props, ref) {
|
|
990
|
+
const { className, children, ...restProps } = props;
|
|
991
|
+
return /*#__PURE__*/ jsx(Breadcrumbs$1, {
|
|
992
|
+
...restProps,
|
|
993
|
+
className: cx(className, 'flex flex-wrap text-sm leading-6'),
|
|
994
|
+
ref: ref,
|
|
995
|
+
children: children
|
|
601
996
|
});
|
|
602
997
|
}
|
|
603
|
-
const
|
|
998
|
+
const _Breadcrumbs = /*#__PURE__*/ forwardRef(Breadcrumbs);
|
|
999
|
+
|
|
1000
|
+
function Breadcrumb(props, ref) {
|
|
1001
|
+
const { className, children, href, ...restProps } = props;
|
|
1002
|
+
return /*#__PURE__*/ jsxs(Breadcrumb$1, {
|
|
1003
|
+
className: cx(className, 'group flex items-center'),
|
|
1004
|
+
...restProps,
|
|
1005
|
+
ref: ref,
|
|
1006
|
+
children: [
|
|
1007
|
+
href ? /*#__PURE__*/ jsx(Link, {
|
|
1008
|
+
href: href,
|
|
1009
|
+
// use outline instead of ring for focus marker that can be offset without creating a white background between the focus marker and the element content
|
|
1010
|
+
className: "rounded-sm data-[focus-visible]:outline-focus group-last:no-underline [&:not([data-focus-visible])]:outline-none",
|
|
1011
|
+
children: children
|
|
1012
|
+
}) : children,
|
|
1013
|
+
/*#__PURE__*/ jsx(ChevronRight, {
|
|
1014
|
+
className: "px-1 group-last:hidden"
|
|
1015
|
+
})
|
|
1016
|
+
]
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
const _Breadcrumb = /*#__PURE__*/ forwardRef(Breadcrumb);
|
|
1020
|
+
|
|
1021
|
+
function isLinkProps(props) {
|
|
1022
|
+
return !!props.href;
|
|
1023
|
+
}
|
|
1024
|
+
function Backlink(props, ref) {
|
|
1025
|
+
const { className, children, withUnderline, ...restProps } = props;
|
|
1026
|
+
const Component = isLinkProps(props) ? Link : Button$1;
|
|
1027
|
+
return /*#__PURE__*/ jsxs(Component, {
|
|
1028
|
+
className: cx(className, 'group flex max-w-fit cursor-pointer items-center gap-3 rounded-md p-2.5 no-underline data-[focus-visible]:outline-focus [&:not([data-focus-visible])]:outline-none'),
|
|
1029
|
+
...restProps,
|
|
1030
|
+
// @ts-expect-error ignore the type of the ref here
|
|
1031
|
+
ref: ref,
|
|
1032
|
+
children: [
|
|
1033
|
+
/*#__PURE__*/ jsx(ChevronLeft, {
|
|
1034
|
+
className: cx('-ml-[0.5em] flex-shrink-0 transition-transform duration-300 group-hover:-translate-x-1')
|
|
1035
|
+
}),
|
|
1036
|
+
/*#__PURE__*/ jsx("span", {
|
|
1037
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
1038
|
+
className: cx('border-b-[1px] border-t-[1px] border-transparent transition-colors duration-300', withUnderline ? 'border-b-black' : 'group-hover:border-b-black'),
|
|
1039
|
+
children: children
|
|
1040
|
+
})
|
|
1041
|
+
})
|
|
1042
|
+
]
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
const _Backlink = /*#__PURE__*/ forwardRef(Backlink);
|
|
1046
|
+
|
|
1047
|
+
const cardVariants = cva({
|
|
1048
|
+
base: [
|
|
1049
|
+
'group/card',
|
|
1050
|
+
'rounded-2xl border p-3',
|
|
1051
|
+
'grid auto-rows-max gap-y-4',
|
|
1052
|
+
'relative',
|
|
1053
|
+
// **** Heading ****
|
|
1054
|
+
'[&_[data-slot="heading"]]:inline',
|
|
1055
|
+
'[&_[data-slot="heading"]]:heading-s',
|
|
1056
|
+
'[&_[data-slot="heading"]]:leading-6',
|
|
1057
|
+
'[&_[data-slot="heading"]]:w-fit',
|
|
1058
|
+
'[&_[data-slot="heading"]]:text-pretty',
|
|
1059
|
+
// **** Content ****
|
|
1060
|
+
'[&_[data-slot="content"]]:grid [&_[data-slot="content"]]:auto-rows-max [&_[data-slot="content"]]:gap-y-4',
|
|
1061
|
+
// **** Media ****
|
|
1062
|
+
'[&_[data-slot="media"]]:overflow-hidden',
|
|
1063
|
+
'[&_[data-slot="media"]]:rounded-t-2xl',
|
|
1064
|
+
// Position media at the edges of the card (because of these negative margins the media-element must be a wrapper around the actual image or other media content)
|
|
1065
|
+
'[&_[data-slot="media"]]:mx-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))] [&_[data-slot="media"]]:mt-[calc(theme(space.3)*-1-theme(borderWidth.DEFAULT))]',
|
|
1066
|
+
// Sets the aspect ratio of the media content (width: 100% is necessary to make aspect ratio work in FF)
|
|
1067
|
+
'[&_[data-slot="media"]>*]:aspect-[3/2] [&_[data-slot="media"]>*]:w-full [&_[data-slot="media"]_img]:object-cover',
|
|
1068
|
+
// Prepare zoom animation for hover effects. The hover effect can also be enabled by classes on the parent component, so it is always prepared here.
|
|
1069
|
+
'[&_[data-slot="media"]>*]:duration-300 [&_[data-slot="media"]>*]:ease-in-out [&_[data-slot="media"]>*]:motion-safe:transition-transform',
|
|
1070
|
+
// **** Card link ****
|
|
1071
|
+
// **** Hover ****
|
|
1072
|
+
// Enables the zoom hover effect on media (note that we can't use group-hover/card here, because there might be other clickable elements in the card aside from the heading)
|
|
1073
|
+
'[&:has([data-slot="card-link"]_a:hover)_[data-slot="media"]>*]:scale-110',
|
|
1074
|
+
// **** Card link in Heading ****
|
|
1075
|
+
'[&:has([data-slot="heading"]_[data-slot="card-link"]:hover)_[data-slot="media"]>*]:scale-110',
|
|
1076
|
+
// Border (bottom/top) is set to transparent to make sure the bottom underline is not visible when the card is hovered
|
|
1077
|
+
// Border top is set to even out the border bottom used for the underline
|
|
1078
|
+
'[&_[data-slot="heading"]_[data-slot="card-link"]]:no-underline',
|
|
1079
|
+
'[&_[data-slot="heading"]_[data-slot="card-link"]]:border-y-2',
|
|
1080
|
+
'[&_[data-slot="heading"]_[data-slot="card-link"]]:border-y-transparent',
|
|
1081
|
+
'[&_[data-slot="heading"]_[data-slot="card-link"]]:transition-colors',
|
|
1082
|
+
'[&_[data-slot="heading"]_[data-slot="card-link"]:hover]:border-b-current',
|
|
1083
|
+
// Mimic heading styles for the card link if placed in the heading slot. This is necessary to make the custom underline align with the link text
|
|
1084
|
+
'[&_[data-slot="heading"]_[data-slot="card-link"]]:heading-s [&_[data-slot="heading"]_[data-slot="card-link"]]:text-pretty [&_[data-slot="heading"]_[data-slot="card-link"]]:leading-6',
|
|
1085
|
+
// **** Fail-safe for interactive elements ****
|
|
1086
|
+
// Make interactive elements clickable by themselves, while the rest of the card is clickable as a whole
|
|
1087
|
+
// The card is made clickable by a pseudo-element on the heading that covers the entire card
|
|
1088
|
+
'[&:not(:has([data-slot="card-link"]_a))_a:not([data-slot="card-link"])]:relative [&_button]:relative [&_input]:relative',
|
|
1089
|
+
// Our Button component has position: relative by default, so we need to override that if it is used in a CardLink (to make the entire card clickable)
|
|
1090
|
+
'[&_[data-slot="card-link"]_a]:static',
|
|
1091
|
+
// Place other interactive on top of the pseudo-element that makes the entire card clickable
|
|
1092
|
+
// by setting a higher z-index than the pseudo-element (which implicitly z-index 0)
|
|
1093
|
+
'[&_a:not([data-slot="card-link"])]:z-[1] [&_button]:z-[1] [&_input]:z-[1]'
|
|
1094
|
+
],
|
|
1095
|
+
variants: {
|
|
1096
|
+
/**
|
|
1097
|
+
* The variant of the card
|
|
1098
|
+
* @default subtle
|
|
1099
|
+
*/ variant: {
|
|
1100
|
+
subtle: [
|
|
1101
|
+
'border-transparent',
|
|
1102
|
+
// Media styles:
|
|
1103
|
+
'[&_[data-slot="media"]]:rounded-b-2xl'
|
|
1104
|
+
],
|
|
1105
|
+
outlined: 'border border-black'
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
1108
|
+
defaultVariants: {
|
|
1109
|
+
variant: 'subtle'
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
const Card = ({ children, className: _className, variant, ...restProps })=>{
|
|
1113
|
+
const className = cardVariants({
|
|
1114
|
+
className: _className,
|
|
1115
|
+
variant
|
|
1116
|
+
});
|
|
1117
|
+
return /*#__PURE__*/ jsx("div", {
|
|
1118
|
+
className: className,
|
|
1119
|
+
...restProps,
|
|
1120
|
+
children: children
|
|
1121
|
+
});
|
|
1122
|
+
};
|
|
1123
|
+
const cardLinkVariants = cva({
|
|
1124
|
+
base: 'w-fit max-w-full',
|
|
1125
|
+
variants: {
|
|
1126
|
+
withHref: {
|
|
1127
|
+
true: [
|
|
1128
|
+
// **** Clickarea ****
|
|
1129
|
+
'cursor-pointer',
|
|
1130
|
+
'after:absolute',
|
|
1131
|
+
'after:inset-[calc(theme(borderWidth.DEFAULT)*-1)]',
|
|
1132
|
+
'after:rounded-[calc(theme(borderRadius.2xl)-theme(borderWidth.DEFAULT))]',
|
|
1133
|
+
// **** Focus ****
|
|
1134
|
+
'focus-visible:outline-none',
|
|
1135
|
+
'data-[focus-visible]:after:outline-focus',
|
|
1136
|
+
'data-[focus-visible]:after:outline-offset-2',
|
|
1137
|
+
// **** Hover ****
|
|
1138
|
+
// Links are underlined by default, and the underline is removed on hover.
|
|
1139
|
+
// So we make sure that also happens when the user hovers the clickable area.
|
|
1140
|
+
'hover:no-underline'
|
|
1141
|
+
],
|
|
1142
|
+
false: [
|
|
1143
|
+
// **** Clickarea ****
|
|
1144
|
+
'[&_a]:after:cursor-pointer',
|
|
1145
|
+
'[&_a]:after:absolute',
|
|
1146
|
+
'[&_a]:after:inset-[calc(theme(borderWidth.DEFAULT)*-1)]',
|
|
1147
|
+
'[&_a]:after:rounded-[calc(theme(borderRadius.2xl)-theme(borderWidth.DEFAULT))]',
|
|
1148
|
+
// **** Focus ****
|
|
1149
|
+
'[&_a[data-focus-visible]]:outline-none',
|
|
1150
|
+
'[&_a[data-focus-visible]]:after:outline-focus',
|
|
1151
|
+
'[&_a[data-focus-visible]]:after:outline-offset-2',
|
|
1152
|
+
// **** Hover ****
|
|
1153
|
+
// Links are underlined by default, and the underline is removed on hover.
|
|
1154
|
+
// So we make sure that also happens when the user hovers the card.
|
|
1155
|
+
// The group-hover ensures that the hover effect also applies when this component is used as a wrapper around a link.
|
|
1156
|
+
'[&_a]:group-hover/card:no-underline'
|
|
1157
|
+
]
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
/**
|
|
1162
|
+
* A component that creates a clickable area on a card.
|
|
1163
|
+
* It can be used either as a wrapper around a link or as a standalone link.
|
|
1164
|
+
*/ const CardLink = ({ className: _className, href, ...restProps })=>{
|
|
1165
|
+
const className = cardLinkVariants({
|
|
1166
|
+
className: _className,
|
|
1167
|
+
withHref: !!href
|
|
1168
|
+
});
|
|
1169
|
+
return href ? /*#__PURE__*/ jsx(Link, {
|
|
1170
|
+
"data-slot": "card-link",
|
|
1171
|
+
...restProps,
|
|
1172
|
+
href: href,
|
|
1173
|
+
className: className
|
|
1174
|
+
}) : // We can't utilize that the `Link` component from react-aria-components renders as a span if it doesn't have an href,
|
|
1175
|
+
// because it still renders with role="link" and tabindex="0" which makes it focusable.
|
|
1176
|
+
// So we need to render a div instead.
|
|
1177
|
+
/*#__PURE__*/ jsx("div", {
|
|
1178
|
+
"data-slot": "card-link",
|
|
1179
|
+
className: className,
|
|
1180
|
+
...restProps
|
|
1181
|
+
});
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* A React component that wraps https://react-spectrum.adobe.com/react-aria/useDateFormatter.html
|
|
1186
|
+
* By default it sets the timeZone to `Europe/Berlin` to prevent the server's timezone from affecting
|
|
1187
|
+
* the localized format
|
|
1188
|
+
*/ const DateFormatter = ({ options: _options, value, children: render })=>{
|
|
1189
|
+
const options = {
|
|
1190
|
+
timeZone: 'Europe/Berlin',
|
|
1191
|
+
..._options
|
|
1192
|
+
};
|
|
1193
|
+
const formatter = useDateFormatter(options);
|
|
1194
|
+
const date = typeof value === 'string' ? new Date(value) : value;
|
|
1195
|
+
const formatted = formatter.format(date);
|
|
1196
|
+
return render ? render(formatted) : formatted;
|
|
1197
|
+
};
|
|
604
1198
|
|
|
605
|
-
export { _Button as Button, _Checkbox as Checkbox, _CheckboxGroup as CheckboxGroup, _Combobox as Combobox, ComboboxItem, _Radio as Radio, _RadioGroup as RadioGroup, _Select as Select, SelectItem, _TextArea as TextArea, _TextField as TextField };
|
|
1199
|
+
export { _Accordion as Accordion, _AccordionItem as AccordionItem, Alertbox, _Backlink as Backlink, _Badge as Badge, _Breadcrumb as Breadcrumb, _Breadcrumbs as Breadcrumbs, _Button as Button, Card, CardLink, _Checkbox as Checkbox, _CheckboxGroup as CheckboxGroup, _Combobox as Combobox, ListBoxItem as ComboboxItem, Content, ContentContext, DateFormatter, Footer, GrunnmurenProvider, Heading, HeadingContext, Media, _NumberField as NumberField, _Radio as Radio, _RadioGroup as RadioGroup, _Select as Select, ListBoxItem as SelectItem, _TextArea as TextArea, _TextField as TextField, _useLocale as useLocale };
|