@obosbbl/grunnmuren-react 2.0.0-canary.12 → 2.0.0-canary.14

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 CHANGED
@@ -14,20 +14,35 @@ npm install @obosbbl/grunnmuren-react@canary
14
14
  pnpm add @obosbbl/grunnmuren-react@canary
15
15
  ```
16
16
 
17
- ## Localization configuration
17
+ ## Setup
18
+
19
+ ### Internationalization
18
20
 
19
21
  Grunnmuren uses [React Aria Components](https://react-spectrum.adobe.com/react-aria/) under the hood. RAC has built in translation strings for non visible content (for accessibility reasons). It also automatically detects the language based on the browser or system language.
20
22
 
21
- To ensure that the language of the page content matches the accessibility strings you should wrap your application in a `I18nProvider`. This will override RAC's automatic locale selection.
23
+ To ensure that the language of the page content matches the accessibility strings you must wrap your application in a `GrunnmurenProvider` with a `locale` prop. This will override RAC's automatic locale selection.
22
24
 
23
- In [Next.js](https://nextjs.org/) you can do this in the root [root layout](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required).
25
+ In [Next.js](https://nextjs.org/) you can do this in the root [root layout](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required). In order to avoid making `RootLayout` a client component, you should import `GrunnmurenProvider` in a providers-file, that uses `"use client"`
24
26
 
25
- Valid locales for Norwegian are `nb-NO` or `nb`, Swedish is `sv-SE` or `sv`.
27
+ Valid locales are `nb`, `sv` or `en`. The provider defaults to `nb` if unspecified.
26
28
 
27
29
  ```js
28
- // app/layout.tsx
29
- import { I18nProvider } from '@obosbbl/grunnmuren-react';
30
+ // app/providers.tsx
31
+ 'use client'
32
+ import { GrunnmurenProvider } from '@obosbbl/grunnmuren-react';
30
33
 
34
+ export function Providers({children, locale}: { children: React.ReactNode, locale: 'nb' | 'sv' | 'en'}) {
35
+
36
+ return (
37
+ <GrunnmurenProvider locale={locale}>
38
+ {children}
39
+ </GrunnmurenProvider>
40
+ )
41
+ }
42
+ ```
43
+
44
+ ```js
45
+ // app/layout.tsx
31
46
 
