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