@obosbbl/grunnmuren-react 2.0.0-canary.1 → 2.0.0-canary.2
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 +1 -1
- package/dist/index.d.mts +228 -0
- package/dist/index.mjs +585 -0
- package/dist/useClientLayoutEffect-client-2_5nawgR.js +9 -0
- package/package.json +7 -38
- package/dist/button/Button.d.mts +0 -83
- package/dist/button/Button.d.ts +0 -83
- package/dist/button/Button.mjs +0 -149
- package/dist/checkbox/index.d.mts +0 -32
- package/dist/checkbox/index.d.ts +0 -32
- package/dist/checkbox/index.mjs +0 -106
- package/dist/label/index.d.mts +0 -13
- package/dist/label/index.d.ts +0 -13
- package/dist/label/index.mjs +0 -45
- package/dist/radiogroup/index.d.mts +0 -30
- package/dist/radiogroup/index.d.ts +0 -30
- package/dist/radiogroup/index.mjs +0 -65
- package/dist/textfield/index.d.mts +0 -31
- package/dist/textfield/index.d.ts +0 -31
- package/dist/textfield/index.mjs +0 -88
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
import { Text, CheckboxContext, Checkbox as Checkbox$1, Label as Label$1, CheckboxGroup as CheckboxGroup$1, FieldError, ComboBox, Group, Input, Button as Button$1, Popover, ListBox, ListBoxItem, RadioGroup as RadioGroup$1, Radio as Radio$1, Select as Select$1, SelectValue, TextField as TextField$1, TextArea as TextArea$1 } from 'react-aria-components';
|
|
2
|
+
export { Form } from 'react-aria-components';
|
|
3
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
|
+
import { useRef, useState, useId } from 'react';
|
|
5
|
+
import { cva, cx, compose } from 'cva';
|
|
6
|
+
import { LoadingSpinner, Check, ChevronDown } from '@obosbbl/grunnmuren-icons-react';
|
|
7
|
+
import { u as useClientLayoutEffect } from './useClientLayoutEffect-client-2_5nawgR.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
|
|
11
|
+
*/ const buttonVariants = cva({
|
|
12
|
+
base: [
|
|
13
|
+
'inline-flex min-h-[44px] cursor-pointer items-center justify-center whitespace-nowrap rounded-lg font-medium transition-all duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2'
|
|
14
|
+
],
|
|
15
|
+
variants: {
|
|
16
|
+
/**
|
|
17
|
+
* The variant of the button
|
|
18
|
+
* @default primary
|
|
19
|
+
*/ variant: {
|
|
20
|
+
primary: 'no-underline',
|
|
21
|
+
// by using an inset box-shadow to emulate a border instead of an actual border, the button size will be equal regardless of the variant
|
|
22
|
+
secondary: 'no-underline shadow-[inset_0_0_0_2px]',
|
|
23
|
+
tertiary: 'underline hover:no-underline'
|
|
24
|
+
},
|
|
25
|
+
/**
|
|
26
|
+
* Adjusts the color of the button for usage on different backgrounds.
|
|
27
|
+
* @default green
|
|
28
|
+
*/ color: {
|
|
29
|
+
green: 'focus-visible:ring-black',
|
|
30
|
+
mint: 'focus-visible:ring-mint focus-visible:ring-offset-green-dark',
|
|
31
|
+
white: 'focus-visible:ring-white focus-visible:ring-offset-blue'
|
|
32
|
+
},
|
|
33
|
+
/**
|
|
34
|
+
* When the button is without text, but with a single icon.
|
|
35
|
+
* @default false
|
|
36
|
+
*/ isIconOnly: {
|
|
37
|
+
true: 'p-2 [&>svg]:h-7 [&>svg]:w-7',
|
|
38
|
+
false: // The of-type classes takes care to add spacing when the button is used with icons
|
|
39
|
+
'px-4 py-2 [&>svg]:first-of-type:mr-2.5 [&>svg]:last-of-type:ml-2.5'
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
compoundVariants: [
|
|
43
|
+
{
|
|
44
|
+
color: 'green',
|
|
45
|
+
variant: 'primary',
|
|
46
|
+
// Darken bg by 20% on hover. The color is manually crafted
|
|
47
|
+
className: 'bg-green text-white hover:bg-green-dark active:bg-[#007352]'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
color: 'green',
|
|
51
|
+
variant: 'secondary',
|
|
52
|
+
className: 'bg-white text-black shadow-green hover:bg-green hover:text-white active:bg-green'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
color: 'mint',
|
|
56
|
+
variant: 'primary',
|
|
57
|
+
// Darken bg by 20% on hover. The color is manually crafted
|
|
58
|
+
className: 'active:[#9ddac6] bg-mint text-black hover:bg-[#8dd4bd]'
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
color: 'mint',
|
|
62
|
+
variant: 'secondary',
|
|
63
|
+
className: 'text-mint shadow-mint hover:bg-mint hover:text-black'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
color: 'mint',
|
|
67
|
+
variant: 'tertiary',
|
|
68
|
+
className: 'text-mint'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
color: 'white',
|
|
72
|
+
variant: 'primary',
|
|
73
|
+
className: 'bg-white text-black hover:bg-sky active:bg-sky-light'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
color: 'white',
|
|
77
|
+
variant: 'secondary',
|
|
78
|
+
className: 'text-white shadow-white hover:bg-white hover:text-black'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
color: 'white',
|
|
82
|
+
variant: 'tertiary',
|
|
83
|
+
className: 'text-white'
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
defaultVariants: {
|
|
87
|
+
variant: 'primary',
|
|
88
|
+
color: 'green',
|
|
89
|
+
isIconOnly: false
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
function Button(props) {
|
|
93
|
+
const { children, className, color, isIconOnly, loading, variant, style, ...restProps } = props;
|
|
94
|
+
// TODO: Merge refs when we use RAC
|
|
95
|
+
const buttonRef = useRef(null);
|
|
96
|
+
const [widthOverride, setWidthOverride] = useState();
|
|
97
|
+
useClientLayoutEffect(()=>{
|
|
98
|
+
if (loading) {
|
|
99
|
+
const requestID = window.requestAnimationFrame(()=>{
|
|
100
|
+
setWidthOverride(buttonRef?.current?.getBoundingClientRect()?.width);
|
|
101
|
+
});
|
|
102
|
+
return ()=>{
|
|
103
|
+
setWidthOverride(undefined);
|
|
104
|
+
cancelAnimationFrame(requestID);
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}, [
|
|
108
|
+
loading,
|
|
109
|
+
children
|
|
110
|
+
]);
|
|
111
|
+
let Component = 'a';
|
|
112
|
+
if (props.href == null) {
|
|
113
|
+
// 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
|
|
114
|
+
Component = 'button';
|
|
115
|
+
restProps.type ??= 'button';
|
|
116
|
+
}
|
|
117
|
+
return(// @ts-expect-error TS doesn't agree here taht restProps is safe to spread, because restProps for anchors aren't type compatible with restProps for buttons, but that should be okay here
|
|
118
|
+
/*#__PURE__*/ jsx(Component, {
|
|
119
|
+
"aria-busy": loading ? true : undefined,
|
|
120
|
+
className: buttonVariants({
|
|
121
|
+
className,
|
|
122
|
+
color,
|
|
123
|
+
isIconOnly,
|
|
124
|
+
variant
|
|
125
|
+
}),
|
|
126
|
+
ref: buttonRef,
|
|
127
|
+
style: {
|
|
128
|
+
...style,
|
|
129
|
+
width: widthOverride
|
|
130
|
+
},
|
|
131
|
+
...restProps,
|
|
132
|
+
children: widthOverride ? // remove margin for icon alignment
|
|
133
|
+
/*#__PURE__*/ jsx(LoadingSpinner, {
|
|
134
|
+
className: "!m-0 mx-auto animate-spin"
|
|
135
|
+
}) : children
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const formField = cx('group flex flex-col gap-2');
|
|
140
|
+
const formFieldError = cx('w-fit rounded-sm bg-red-light px-2 py-1 text-sm leading-6 text-red');
|
|
141
|
+
const input = cva({
|
|
142
|
+
base: [
|
|
143
|
+
'rounded-md px-3 py-2.5 text-sm font-normal leading-6 placeholder-[#727070] outline-none ring-1 ring-black',
|
|
144
|
+
// invalid styles
|
|
145
|
+
'group-data-[invalid]:ring-2 group-data-[invalid]:ring-red'
|
|
146
|
+
],
|
|
147
|
+
variants: {
|
|
148
|
+
// Focus rings. Can either be :focus or :focus-visible based on the needs of the particular component.
|
|
149
|
+
focusModifier: {
|
|
150
|
+
focus: 'focus:ring-2 group-data-[invalid]:focus:ring',
|
|
151
|
+
visible: 'data-[focus-visible]:ring-2 group-data-[invalid]:data-[focus-visible]:ring'
|
|
152
|
+
},
|
|
153
|
+
isGrouped: {
|
|
154
|
+
false: '',
|
|
155
|
+
//
|
|
156
|
+
true: 'flex-1 !ring-0 first:pl-0 last:pr-0'
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
defaultVariants: {
|
|
160
|
+
focusModifier: 'focus',
|
|
161
|
+
isGrouped: false
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
const inputGroup = cx('inline-flex items-center overflow-hidden rounded-md px-3 ring-1 ring-black focus-within:ring-2 group-data-[invalid]:ring-2 group-data-[invalid]:ring-red group-data-[invalid]:focus-within:ring');
|
|
165
|
+
const dropdown = {
|
|
166
|
+
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'),
|
|
167
|
+
listbox: cx('text-sm outline-none'),
|
|
168
|
+
chevronIcon: cx('text-base transition-transform duration-150 group-data-[open]:rotate-180 motion-reduce:transition-none')
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
function ErrorMessage(props) {
|
|
172
|
+
const { children, className, ...restProps } = props;
|
|
173
|
+
return /*#__PURE__*/ jsx(Text, {
|
|
174
|
+
...restProps,
|
|
175
|
+
className: cx(className, formFieldError),
|
|
176
|
+
slot: "errorMessage",
|
|
177
|
+
children: children
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function Description(props) {
|
|
182
|
+
const { className, ...restProps } = props;
|
|
183
|
+
return /*#__PURE__*/ jsx(Text, {
|
|
184
|
+
...restProps,
|
|
185
|
+
className: cx(className, 'text-sm font-light leading-6'),
|
|
186
|
+
slot: "description"
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const defaultClasses$1 = cx([
|
|
191
|
+
'group relative left-0 inline-flex max-w-fit cursor-pointer items-start gap-4 py-2 leading-7'
|
|
192
|
+
]);
|
|
193
|
+
// Pulling this out into it's own component. Will probably export it in the future
|
|
194
|
+
// so it can be used in other views, outside of an input of type checkbox, like in table rows.
|
|
195
|
+
function CheckmarkBox() {
|
|
196
|
+
return /*#__PURE__*/ jsx("div", {
|
|
197
|
+
className: cx([
|
|
198
|
+
'relative left-0 grid flex-none place-content-center rounded-sm border-2 border-black text-white',
|
|
199
|
+
// 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.
|
|
200
|
+
// For the ::before psuedo element the line height of the label is always 1em.
|
|
201
|
+
// When we know the height of the label we use the height of the radio to push it down to align with the label's first line
|
|
202
|
+
// TODO: 1.75 here is the unit less lineheight, altough we use 1.75rem as the line height, so there is a mismatch here. Revisit this when we've worked on typography in v2. Should this be a CSS custom property instead?
|
|
203
|
+
'mt-[calc((1em_*_1.75_-_24px)_/_2)] h-[24px] w-[24px]',
|
|
204
|
+
// selected
|
|
205
|
+
'group-data-[selected]:!border-green group-data-[selected]:!bg-green',
|
|
206
|
+
// focus
|
|
207
|
+
'group-data-[focus-visible]:ring-2 group-data-[focus-visible]:ring-black group-data-[focus-visible]:ring-offset-[9px]',
|
|
208
|
+
// hovered
|
|
209
|
+
'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',
|
|
210
|
+
// 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
|
|
211
|
+
// so we use an inner shadow of 1 px instead to pad the actual border
|
|
212
|
+
'group-data-[invalid]:border-red group-data-[invalid]:group-data-[selected]:shadow-none group-data-[invalid]:shadow-[inset_0_0_0_1px] group-data-[invalid]:shadow-red'
|
|
213
|
+
]),
|
|
214
|
+
children: /*#__PURE__*/ jsx(Check, {
|
|
215
|
+
className: "h-full w-full opacity-0 group-data-[selected]:opacity-100"
|
|
216
|
+
})
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function Checkbox(props) {
|
|
220
|
+
const { children, className, description, errorMessage, isInvalid: _isInvalid, ...restProps } = props;
|
|
221
|
+
const id = useId();
|
|
222
|
+
const descriptionId = 'desc' + id;
|
|
223
|
+
const errorMessageId = 'error' + id;
|
|
224
|
+
const isInvalid = _isInvalid || errorMessage != null;
|
|
225
|
+
return /*#__PURE__*/ jsx("div", {
|
|
226
|
+
children: /*#__PURE__*/ jsxs(CheckboxContext.Provider, {
|
|
227
|
+
value: {
|
|
228
|
+
'aria-describedby': description ? descriptionId : undefined,
|
|
229
|
+
'aria-errormessage': errorMessage ? errorMessageId : undefined
|
|
230
|
+
},
|
|
231
|
+
children: [
|
|
232
|
+
/*#__PURE__*/ jsxs(Checkbox$1, {
|
|
233
|
+
...restProps,
|
|
234
|
+
className: cx(className, defaultClasses$1),
|
|
235
|
+
isInvalid: isInvalid,
|
|
236
|
+
children: [
|
|
237
|
+
/*#__PURE__*/ jsx("div", {
|
|
238
|
+
className: "absolute -left-2.5 top-0 z-10 h-11 w-11"
|
|
239
|
+
}),
|
|
240
|
+
/*#__PURE__*/ jsx(CheckmarkBox, {}),
|
|
241
|
+
children
|
|
242
|
+
]
|
|
243
|
+
}),
|
|
244
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
245
|
+
className: "block",
|
|
246
|
+
id: descriptionId,
|
|
247
|
+
children: description
|
|
248
|
+
}),
|
|
249
|
+
errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
|
|
250
|
+
className: "mt-2 block",
|
|
251
|
+
id: errorMessageId,
|
|
252
|
+
children: errorMessage
|
|
253
|
+
})
|
|
254
|
+
]
|
|
255
|
+
})
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function Label(props) {
|
|
260
|
+
const { children, className, ...restProps } = props;
|
|
261
|
+
return /*#__PURE__*/ jsx(Label$1, {
|
|
262
|
+
className: cx(className, 'font-semibold leading-7'),
|
|
263
|
+
...restProps,
|
|
264
|
+
children: children
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function CheckboxGroup(props) {
|
|
269
|
+
const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
|
|
270
|
+
const isInvalid = _isInvalid || errorMessage != null;
|
|
271
|
+
return /*#__PURE__*/ jsxs(CheckboxGroup$1, {
|
|
272
|
+
...restProps,
|
|
273
|
+
className: cx(className, 'flex flex-col gap-2'),
|
|
274
|
+
isInvalid: isInvalid,
|
|
275
|
+
isRequired: isRequired,
|
|
276
|
+
children: [
|
|
277
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
278
|
+
children: label
|
|
279
|
+
}),
|
|
280
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
281
|
+
children: description
|
|
282
|
+
}),
|
|
283
|
+
children,
|
|
284
|
+
errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
|
|
285
|
+
children: errorMessage
|
|
286
|
+
})
|
|
287
|
+
]
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* This component handles renders a custom error message (if provided), otherwise it falls back to the browser's native validation.
|
|
293
|
+
* In other words, this handles controlled and uncontrolled form errors.
|
|
294
|
+
*/ function ErrorMessageOrFieldError({ errorMessage }) {
|
|
295
|
+
return errorMessage ? /*#__PURE__*/ jsx(ErrorMessage, {
|
|
296
|
+
children: errorMessage
|
|
297
|
+
}) : /*#__PURE__*/ jsx(FieldError, {
|
|
298
|
+
className: formFieldError
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function Combobox(props) {
|
|
303
|
+
const { className, children, description, errorMessage, isLoading, label, isInvalid: _isInvalid, ...restProps } = props;
|
|
304
|
+
const isInvalid = _isInvalid || errorMessage != null;
|
|
305
|
+
return /*#__PURE__*/ jsxs(ComboBox, {
|
|
306
|
+
...restProps,
|
|
307
|
+
className: cx(className, formField),
|
|
308
|
+
isInvalid: isInvalid,
|
|
309
|
+
children: [
|
|
310
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
311
|
+
children: label
|
|
312
|
+
}),
|
|
313
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
314
|
+
children: description
|
|
315
|
+
}),
|
|
316
|
+
/*#__PURE__*/ jsxs(Group, {
|
|
317
|
+
className: inputGroup,
|
|
318
|
+
children: [
|
|
319
|
+
/*#__PURE__*/ jsx(Input, {
|
|
320
|
+
className: input({
|
|
321
|
+
isGrouped: true
|
|
322
|
+
})
|
|
323
|
+
}),
|
|
324
|
+
/*#__PURE__*/ jsx(Button$1, {
|
|
325
|
+
children: isLoading ? /*#__PURE__*/ jsx(LoadingSpinner, {
|
|
326
|
+
className: "animate-spin"
|
|
327
|
+
}) : /*#__PURE__*/ jsx(ChevronDown, {
|
|
328
|
+
className: dropdown.chevronIcon
|
|
329
|
+
})
|
|
330
|
+
})
|
|
331
|
+
]
|
|
332
|
+
}),
|
|
333
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
334
|
+
errorMessage: errorMessage
|
|
335
|
+
}),
|
|
336
|
+
/*#__PURE__*/ jsx(Popover, {
|
|
337
|
+
// FIXME: The trigger width doesn't include the padding of the group, so for now we have to apply this workaround.
|
|
338
|
+
// Also... the combobox border gets a pixel wider when focused, so we account for that as well when calculating the width
|
|
339
|
+
// and the offset.
|
|
340
|
+
// The input gutter should probably be moved to a theme variable instead of using the hardcoded value as here.
|
|
341
|
+
className: cx(dropdown.popover, 'min-w-[calc(var(--trigger-width)+26px)]'),
|
|
342
|
+
crossOffset: -13,
|
|
343
|
+
children: /*#__PURE__*/ jsx(ListBox, {
|
|
344
|
+
className: dropdown.listbox,
|
|
345
|
+
children: children
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
]
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
const ComboboxItem = (props)=>{
|
|
352
|
+
let textValue = props.textValue;
|
|
353
|
+
// When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
|
|
354
|
+
// Since we use a render function (to handle the selected state) the child is never a string.
|
|
355
|
+
// This condition adds back that behaviour
|
|
356
|
+
if (textValue == null && typeof props.children === 'string') {
|
|
357
|
+
textValue = props.children;
|
|
358
|
+
}
|
|
359
|
+
return /*#__PURE__*/ jsx(ListBoxItem, {
|
|
360
|
+
...props,
|
|
361
|
+
className: cx(props.className, 'flex cursor-default px-6 py-2 leading-6 outline-none data-[focused]:bg-sky-lightest'),
|
|
362
|
+
textValue: textValue,
|
|
363
|
+
children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
|
|
364
|
+
children: [
|
|
365
|
+
isSelected && /*#__PURE__*/ jsx(Check, {
|
|
366
|
+
className: "-ml-6 text-base"
|
|
367
|
+
}),
|
|
368
|
+
props.children
|
|
369
|
+
]
|
|
370
|
+
})
|
|
371
|
+
});
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
function RadioGroup(props) {
|
|
375
|
+
const { children, className, description, errorMessage, label, isRequired, isInvalid: _isInvalid, ...restProps } = props;
|
|
376
|
+
const isInvalid = _isInvalid || errorMessage != null;
|
|
377
|
+
return /*#__PURE__*/ jsxs(RadioGroup$1, {
|
|
378
|
+
...restProps,
|
|
379
|
+
className: cx(className, 'flex flex-col gap-2'),
|
|
380
|
+
isInvalid: isInvalid,
|
|
381
|
+
isRequired: isRequired,
|
|
382
|
+
children: [
|
|
383
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
384
|
+
children: label
|
|
385
|
+
}),
|
|
386
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
387
|
+
children: description
|
|
388
|
+
}),
|
|
389
|
+
children,
|
|
390
|
+
errorMessage && /*#__PURE__*/ jsx(ErrorMessage, {
|
|
391
|
+
children: errorMessage
|
|
392
|
+
})
|
|
393
|
+
]
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const defaultClasses = cx([
|
|
398
|
+
'relative inline-flex max-w-fit cursor-pointer items-start gap-4 py-2 leading-7',
|
|
399
|
+
// the radio button itself
|
|
400
|
+
'before:flex-none before:rounded-full before:border-2 before:border-black',
|
|
401
|
+
// 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.
|
|
402
|
+
// For the ::before psuedo element the line height of the label is always 1em.
|
|
403
|
+
// When we know the height of the label we use the height of the radio to push it down to align with the label's first line
|
|
404
|
+
// TODO: 1.75 here is the unit less lineheight, altough we use 1.75rem as the line height, so there is a mismatch here. Revisit this when we've worked on typography in v2. Should this be a CSS custom property instead?
|
|
405
|
+
'before:mt-[calc((1em_*_1.75_-_24px)_/_2)] before:h-[24px] before:w-[24px]',
|
|
406
|
+
// selected
|
|
407
|
+
'data-[selected]:before:border-black data-[selected]:before:bg-green data-[selected]:before:shadow-[inset_0_0_0_4px_rgb(255,255,255)]',
|
|
408
|
+
// hover
|
|
409
|
+
'data-[hovered]:before:border-green data-[hovered]:before:bg-green-lightest data-[hovered]:data-[invalid]:before:bg-red-light',
|
|
410
|
+
// focus
|
|
411
|
+
'data-[focus-visible]:before:ring data-[focus-visible]:before:ring-black data-[focus-visible]:before:ring-offset-[9px]',
|
|
412
|
+
// 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
|
|
413
|
+
// so we use an inner outline to artifically pad the border
|
|
414
|
+
'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'
|
|
415
|
+
]);
|
|
416
|
+
function Radio(props) {
|
|
417
|
+
const { children, className, description, ...restProps } = props;
|
|
418
|
+
return /*#__PURE__*/ jsxs(Radio$1, {
|
|
419
|
+
...restProps,
|
|
420
|
+
className: cx(className, defaultClasses),
|
|
421
|
+
children: [
|
|
422
|
+
/*#__PURE__*/ jsx("div", {
|
|
423
|
+
className: "absolute -left-2.5 top-0 z-10 h-11 w-11 "
|
|
424
|
+
}),
|
|
425
|
+
/*#__PURE__*/ jsxs("div", {
|
|
426
|
+
children: [
|
|
427
|
+
children,
|
|
428
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
429
|
+
className: "mt-2 block",
|
|
430
|
+
children: description
|
|
431
|
+
})
|
|
432
|
+
]
|
|
433
|
+
})
|
|
434
|
+
]
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function Select(props) {
|
|
439
|
+
const { className, children, description, errorMessage, label, isInvalid: _isInvalid, ...restProps } = props;
|
|
440
|
+
const isInvalid = _isInvalid || errorMessage != null;
|
|
441
|
+
return /*#__PURE__*/ jsxs(Select$1, {
|
|
442
|
+
...restProps,
|
|
443
|
+
className: cx(className, formField),
|
|
444
|
+
isInvalid: isInvalid,
|
|
445
|
+
children: [
|
|
446
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
447
|
+
children: label
|
|
448
|
+
}),
|
|
449
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
450
|
+
children: description
|
|
451
|
+
}),
|
|
452
|
+
/*#__PURE__*/ jsxs(Button$1, {
|
|
453
|
+
className: cx(input({
|
|
454
|
+
focusModifier: 'visible'
|
|
455
|
+
}), // How to reuse placeholder text?
|
|
456
|
+
'inline-flex cursor-default items-center gap-2'),
|
|
457
|
+
children: [
|
|
458
|
+
/*#__PURE__*/ jsx(SelectValue, {
|
|
459
|
+
className: "flex-1 truncate text-left data-[placeholder]:text-[#727070]"
|
|
460
|
+
}),
|
|
461
|
+
/*#__PURE__*/ jsx(ChevronDown, {
|
|
462
|
+
className: dropdown.chevronIcon
|
|
463
|
+
})
|
|
464
|
+
]
|
|
465
|
+
}),
|
|
466
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
467
|
+
errorMessage: errorMessage
|
|
468
|
+
}),
|
|
469
|
+
/*#__PURE__*/ jsx(Popover, {
|
|
470
|
+
className: dropdown.popover,
|
|
471
|
+
children: /*#__PURE__*/ jsx(ListBox, {
|
|
472
|
+
className: dropdown.listbox,
|
|
473
|
+
children: children
|
|
474
|
+
})
|
|
475
|
+
})
|
|
476
|
+
]
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
const SelectItem = (props)=>{
|
|
480
|
+
let textValue = props.textValue;
|
|
481
|
+
// When the ListBoxItem child isn't a string we have to set textValue for keyboard completion to work.
|
|
482
|
+
// Since we use a render function (to handle the selected state) the child is never a string.
|
|
483
|
+
// This condition adds back that behaviour
|
|
484
|
+
if (textValue == null && typeof props.children === 'string') {
|
|
485
|
+
textValue = props.children;
|
|
486
|
+
}
|
|
487
|
+
return /*#__PURE__*/ jsx(ListBoxItem, {
|
|
488
|
+
...props,
|
|
489
|
+
className: cx(props.className, 'flex cursor-default px-6 py-2 leading-6 outline-none data-[focused]:bg-sky-lightest'),
|
|
490
|
+
textValue: textValue,
|
|
491
|
+
children: ({ isSelected })=>/*#__PURE__*/ jsxs(Fragment, {
|
|
492
|
+
children: [
|
|
493
|
+
isSelected && /*#__PURE__*/ jsx(Check, {
|
|
494
|
+
className: "-ml-6 text-base"
|
|
495
|
+
}),
|
|
496
|
+
props.children
|
|
497
|
+
]
|
|
498
|
+
})
|
|
499
|
+
});
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
function TextArea(props) {
|
|
503
|
+
const { className, description, errorMessage, label, isInvalid: _isInvalid, rows, ...restProps } = props;
|
|
504
|
+
const isInvalid = _isInvalid || errorMessage != null;
|
|
505
|
+
return /*#__PURE__*/ jsxs(TextField$1, {
|
|
506
|
+
...restProps,
|
|
507
|
+
className: cx(className, formField),
|
|
508
|
+
isInvalid: isInvalid,
|
|
509
|
+
children: [
|
|
510
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
511
|
+
children: label
|
|
512
|
+
}),
|
|
513
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
514
|
+
children: description
|
|
515
|
+
}),
|
|
516
|
+
/*#__PURE__*/ jsx(TextArea$1, {
|
|
517
|
+
className: input(),
|
|
518
|
+
rows: rows
|
|
519
|
+
}),
|
|
520
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
521
|
+
errorMessage: errorMessage
|
|
522
|
+
})
|
|
523
|
+
]
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const inputWithAlignment = compose(input, cva({
|
|
528
|
+
base: '',
|
|
529
|
+
variants: {
|
|
530
|
+
textAlign: {
|
|
531
|
+
right: 'text-right',
|
|
532
|
+
left: ''
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}));
|
|
536
|
+
function TextField(props) {
|
|
537
|
+
const { className, description, errorMessage, label, leftAddon, isInvalid: _isInvalid, textAlign, rightAddon, withAddonDivider, ...restProps } = props;
|
|
538
|
+
const isInvalid = _isInvalid || errorMessage != null;
|
|
539
|
+
return /*#__PURE__*/ jsxs(TextField$1, {
|
|
540
|
+
...restProps,
|
|
541
|
+
className: cx(className, formField),
|
|
542
|
+
isInvalid: isInvalid,
|
|
543
|
+
children: [
|
|
544
|
+
label && /*#__PURE__*/ jsx(Label, {
|
|
545
|
+
children: label
|
|
546
|
+
}),
|
|
547
|
+
description && /*#__PURE__*/ jsx(Description, {
|
|
548
|
+
children: description
|
|
549
|
+
}),
|
|
550
|
+
leftAddon || rightAddon ? /*#__PURE__*/ jsxs(Group, {
|
|
551
|
+
className: inputGroup,
|
|
552
|
+
children: [
|
|
553
|
+
leftAddon,
|
|
554
|
+
withAddonDivider && leftAddon && /*#__PURE__*/ jsx(Divider, {
|
|
555
|
+
className: "ml-3"
|
|
556
|
+
}),
|
|
557
|
+
/*#__PURE__*/ jsx(Input, {
|
|
558
|
+
className: inputWithAlignment({
|
|
559
|
+
textAlign,
|
|
560
|
+
isGrouped: true
|
|
561
|
+
})
|
|
562
|
+
}),
|
|
563
|
+
withAddonDivider && rightAddon && /*#__PURE__*/ jsx(Divider, {
|
|
564
|
+
className: "mr-3"
|
|
565
|
+
}),
|
|
566
|
+
rightAddon
|
|
567
|
+
]
|
|
568
|
+
}) : /*#__PURE__*/ jsx(Input, {
|
|
569
|
+
className: inputWithAlignment({
|
|
570
|
+
textAlign
|
|
571
|
+
})
|
|
572
|
+
}),
|
|
573
|
+
/*#__PURE__*/ jsx(ErrorMessageOrFieldError, {
|
|
574
|
+
errorMessage: errorMessage
|
|
575
|
+
})
|
|
576
|
+
]
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
function Divider({ className }) {
|
|
580
|
+
return /*#__PURE__*/ jsx("span", {
|
|
581
|
+
className: cx(className, 'block h-6 w-px flex-none bg-black')
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export { Button, Checkbox, CheckboxGroup, Combobox, ComboboxItem, Radio, RadioGroup, Select, SelectItem, TextArea, TextField };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useLayoutEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
const canUseDOM = ()=>{
|
|
5
|
+
return typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
|
|
6
|
+
};
|
|
7
|
+
const useClientLayoutEffect = canUseDOM() ? useLayoutEffect : ()=>{};
|
|
8
|
+
|
|
9
|
+
export { useClientLayoutEffect as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obosbbl/grunnmuren-react",
|
|
3
|
-
"version": "2.0.0-canary.
|
|
3
|
+
"version": "2.0.0-canary.2",
|
|
4
4
|
"description": "Grunnmuren components in React",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "https://github.com/code-obos/grunnmuren"
|
|
@@ -9,54 +9,23 @@
|
|
|
9
9
|
"sideEffects": false,
|
|
10
10
|
"type": "module",
|
|
11
11
|
"exports": {
|
|
12
|
-
"
|
|
13
|
-
"types": "./dist/
|
|
14
|
-
"default": "./dist/
|
|
15
|
-
},
|
|
16
|
-
"./checkbox": {
|
|
17
|
-
"types": "./dist/checkbox/index.d.ts",
|
|
18
|
-
"default": "./dist/checkbox/index.mjs"
|
|
19
|
-
},
|
|
20
|
-
"./label": {
|
|
21
|
-
"types": "./dist/label/index.d.ts",
|
|
22
|
-
"default": "./dist/label/index.mjs"
|
|
23
|
-
},
|
|
24
|
-
"./radiogroup": {
|
|
25
|
-
"types": "./dist/radiogroup/index.d.ts",
|
|
26
|
-
"default": "./dist/radiogroup/index.mjs"
|
|
27
|
-
},
|
|
28
|
-
"./textfield": {
|
|
29
|
-
"types": "./dist/textfield/index.d.ts",
|
|
30
|
-
"default": "./dist/textfield/index.mjs"
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.mts",
|
|
14
|
+
"default": "./dist/index.mjs"
|
|
31
15
|
}
|
|
32
16
|
},
|
|
33
17
|
"files": [
|
|
34
18
|
"dist"
|
|
35
19
|
],
|
|
36
20
|
"dependencies": {
|
|
37
|
-
"@obosbbl/grunnmuren-icons-react": "^2.0.0-canary.
|
|
21
|
+
"@obosbbl/grunnmuren-icons-react": "^2.0.0-canary.1",
|
|
38
22
|
"cva": "1.0.0-beta.1",
|
|
39
|
-
"react-aria-components": "1.0.0
|
|
23
|
+
"react-aria-components": "^1.0.0"
|
|
40
24
|
},
|
|
41
25
|
"peerDependencies": {
|
|
42
26
|
"react": "^18"
|
|
43
27
|
},
|
|
44
|
-
"unbuild": {
|
|
45
|
-
"entries": [
|
|
46
|
-
"./src/button/Button.tsx",
|
|
47
|
-
"./src/checkbox/index.ts",
|
|
48
|
-
"./src/label/index.ts",
|
|
49
|
-
"./src/radiogroup/index.ts",
|
|
50
|
-
"./src/textfield/index.ts"
|
|
51
|
-
],
|
|
52
|
-
"declaration": true,
|
|
53
|
-
"rollup": {
|
|
54
|
-
"esbuild": {
|
|
55
|
-
"jsx": "automatic"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
28
|
"scripts": {
|
|
60
|
-
"build": "
|
|
29
|
+
"build": "bunchee"
|
|
61
30
|
}
|
|
62
31
|
}
|