32
47
  export default function RootLayout({
33
48
  children,
@@ -35,21 +50,72 @@ export default function RootLayout({
35
50
  children: React.ReactNode
36
51
  }) {
37
52
 
38
- // Either 'nb' or 'sv'
53
+ // Either 'nb', 'sv' or 'en'
39
54
  const locale = 'nb';
40
55
 
41
56
  return (
42
- <I18nProvider locale={locale}>
57
+ <Providers locale={locale}>
43
58
  <html lang={locale}>
44
59
  <body>{children}</body>
45
60
  </html>
46
- </I18nProvider>
61
+ </Providers>
47
62
  )
48
63
  }
49
64
  ```
50
65
 
51
66
  See the [RAC internationalization docs](https://react-spectrum.adobe.com/react-aria/internationalization.html) for more information.
52
67
 
68
+ ### Routing
69
+
70
+ When using compontents that include links from RAC (For example `Breadcrumbs`), the links will always treat the hrefs as external.
71
+
72
+ In order to avoid hard refreshing, you need to prop your router navigation-function
73
+ through `GrunnmurenProvider`. See the [RAC routing docs](https://react-spectrum.adobe.com/react-aria/routing.html)
74
+
75
+ In [Next.js](https://nextjs.org/) this is also done in the root [root layout](https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts#root-layout-required). In order to avoid making `RootLayout` a client component, you should import `GrunnmurenProvider` in a providers-file, that uses `"use client"`
76
+
77
+ ```js
78
+ // app/providers.tsx
79
+ 'use client'
80
+ import { GrunnmurenProvider } from '@obosbbl/grunnmuren-react';
81
+ import { useRouter } from 'next/navigation';
82
+
83
+ export function Providers({children, locale}: { children: React.ReactNode, locale: string}) {
84
+ let router = useRouter();
85
+
86
+ return (
87
+ <GrunnmurenProvider locale={locale} navigate={router.push}>
88
+ {children}
89
+ </GrunnmurenProvider>
90
+ )
91
+ }
92
+ ```
93
+
94
+ The `RootLayout` file then looks exactly like it does in the previous step:
95
+
96
+ ```js
97
+ // app/layout.tsx
98
+ import {Providers} from "./providers";
99
+
100
+ export default function RootLayout({
101
+ children,
102
+ }: {
103
+ children: React.ReactNode
104
+ }) {
105
+
106
+ // Either 'nb', 'sv' or 'en'
107
+ const locale = 'nb';
108
+
109
+ return (
110
+ <Providers locale={locale}>
111
+ <html lang={locale}>
112
+ <body>{children}</body>
113
+ </html>
114
+ </Providers>
115
+ )
116
+ }
117
+ ```
118
+
53
119
  ### Optimize bundle size by removing unused locales
54
120
 
55
121
  React Aria Components has built in support for over 30 languages, most of which will be unused in your application. To optimize your applications bundle size, it is recommended to use React Aria's build plugin to remove all the unused locales. Here is a quick example for Next.js:
@@ -76,7 +142,7 @@ module.exports = {
76
142
  optimizeLocales.webpack({
77
143
  // If you have a multitenant app, include both Norwegian and Swedish
78
144
  // If your app only serves one language, adjust accordingly
79
- locales: ['nb-NO', 'sv-SE'],
145
+ locales: ['nb', 'sv'],
80
146
  }),
81
147
  );
82
148
  return config;
package/dist/index.d.mts CHANGED
@@ -1,8 +1,21 @@
1
- import { CheckboxProps as CheckboxProps$1, CheckboxGroupProps as CheckboxGroupProps$1, ListBoxItemProps, ComboBoxProps, RadioGroupProps as RadioGroupProps$1, RadioProps as RadioProps$1, SelectProps as SelectProps$1, TextFieldProps as TextFieldProps$1, NumberFieldProps as NumberFieldProps$1 } from 'react-aria-components';
2
- export { ListBoxItemProps as ComboboxItemProps, Form, I18nProvider, ListBoxItemProps as SelectItemProps } from 'react-aria-components';
1
+ import { CheckboxProps as CheckboxProps$1, CheckboxGroupProps as CheckboxGroupProps$1, ListBoxItemProps, ComboBoxProps, RadioGroupProps as RadioGroupProps$1, RadioProps as RadioProps$1, SelectProps as SelectProps$1, TextFieldProps as TextFieldProps$1, NumberFieldProps as NumberFieldProps$1, BreadcrumbProps as BreadcrumbProps$1, BreadcrumbsProps as BreadcrumbsProps$1 } from 'react-aria-components';
2
+ export { ListBoxItemProps as ComboboxItemProps, Form, ListBoxItemProps as SelectItemProps } from 'react-aria-components';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
3
4
  import * as react from 'react';
5
+ import { HTMLProps } from 'react';
4
6
  import { VariantProps } from 'cva';
5
- import * as react_jsx_runtime from 'react/jsx-runtime';
7
+
8
+ type GrunnmurenProviderProps = {
9
+ children: React.ReactNode;
10
+ /**
11
+ * The locale to apply to the children.
12
+ * @default nb
13
+ */
14
+ locale?: 'nb' | 'sv' | 'en';
15
+ /** The router to use for navigation */
16
+ navigate?: (path: string) => void;
17
+ };
18
+ declare function GrunnmurenProvider({ children, locale, navigate, }: GrunnmurenProviderProps): react_jsx_runtime.JSX.Element;
6
19
 
7
20
  /**
8
21
  * Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
@@ -104,7 +117,7 @@ declare const _Checkbox: react.ForwardRefExoticComponent<{
104
117
  errorMessage?: React.ReactNode;
105
118
  /** Additional style properties for the element. */
106
119
  style?: react.CSSProperties | undefined;
107
- } & Omit<CheckboxProps$1, "style" | "children" | "isDisabled" | "isIndeterminate" | "isReadOnly"> & react.RefAttributes<HTMLLabelElement>>;
120
+ } & Omit<CheckboxProps$1, "children" | "style" | "isDisabled" | "isIndeterminate" | "isReadOnly"> & react.RefAttributes<HTMLLabelElement>>;
108
121
 
109
122
  type CheckboxGroupProps = {
110
123
  children: React.ReactNode;
@@ -131,7 +144,7 @@ declare const _CheckboxGroup: react.ForwardRefExoticComponent<{
131
144
  label?: React.ReactNode;
132
145
  /** Additional style properties for the element. */
133
146
  style?: react.CSSProperties | undefined;
134
- } & Omit<CheckboxGroupProps$1, "className" | "style" | "children" | "isDisabled" | "isReadOnly" | "orientation"> & react.RefAttributes<HTMLDivElement>>;
147
+ } & Omit<CheckboxGroupProps$1, "children" | "className" | "style" | "isDisabled" | "isReadOnly" | "orientation"> & react.RefAttributes<HTMLDivElement>>;
135
148
 
136
149
  declare const ListBoxItem: (props: ListBoxItemProps) => react_jsx_runtime.JSX.Element;
137
150
 
@@ -174,7 +187,7 @@ declare const _Combobox: react.ForwardRefExoticComponent<{
174
187
  placeholder?: string | undefined;
175
188
  /** Additional style properties for the element. */
176
189
  style?: react.CSSProperties | undefined;
177
- } & Omit<ComboBoxProps<object>, "className" | "style" | "children" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLInputElement>>;
190
+ } & Omit<ComboBoxProps<object>, "children" | "className" | "style" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLInputElement>>;
178
191
 
179
192
  type RadioGroupProps = {
180
193
  children: React.ReactNode;
@@ -201,7 +214,7 @@ declare const _RadioGroup: react.ForwardRefExoticComponent<{
201
214
  label?: React.ReactNode;
202
215
  /** Additional style properties for the element. */
203
216
  style?: react.CSSProperties | undefined;
204
- } & Omit<RadioGroupProps$1, "className" | "style" | "children" | "isDisabled" | "isReadOnly" | "orientation"> & react.RefAttributes<HTMLDivElement>>;
217
+ } & Omit<RadioGroupProps$1, "children" | "className" | "style" | "isDisabled" | "isReadOnly" | "orientation"> & react.RefAttributes<HTMLDivElement>>;
205
218
 
206
219
  type RadioProps = {
207
220
  children: React.ReactNode;
@@ -220,7 +233,7 @@ declare const _Radio: react.ForwardRefExoticComponent<{
220
233
  description?: React.ReactNode;
221
234
  /** Additional style properties for the element. */
222
235
  style?: react.CSSProperties | undefined;
223
- } & Omit<RadioProps$1, "style" | "children" | "isDisabled"> & react.RefAttributes<HTMLLabelElement>>;
236
+ } & Omit<RadioProps$1, "children" | "style" | "isDisabled"> & react.RefAttributes<HTMLLabelElement>>;
224
237
 
225
238
  type SelectProps<T extends object> = {
226
239
  children: React.ReactNode;
@@ -251,7 +264,7 @@ declare const _Select: react.ForwardRefExoticComponent<{
251
264
  placeholder?: string | undefined;
252
265
  /** Additional style properties for the element. */
253
266
  style?: react.CSSProperties | undefined;
254
- } & Omit<SelectProps$1<object>, "className" | "style" | "children" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLButtonElement>>;
267
+ } & Omit<SelectProps$1<object>, "children" | "className" | "style" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLButtonElement>>;
255
268
 
256
269
  type TextAreaProps = {
257
270
  /** Additional CSS className for the element. */
@@ -290,7 +303,7 @@ declare const _TextArea: react.ForwardRefExoticComponent<{
290
303
  * @default 2
291
304
  */
292
305
  rows?: number | undefined;
293
- } & Omit<TextFieldProps$1, "className" | "style" | "children" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLTextAreaElement>>;
306
+ } & Omit<TextFieldProps$1, "children" | "className" | "style" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLTextAreaElement>>;
294
307
 
295
308
  type TextFieldProps = {
296
309
  /** Additional CSS className for the element. */
@@ -341,7 +354,7 @@ declare const _TextField: react.ForwardRefExoticComponent<{
341
354
  style?: react.CSSProperties | undefined;
342
355
  /** Add a divider between the left/right addons and the input */
343
356
  withAddonDivider?: boolean | undefined;
344
- } & Omit<TextFieldProps$1, "className" | "style" | "children" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLInputElement>>;
357
+ } & Omit<TextFieldProps$1, "children" | "className" | "style" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLInputElement>>;
345
358
 
346
359
  type NumberFieldProps = {
347
360
  /** Additional CSS className for the element. */
@@ -392,6 +405,140 @@ declare const _NumberField: react.ForwardRefExoticComponent<{
392
405
  style?: react.CSSProperties | undefined;
393
406
  /** Add a divider between the left/right addons and the input */
394
407
  withAddonDivider?: boolean | undefined;
395
- } & Omit<NumberFieldProps$1, "className" | "style" | "children" | "isDisabled" | "isReadOnly" | "hideStepper"> & react.RefAttributes<HTMLInputElement>>;
408
+ } & Omit<NumberFieldProps$1, "children" | "className" | "style" | "isDisabled" | "isReadOnly" | "hideStepper"> & react.RefAttributes<HTMLInputElement>>;
409
+
410
+ declare const alertVariants: (props?: ({
411
+ variant?: "info" | "success" | "warning" | "danger" | undefined;
412
+ } & ({
413
+ class?: string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | any | {
414
+ [x: string]: any;
415
+ } | null | undefined)[] | {
416
+ [x: string]: any;
417
+ } | null | undefined)[] | {
418
+ [x: string]: any;
419
+ } | null | undefined)[] | {
420
+ [x: string]: any;
421
+ } | null | undefined)[] | {
422
+ [x: string]: any;
423
+ } | null | undefined)[] | {
424
+ [x: string]: any;
425
+ } | null | undefined)[] | {
426
+ [x: string]: any;
427
+ } | null | undefined)[] | {
428
+ [x: string]: any;
429
+ } | null | undefined)[] | {
430
+ [x: string]: any;
431
+ } | null | undefined)[] | {
432
+ [x: string]: any;
433
+ } | null | undefined)[] | {
434
+ [x: string]: any;
435
+ } | null | undefined)[] | {
436
+ [x: string]: any;
437
+ } | null | undefined;
438
+ className?: undefined;
439
+ } | {
440
+ class?: undefined;
441
+ className?: string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | any | {
442
+ [x: string]: any;
443
+ } | null | undefined)[] | {
444
+ [x: string]: any;
445
+ } | null | undefined)[] | {
446
+ [x: string]: any;
447
+ } | null | undefined)[] | {
448
+ [x: string]: any;
449
+ } | null | undefined)[] | {
450
+ [x: string]: any;
451
+ } | null | undefined)[] | {
452
+ [x: string]: any;
453
+ } | null | undefined)[] | {
454
+ [x: string]: any;
455
+ } | null | undefined)[] | {
456
+ [x: string]: any;
457
+ } | null | undefined)[] | {
458
+ [x: string]: any;
459
+ } | null | undefined)[] | {
460
+ [x: string]: any;
461
+ } | null | undefined)[] | {
462
+ [x: string]: any;
463
+ } | null | undefined)[] | {
464
+ [x: string]: any;
465
+ } | null | undefined;
466
+ })) | undefined) => string;
467
+ type Props = VariantProps<typeof alertVariants> & {
468
+ children: React.ReactNode;
469
+ /**
470
+ * The ARIA role for the alertbox.
471
+ */
472
+ role: 'alert' | 'status' | 'none';
473
+ /** Additional CSS className for the element. */
474
+ className?: string;
475
+ /**
476
+ * Controls if the alert is expandable or not
477
+ * @default false
478
+ */
479
+ isExpandable?: boolean;
480
+ /**
481
+ * Controls if the alert can be dismissed with a close button.
482
+ * @default false
483
+ */
484
+ isDismissable?: boolean;
485
+ /**
486
+ * Controls if the alert is rendered or not.
487
+ * This is used to control the open/closed state of the component; make the component "controlled".
488
+ * @default false
489
+ */
490
+ isDismissed?: boolean;
491
+ /**
492
+ * Callback that should be triggered when a dismissable alert is closed.
493
+ * This is used to control the open/closed state of the component; make the component "controlled".
494
+ */
495
+ onDismiss?: () => void;
496
+ };
497
+ declare const Alertbox: ({ children, role, className, variant, isDismissable, isDismissed, onDismiss, isExpandable, }: Props) => react_jsx_runtime.JSX.Element | undefined;
498
+
499
+ type HeadingProps = HTMLProps<HTMLHeadingElement> & {
500
+ children: React.ReactNode;
501
+ /** The level of the heading */
502
+ level: 1 | 2 | 3 | 4 | 5 | 6;
503
+ };
504
+ declare const Heading: ({ level, ...restProps }: HeadingProps) => react_jsx_runtime.JSX.Element;
505
+ type ContentProps = HTMLProps<HTMLDivElement> & {
506
+ children: React.ReactNode;
507
+ };
508
+ declare const Content: (props: ContentProps) => react_jsx_runtime.JSX.Element;
509
+ type FooterProps = HTMLProps<HTMLDivElement> & {
510
+ children: React.ReactNode;
511
+ };
512
+ declare const Footer: (props: FooterProps) => react_jsx_runtime.JSX.Element;
513
+
514
+ type BreadcrumbProps = {
515
+ /** Additional CSS className for the element. */
516
+ className?: string;
517
+ /** Additional style properties for the element. */
518
+ style?: React.CSSProperties;
519
+ /** The URL to navigate to when clicking the breadcrumb. */
520
+ href?: string;
521
+ } & Omit<BreadcrumbProps$1, 'className' | 'style'>;
522
+ declare const _Breadcrumb: react.ForwardRefExoticComponent<{
523
+ /** Additional CSS className for the element. */
524
+ className?: string | undefined;
525
+ /** Additional style properties for the element. */
526
+ style?: react.CSSProperties | undefined;
527
+ /** The URL to navigate to when clicking the breadcrumb. */
528
+ href?: string | undefined;
529
+ } & Omit<BreadcrumbProps$1, "className" | "style"> & react.RefAttributes<HTMLLIElement>>;
530
+
531
+ type BreadcrumbsProps = {
532
+ /** Additional CSS className for the element. */
533
+ className?: string;
534
+ /** Additional style properties for the element. */
535
+ style?: React.CSSProperties;
536
+ } & Omit<BreadcrumbsProps$1<BreadcrumbProps>, 'className' | 'style'>;
537
+ declare const _Breadcrumbs: react.ForwardRefExoticComponent<{
538
+ /** Additional CSS className for the element. */
539
+ className?: string | undefined;
540
+ /** Additional style properties for the element. */
541
+ style?: react.CSSProperties | undefined;
542
+ } & Omit<BreadcrumbsProps$1<BreadcrumbProps>, "className" | "style"> & react.RefAttributes<HTMLOListElement>>;
396
543
 
397
- export { _Button as Button, type ButtonProps, _Checkbox as Checkbox, _CheckboxGroup as CheckboxGroup, type CheckboxGroupProps, type CheckboxProps, _Combobox as Combobox, ListBoxItem as ComboboxItem, type ComboboxProps, _NumberField as NumberField, type NumberFieldProps, _Radio as Radio, _RadioGroup as RadioGroup, type RadioGroupProps, type RadioProps, _Select as Select, ListBoxItem as SelectItem, type SelectProps, _TextArea as TextArea, type TextAreaProps, _TextField as TextField, type TextFieldProps };
544
+ export { Alertbox, type Props as AlertboxProps, _Breadcrumb as Breadcrumb, type BreadcrumbProps, _Breadcrumbs as Breadcrumbs, type BreadcrumbsProps, _Button as Button, type ButtonProps, _Checkbox as Checkbox, _CheckboxGroup as CheckboxGroup, type CheckboxGroupProps, type CheckboxProps, _Combobox as Combobox, ListBoxItem as ComboboxItem, type ComboboxProps, Content, type ContentProps, Footer, type FooterProps, GrunnmurenProvider, type GrunnmurenProviderProps, Heading, type HeadingProps, _NumberField as NumberField, type NumberFieldProps, _Radio as Radio, _RadioGroup as RadioGroup, type RadioGroupProps, type RadioProps, _Select as Select, ListBoxItem as SelectItem, type SelectProps, _TextArea as TextArea, type TextAreaProps, _TextField as TextField, type TextFieldProps };
package/dist/index.mjs CHANGED
@@ -1,10 +1,156 @@
1
- import { Text, CheckboxContext, Checkbox as Checkbox$1, Label as Label$1, CheckboxGroup as CheckboxGroup$1, FieldError, ListBoxItem as ListBoxItem$1, ListBox as ListBox$1, ComboBox, Group, Input, Button, 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 } from 'react-aria-components';
2
- export { Form, I18nProvider } from 'react-aria-components';
3
- export { _ as Button } from './Button-client-XmGlKEk4.js';
1
+ 'use client';
2
+ import { I18nProvider, RouterProvider, Text, CheckboxContext, Checkbox as Checkbox$1, Label as Label$1, CheckboxGroup as CheckboxGroup$1, FieldError, ListBoxItem as ListBoxItem$1, ListBox as ListBox$1, ComboBox, Group, Input, Button as Button$1, 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, useLocale, Breadcrumbs as Breadcrumbs$1, Breadcrumb as Breadcrumb$1, Link } from 'react-aria-components';
3
+ export { Form } from 'react-aria-components';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
- import { forwardRef, useId } from 'react';
6
- import { cx, cva, compose } from 'cva';
7
- import { Check, LoadingSpinner, ChevronDown } from '@obosbbl/grunnmuren-icons-react';
5
+ import { useLayoutEffect, forwardRef, useState, useRef, useId, Children } from 'react';
6
+ import { cva, cx, compose } from 'cva';
7
+ import { LoadingSpinner, Check, ChevronDown, Close, InfoCircle, CheckCircle, Warning, CloseCircle, ChevronRight } from '@obosbbl/grunnmuren-icons-react';
8
+ import { mergeRefs } from '@react-aria/utils';
9
+
10
+ function GrunnmurenProvider({ children, locale = 'nb', navigate }) {
11
+ return /*#__PURE__*/ jsx(I18nProvider, {
12
+ locale: locale,
13
+ children: navigate ? /*#__PURE__*/ jsx(RouterProvider, {
14
+ navigate: navigate,
15
+ children: children
16
+ }) : children
17
+ });
18
+ }
19
+
20
+ const canUseDOM = ()=>{
21
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
22
+ };
23
+ const useClientLayoutEffect = canUseDOM() ? useLayoutEffect : ()=>{};
24
+
25
+ /**
26
+ * Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
27
+ */ const buttonVariants = cva({
28
+ base: [
29
+ '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'
30
+ ],
31
+ variants: {
32
+ /**
33
+ * The variant of the button
34
+ * @default primary
35
+ */ variant: {
36
+ primary: 'no-underline',
37
+ // 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
38
+ secondary: 'no-underline shadow-[inset_0_0_0_2px]',
39
+ tertiary: 'underline hover:no-underline'
40
+ },
41
+ /**
42
+ * Adjusts the color of the button for usage on different backgrounds.
43
+ * @default green
44
+ */ color: {
45
+ green: 'focus-visible:ring-black',
46
+ mint: 'focus-visible:ring-mint focus-visible:ring-offset-green-dark',
47
+ white: 'focus-visible:ring-white focus-visible:ring-offset-blue'
48
+ },
49
+ /**
50
+ * When the button is without text, but with a single icon.
51
+ * @default false
52
+ */ isIconOnly: {
53
+ true: 'p-2 [&>svg]:h-7 [&>svg]:w-7',
54
+ false: 'gap-2.5 px-4 py-2'
55
+ }
56
+ },
57
+ compoundVariants: [
58
+ {
59
+ color: 'green',
60
+ variant: 'primary',
61
+ // Darken bg by 20% on hover. The color is manually crafted
62
+ className: 'bg-green text-white hover:bg-green-dark active:bg-[#007352]'
63
+ },
64
+ {
65
+ color: 'green',
66
+ variant: 'secondary',
67
+ className: 'bg-white text-black shadow-green hover:bg-green hover:text-white active:bg-green'
68
+ },
69
+ {
70
+ color: 'mint',
71
+ variant: 'primary',
72
+ // Darken bg by 20% on hover. The color is manually crafted
73
+ className: 'active:[#9ddac6] bg-mint text-black hover:bg-[#8dd4bd]'
74
+ },
75
+ {
76
+ color: 'mint',
77
+ variant: 'secondary',
78
+ className: 'text-mint shadow-mint hover:bg-mint hover:text-black'
79
+ },
80
+ {
81
+ color: 'mint',
82
+ variant: 'tertiary',
83
+ className: 'text-mint'
84
+ },
85
+ {
86
+ color: 'white',
87
+ variant: 'primary',
88
+ className: 'bg-white text-black hover:bg-sky active:bg-sky-light'
89
+ },
90
+ {
91
+ color: 'white',
92
+ variant: 'secondary',
93
+ className: 'text-white shadow-white hover:bg-white hover:text-black'
94
+ },
95
+ {
96
+ color: 'white',
97
+ variant: 'tertiary',
98
+ className: 'text-white'
99
+ }
100
+ ],
101
+ defaultVariants: {
102
+ variant: 'primary',
103
+ color: 'green',
104
+ isIconOnly: false
105
+ }
106
+ });
107
+ function Button(props, forwardedRef) {
108
+ const { children, className, color, isIconOnly, isLoading, variant, style, ...restProps } = props;
109
+ const [widthOverride, setWidthOverride] = useState();
110
+ const ownRef = useRef(null);
111
+ const ref = mergeRefs(ownRef, forwardedRef);
112
+ useClientLayoutEffect(()=>{
113
+ if (isLoading) {
114
+ const requestID = window.requestAnimationFrame(()=>{
115
+ setWidthOverride(ownRef.current?.getBoundingClientRect()?.width);
116
+ });
117
+ return ()=>{
118
+ setWidthOverride(undefined);
119
+ cancelAnimationFrame(requestID);
120
+ };
121
+ }
122
+ }, [
123
+ isLoading,
124
+ children
125
+ ]);
126
+ let Component = 'a';
127
+ if (props.href == null) {
128
+ // 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
129
+ Component = 'button';
130
+ restProps.type ??= 'button';
131
+ }
132
+ 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
133
+ /*#__PURE__*/ jsx(Component, {
134
+ "aria-busy": isLoading ? true : undefined,
135
+ className: buttonVariants({
136
+ className,
137
+ color,
138
+ isIconOnly,
139
+ variant
140
+ }),
141
+ ref: ref,
142
+ style: {
143
+ ...style,
144
+ width: widthOverride
145
+ },
146
+ ...restProps,
147
+ children: widthOverride ? // remove margin for icon alignment
148
+ /*#__PURE__*/ jsx(LoadingSpinner, {
149
+ className: "!m-0 mx-auto animate-spin"
150
+ }) : children
151
+ }));
152
+ }
153
+ const _Button = /*#__PURE__*/ forwardRef(Button);
8
154
 
9
155
  const formField = cx('group flex flex-col gap-2');
10
156
  const formFieldError = cx('w-fit rounded-sm bg-red-light px-2 py-1 text-sm leading-6 text-red');
@@ -235,7 +381,7 @@ function Combobox(props, ref) {
235
381
  }),
236
382
  ref: ref
237
383
  }),
238
- /*#__PURE__*/ jsx(Button, {
384
+ /*#__PURE__*/ jsx(Button$1, {
239
385
  children: isLoading ? /*#__PURE__*/ jsx(LoadingSpinner, {
240
386
  className: "animate-spin"
241
387
  }) : /*#__PURE__*/ jsx(ChevronDown, {
@@ -345,7 +491,7 @@ function Select(props, ref) {
345
491
  description && /*#__PURE__*/ jsx(Description, {
346
492
  children: description
347
493
  }),
348
- /*#__PURE__*/ jsxs(Button, {
494
+ /*#__PURE__*/ jsxs(Button$1, {
349
495
  className: cx(input({
350
496
  focusModifier: 'visible'
351
497
  }), // How to reuse placeholder text?
@@ -507,4 +653,163 @@ function NumberField(props, ref) {
507
653
  }
508
654
  const _NumberField = /*#__PURE__*/ forwardRef(NumberField);
509
655
 
510
- export { _Checkbox as Checkbox, _CheckboxGroup as CheckboxGroup, _Combobox as Combobox, ListBoxItem as ComboboxItem, _NumberField as NumberField, _Radio as Radio, _RadioGroup as RadioGroup, _Select as Select, ListBoxItem as SelectItem, _TextArea as TextArea, _TextField as TextField };
656
+ // TODO: add new icons
657
+ const iconMap = {
658
+ info: InfoCircle,
659
+ success: CheckCircle,
660
+ warning: Warning,
661
+ danger: CloseCircle
662
+ };
663
+ const alertVariants = cva({
664
+ base: [
665
+ 'grid grid-cols-[auto_1fr_auto] items-center gap-2 rounded-md border-2 px-3 py-2',
666
+ // Heading styles:
667
+ '[&_[data-slot="heading"]]:text-base [&_[data-slot="heading"]]:font-medium [&_[data-slot="heading"]]:leading-7',
668
+ // Content styles:
669
+ '[&:has([data-slot="heading"])_[data-slot="content"]]:col-span-full [&_[data-slot="content"]]:text-sm [&_[data-slot="content"]]:leading-6',
670
+ // Footer styles:
671
+ '[&_[data-slot="footer"]]:col-span-full [&_[data-slot="footer"]]:text-xs [&_[data-slot="footer"]]:font-light [&_[data-slot="footer"]]:leading-6'
672
+ ],
673
+ variants: {
674
+ /**
675
+ * The variant of the alert
676
+ * @default info
677
+ */ variant: {
678
+ info: 'border-[#1A7FA7] bg-sky-light',
679
+ success: 'border-[#0F9B6E] bg-mint-light',
680
+ warning: 'border-[#C57C13] bg-[#FFF2DE]',
681
+ danger: 'border-[#C0385D] bg-red-light'
682
+ }
683
+ },
684
+ defaultVariants: {
685
+ variant: 'info'
686
+ }
687
+ });
688
+ const translations = {
689
+ close: {
690
+ nb: 'Lukk',
691
+ sv: 'Stäng',
692
+ en: 'Close'
693
+ },
694
+ showMore: {
695
+ nb: 'Les mer',
696
+ sv: 'Läs mer',
697
+ en: 'Read more'
698
+ },
699
+ showLess: {
700
+ nb: 'Vis mindre',
701
+ sv: 'Dölj',
702
+ en: 'Show less'
703
+ }
704
+ };
705
+ const Alertbox = ({ children, role, className, variant = 'info', isDismissable = false, isDismissed, onDismiss, isExpandable })=>{
706
+ const Icon = iconMap[variant];
707
+ const { locale } = useLocale();
708
+ const id = useId();
709
+ const [isExpanded, setIsExpanded] = useState(false);
710
+ const isCollapsed = isExpandable && !isExpanded;
711
+ const [isUncontrolledVisible, setIsUncontrolledVisible] = useState(true);
712
+ const isVisible = isDismissed !== undefined ? !isDismissed : isUncontrolledVisible;
713
+ if (!isVisible) return;
714
+ const close = ()=>{
715
+ setIsUncontrolledVisible(false);
716
+ if (onDismiss) onDismiss();
717
+ };
718
+ const isInDevMode = process.env.NODE_ENV !== 'production';
719
+ if (isInDevMode && onDismiss && !isDismissable) {
720
+ console.warn('Passing an `onDismiss` callback without setting the `isDismissable` prop to `true` will not have any effect.');
721
+ }
722
+ if (isInDevMode && !children) {
723
+ console.error('`No children was passed to the <AlertBox/>` component.');
724
+ return;
725
+ }
726
+ const [firstChild, ...restChildren] = Children.toArray(children);
727
+ const lastChild = restChildren.pop();
728
+ return /*#__PURE__*/ jsxs("div", {
729
+ className: alertVariants({
730
+ className,
731
+ variant
732
+ }),
733
+ // The role prop is required to force consumers to consider and choose the appropriate alertbox role.
734
+ // role="none" will not have any effect on a div, so it can be omitted.
735
+ role: role === 'none' ? undefined : role,
736
+ children: [
737
+ /*#__PURE__*/ jsx(Icon, {}),
738
+ firstChild,
739
+ isDismissable && /*#__PURE__*/ jsx("button", {
740
+ className: cx('-m-2 grid h-11 w-11 place-items-center rounded-xl', 'focus:outline-none focus:-outline-offset-8 focus:outline-black'),
741
+ onClick: close,
742
+ "aria-label": translations.close[locale],
743
+ children: /*#__PURE__*/ jsx(Close, {})
744
+ }),
745
+ isExpandable && /*#__PURE__*/ jsxs("button", {
746
+ 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:
747
+ 'outline-none after:absolute after:bottom-3 after:left-0 after:right-0 after:h-0 after:bg-transparent after:transition-all after:duration-200', 'focus:after:h-[1px] focus:after:bg-black'),
748
+ onClick: ()=>setIsExpanded((prevState)=>!prevState),
749
+ "aria-expanded": isExpanded,
750
+ "aria-controls": id,
751
+ children: [
752
+ isExpanded ? translations.showLess[locale] : translations.showMore[locale],
753
+ /*#__PURE__*/ jsx(ChevronDown, {
754
+ className: cx('transition-transform duration-150 motion-reduce:transition-none', isExpanded && 'rotate-180')
755
+ })
756
+ ]
757
+ }),
758
+ !isCollapsed && restChildren.length > 0 && /*#__PURE__*/ jsx("div", {
759
+ className: "col-span-full grid gap-y-4",
760
+ id: id,
761
+ children: restChildren
762
+ }),
763
+ lastChild
764
+ ]
765
+ });
766
+ };
767
+
768
+ const Heading = ({ level, ...restProps })=>{
769
+ const Heading = `h${level}`;
770
+ return /*#__PURE__*/ jsx(Heading, {
771
+ ...restProps,
772
+ "data-slot": "heading"
773
+ });
774
+ };
775
+ const Content = (props)=>/*#__PURE__*/ jsx("div", {
776
+ ...props,
777
+ "data-slot": "content"
778
+ });
779
+ const Footer = (props)=>/*#__PURE__*/ jsx("div", {
780
+ ...props,
781
+ "data-slot": "footer"
782
+ });
783
+
784
+ function Breadcrumbs(props, ref) {
785
+ const { className, children, ...restProps } = props;
786
+ return /*#__PURE__*/ jsx(Breadcrumbs$1, {
787
+ ...restProps,
788
+ className: cx(className, 'flex flex-wrap text-sm leading-6'),
789
+ ref: ref,
790
+ children: children
791
+ });
792
+ }
793
+ const _Breadcrumbs = /*#__PURE__*/ forwardRef(Breadcrumbs);
794
+
795
+ function Breadcrumb(props, ref) {
796
+ const { className, children, href, ...restProps } = props;
797
+ return /*#__PURE__*/ jsxs(Breadcrumb$1, {
798
+ className: cx(className, 'group flex items-center'),
799
+ ...restProps,
800
+ ref: ref,
801
+ children: [
802
+ href ? /*#__PURE__*/ jsx(Link, {
803
+ href: href,
804
+ className: "group-last:no-underline",
805
+ children: children
806
+ }) : children,
807
+ /*#__PURE__*/ jsx(ChevronRight, {
808
+ className: "px-1 group-last:hidden"
809
+ })
810
+ ]
811
+ });
812
+ }
813
+ const _Breadcrumb = /*#__PURE__*/ forwardRef(Breadcrumb);
814
+
815
+ export { Alertbox, _Breadcrumb as Breadcrumb, _Breadcrumbs as Breadcrumbs, _Button as Button, _Checkbox as Checkbox, _CheckboxGroup as CheckboxGroup, _Combobox as Combobox, ListBoxItem as ComboboxItem, Content, Footer, GrunnmurenProvider, Heading, _NumberField as NumberField, _Radio as Radio, _RadioGroup as RadioGroup, _Select as Select, ListBoxItem as SelectItem, _TextArea as TextArea, _TextField as TextField };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obosbbl/grunnmuren-react",
3
- "version": "2.0.0-canary.12",
3
+ "version": "2.0.0-canary.14",
4
4
  "description": "Grunnmuren components in React",
5
5
  "repository": {
6
6
  "url": "https://github.com/code-obos/grunnmuren"
@@ -19,9 +19,10 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "@obosbbl/grunnmuren-icons-react": "^2.0.0-canary.1",
22
- "@react-aria/utils": "^3.23.0",
22
+ "@react-aria/utils": "^3.23.2",
23
+ "@types/node": "^20.11.19",
23
24
  "cva": "1.0.0-beta.1",
24
- "react-aria-components": "^1.0.0"
25
+ "react-aria-components": "^1.1.1"
25
26
  },
26
27
  "peerDependencies": {
27
28
  "react": "^18"
@@ -1,143 +0,0 @@
1
- 'use client';
2
- import { jsx } from 'react/jsx-runtime';
3
- import { useLayoutEffect, forwardRef, useState, useRef } from 'react';
4
- import { cva } from 'cva';
5
- import { LoadingSpinner } from '@obosbbl/grunnmuren-icons-react';
6
- import { mergeRefs } from '@react-aria/utils';
7
-
8
- const canUseDOM = ()=>{
9
- return typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
10
- };
11
- const useClientLayoutEffect = canUseDOM() ? useLayoutEffect : ()=>{};
12
-
13
- /**
14
- * Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
15
- */ const buttonVariants = cva({
16
- base: [
17
- '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'
18
- ],
19
- variants: {
20
- /**
21
- * The variant of the button
22
- * @default primary
23
- */ variant: {
24
- primary: 'no-underline',
25
- // 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
26
- secondary: 'no-underline shadow-[inset_0_0_0_2px]',
27
- tertiary: 'underline hover:no-underline'
28
- },
29
- /**
30
- * Adjusts the color of the button for usage on different backgrounds.
31
- * @default green
32
- */ color: {
33
- green: 'focus-visible:ring-black',
34
- mint: 'focus-visible:ring-mint focus-visible:ring-offset-green-dark',
35
- white: 'focus-visible:ring-white focus-visible:ring-offset-blue'
36
- },
37
- /**
38
- * When the button is without text, but with a single icon.
39
- * @default false
40
- */ isIconOnly: {
41
- true: 'p-2 [&>svg]:h-7 [&>svg]:w-7',
42
- false: 'gap-2.5 px-4 py-2'
43
- }
44
- },
45
- compoundVariants: [
46
- {
47
- color: 'green',
48
- variant: 'primary',
49
- // Darken bg by 20% on hover. The color is manually crafted
50
- className: 'bg-green text-white hover:bg-green-dark active:bg-[#007352]'
51
- },
52
- {
53
- color: 'green',
54
- variant: 'secondary',
55
- className: 'bg-white text-black shadow-green hover:bg-green hover:text-white active:bg-green'
56
- },
57
- {
58
- color: 'mint',
59
- variant: 'primary',
60
- // Darken bg by 20% on hover. The color is manually crafted
61
- className: 'active:[#9ddac6] bg-mint text-black hover:bg-[#8dd4bd]'
62
- },
63
- {
64
- color: 'mint',
65
- variant: 'secondary',
66
- className: 'text-mint shadow-mint hover:bg-mint hover:text-black'
67
- },
68
- {
69
- color: 'mint',
70
- variant: 'tertiary',
71
- className: 'text-mint'
72
- },
73
- {
74
- color: 'white',
75
- variant: 'primary',
76
- className: 'bg-white text-black hover:bg-sky active:bg-sky-light'
77
- },
78
- {
79
- color: 'white',
80
- variant: 'secondary',
81
- className: 'text-white shadow-white hover:bg-white hover:text-black'
82
- },
83
- {
84
- color: 'white',
85
- variant: 'tertiary',
86
- className: 'text-white'
87
- }
88
- ],
89
- defaultVariants: {
90
- variant: 'primary',
91
- color: 'green',
92
- isIconOnly: false
93
- }
94
- });
95
- function Button(props, forwardedRef) {
96
- const { children, className, color, isIconOnly, isLoading, variant, style, ...restProps } = props;
97
- const [widthOverride, setWidthOverride] = useState();
98
- const ownRef = useRef(null);
99
- const ref = mergeRefs(ownRef, forwardedRef);
100
- useClientLayoutEffect(()=>{
101
- if (isLoading) {
102
- const requestID = window.requestAnimationFrame(()=>{
103
- setWidthOverride(ownRef.current?.getBoundingClientRect()?.width);
104
- });
105
- return ()=>{
106
- setWidthOverride(undefined);
107
- cancelAnimationFrame(requestID);
108
- };
109
- }
110
- }, [
111
- isLoading,
112
- children
113
- ]);
114
- let Component = 'a';
115
- if (props.href == null) {
116
- // 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
117
- Component = 'button';
118
- restProps.type ??= 'button';
119
- }
120
- 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
121
- /*#__PURE__*/ jsx(Component, {
122
- "aria-busy": isLoading ? true : undefined,
123
- className: buttonVariants({
124
- className,
125
- color,
126
- isIconOnly,
127
- variant
128
- }),
129
- ref: ref,
130
- style: {
131
- ...style,
132
- width: widthOverride
133
- },
134
- ...restProps,
135
- children: widthOverride ? // remove margin for icon alignment
136
- /*#__PURE__*/ jsx(LoadingSpinner, {
137
- className: "!m-0 mx-auto animate-spin"
138
- }) : children
139
- }));
140
- }
141
- const _Button = /*#__PURE__*/ forwardRef(Button);
142
-
143
- export { _Button as _ };