@obosbbl/grunnmuren-react 2.0.0-canary.13 → 2.0.0-canary.15

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,9 +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';
4
5
  import { HTMLProps } from 'react';
5
6
  import { VariantProps } from 'cva';
6
- 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;
7
19
 
8
20
  /**
9
21
  * Figma: https://www.figma.com/file/9OvSg0ZXI5E1eQYi7AWiWn/Grunnmuren-2.0-%E2%94%82-Designsystem?node-id=30%3A2574&mode=dev
@@ -105,7 +117,7 @@ declare const _Checkbox: react.ForwardRefExoticComponent<{
105
117
  errorMessage?: React.ReactNode;
106
118
  /** Additional style properties for the element. */
107
119
  style?: react.CSSProperties | undefined;
108
- } & Omit<CheckboxProps$1, "style" | "children" | "isDisabled" | "isIndeterminate" | "isReadOnly"> & react.RefAttributes<HTMLLabelElement>>;
120
+ } & Omit<CheckboxProps$1, "children" | "style" | "isDisabled" | "isIndeterminate" | "isReadOnly"> & react.RefAttributes<HTMLLabelElement>>;
109
121
 
110
122
  type CheckboxGroupProps = {
111
123
  children: React.ReactNode;
@@ -132,7 +144,7 @@ declare const _CheckboxGroup: react.ForwardRefExoticComponent<{
132
144
  label?: React.ReactNode;
133
145
  /** Additional style properties for the element. */
134
146
  style?: react.CSSProperties | undefined;
135
- } & 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>>;
136
148
 
137
149
  declare const ListBoxItem: (props: ListBoxItemProps) => react_jsx_runtime.JSX.Element;
138
150
 
@@ -175,7 +187,7 @@ declare const _Combobox: react.ForwardRefExoticComponent<{
175
187
  placeholder?: string | undefined;
176
188
  /** Additional style properties for the element. */
177
189
  style?: react.CSSProperties | undefined;
178
- } & Omit<ComboBoxProps<object>, "className" | "style" | "children" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLInputElement>>;
190
+ } & Omit<ComboBoxProps<object>, "children" | "className" | "style" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLInputElement>>;
179
191
 
180
192
  type RadioGroupProps = {
181
193
  children: React.ReactNode;
@@ -202,7 +214,7 @@ declare const _RadioGroup: react.ForwardRefExoticComponent<{
202
214
  label?: React.ReactNode;
203
215
  /** Additional style properties for the element. */
204
216
  style?: react.CSSProperties | undefined;
205
- } & 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>>;
206
218
 
207
219
  type RadioProps = {
208
220
  children: React.ReactNode;
@@ -221,7 +233,7 @@ declare const _Radio: react.ForwardRefExoticComponent<{
221
233
  description?: React.ReactNode;
222
234
  /** Additional style properties for the element. */
223
235
  style?: react.CSSProperties | undefined;
224
- } & Omit<RadioProps$1, "style" | "children" | "isDisabled"> & react.RefAttributes<HTMLLabelElement>>;
236
+ } & Omit<RadioProps$1, "children" | "style" | "isDisabled"> & react.RefAttributes<HTMLLabelElement>>;
225
237
 
226
238
  type SelectProps<T extends object> = {
227
239
  children: React.ReactNode;
@@ -252,7 +264,7 @@ declare const _Select: react.ForwardRefExoticComponent<{
252
264
  placeholder?: string | undefined;
253
265
  /** Additional style properties for the element. */
254
266
  style?: react.CSSProperties | undefined;
255
- } & 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>>;
256
268
 
257
269
  type TextAreaProps = {
258
270
  /** Additional CSS className for the element. */
@@ -291,7 +303,7 @@ declare const _TextArea: react.ForwardRefExoticComponent<{
291
303
  * @default 2
292
304
  */
293
305
  rows?: number | undefined;
294
- } & Omit<TextFieldProps$1, "className" | "style" | "children" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLTextAreaElement>>;
306
+ } & Omit<TextFieldProps$1, "children" | "className" | "style" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLTextAreaElement>>;
295
307
 
296
308
  type TextFieldProps = {
297
309
  /** Additional CSS className for the element. */
@@ -342,7 +354,7 @@ declare const _TextField: react.ForwardRefExoticComponent<{
342
354
  style?: react.CSSProperties | undefined;
343
355
  /** Add a divider between the left/right addons and the input */
344
356
  withAddonDivider?: boolean | undefined;
345
- } & Omit<TextFieldProps$1, "className" | "style" | "children" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLInputElement>>;
357
+ } & Omit<TextFieldProps$1, "children" | "className" | "style" | "isDisabled" | "isReadOnly"> & react.RefAttributes<HTMLInputElement>>;
346
358
 
347
359
  type NumberFieldProps = {
348
360
  /** Additional CSS className for the element. */
@@ -393,7 +405,7 @@ declare const _NumberField: react.ForwardRefExoticComponent<{
393
405
  style?: react.CSSProperties | undefined;
394
406
  /** Add a divider between the left/right addons and the input */
395
407
  withAddonDivider?: boolean | undefined;
396
- } & 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>>;
397
409
 
398
410
  declare const alertVariants: (props?: ({
399
411
  variant?: "info" | "success" | "warning" | "danger" | undefined;
@@ -499,4 +511,34 @@ type FooterProps = HTMLProps<HTMLDivElement> & {
499
511
  };
500
512
  declare const Footer: (props: FooterProps) => react_jsx_runtime.JSX.Element;
501
513
 
502
- export { Alertbox, type Props as AlertboxProps, _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, 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 };
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>>;
543
+
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,11 +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';
8
- export { A as Alertbox } from './Alertbox-client-J1P2mzVC.js';
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);
9
154
 
10
155
  const formField = cx('group flex flex-col gap-2');
11
156
  const formFieldError = cx('w-fit rounded-sm bg-red-light px-2 py-1 text-sm leading-6 text-red');
@@ -34,7 +179,7 @@ const input = cva({
34
179
  }
35
180
  });
36
181
  const inputGroup = cx([
37
- 'inline-flex items-center gap-3 overflow-hidden rounded-md px-3 text-sm ring-1 ring-black focus-within:ring-2',
182
+ 'inline-flex items-center gap-3 overflow-hidden rounded-md bg-white px-3 text-sm ring-1 ring-black focus-within:ring-2',
38
183
  'group-data-[invalid]:ring-2 group-data-[invalid]:ring-red group-data-[invalid]:focus-within:ring',
39
184
  // Make sure icons are the correct size
40
185
  '[&_svg]:text-base'
@@ -236,7 +381,7 @@ function Combobox(props, ref) {
236
381
  }),
237
382
  ref: ref
238
383
  }),
239
- /*#__PURE__*/ jsx(Button, {
384
+ /*#__PURE__*/ jsx(Button$1, {
240
385
  children: isLoading ? /*#__PURE__*/ jsx(LoadingSpinner, {
241
386
  className: "animate-spin"
242
387
  }) : /*#__PURE__*/ jsx(ChevronDown, {
@@ -346,7 +491,7 @@ function Select(props, ref) {
346
491
  description && /*#__PURE__*/ jsx(Description, {
347
492
  children: description
348
493
  }),
349
- /*#__PURE__*/ jsxs(Button, {
494
+ /*#__PURE__*/ jsxs(Button$1, {
350
495
  className: cx(input({
351
496
  focusModifier: 'visible'
352
497
  }), // How to reuse placeholder text?
@@ -508,6 +653,118 @@ function NumberField(props, ref) {
508
653
  }
509
654
  const _NumberField = /*#__PURE__*/ forwardRef(NumberField);
510
655
 
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
+
511
768
  const Heading = ({ level, ...restProps })=>{
512
769
  const Heading = `h${level}`;
513
770
  return /*#__PURE__*/ jsx(Heading, {
@@ -524,4 +781,35 @@ const Footer = (props)=>/*#__PURE__*/ jsx("div", {
524
781
  "data-slot": "footer"
525
782
  });
526
783
 
527
- export { _Checkbox as Checkbox, _CheckboxGroup as CheckboxGroup, _Combobox as Combobox, ListBoxItem as ComboboxItem, Content, Footer, Heading, _NumberField as NumberField, _Radio as Radio, _RadioGroup as RadioGroup, _Select as Select, ListBoxItem as SelectItem, _TextArea as TextArea, _TextField as TextField };
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.13",
3
+ "version": "2.0.0-canary.15",
4
4
  "description": "Grunnmuren components in React",
5
5
  "repository": {
6
6
  "url": "https://github.com/code-obos/grunnmuren"
@@ -19,10 +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
23
  "@types/node": "^20.11.19",
24
24
  "cva": "1.0.0-beta.1",
25
- "react-aria-components": "^1.0.0"
25
+ "react-aria-components": "^1.1.1"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": "^18"
@@ -1,120 +0,0 @@
1
- 'use client';
2
- import { jsxs, jsx } from 'react/jsx-runtime';
3
- import { useId, useState, Children } from 'react';
4
- import { cva, cx } from 'cva';
5
- import { useLocale } from 'react-aria-components';
6
- import { Close, ChevronDown, InfoCircle, CheckCircle, Warning, CloseCircle } from '@obosbbl/grunnmuren-icons-react';
7
-
8
- // TODO: add new icons
9
- const iconMap = {
10
- info: InfoCircle,
11
- success: CheckCircle,
12
- warning: Warning,
13
- danger: CloseCircle
14
- };
15
- const alertVariants = cva({
16
- base: [
17
- 'grid grid-cols-[auto_1fr_auto] items-center gap-2 rounded-md border-2 px-3 py-2',
18
- // Heading styles:
19
- '[&_[data-slot="heading"]]:text-base [&_[data-slot="heading"]]:font-medium [&_[data-slot="heading"]]:leading-7',
20
- // Content styles:
21
- '[&:has([data-slot="heading"])_[data-slot="content"]]:col-span-full [&_[data-slot="content"]]:text-sm [&_[data-slot="content"]]:leading-6',
22
- // Footer styles:
23
- '[&_[data-slot="footer"]]:col-span-full [&_[data-slot="footer"]]:text-xs [&_[data-slot="footer"]]:font-light [&_[data-slot="footer"]]:leading-6'
24
- ],
25
- variants: {
26
- /**
27
- * The variant of the alert
28
- * @default info
29
- */ variant: {
30
- info: 'border-[#1A7FA7] bg-sky-light',
31
- success: 'border-[#0F9B6E] bg-mint-light',
32
- warning: 'border-[#C57C13] bg-[#FFF2DE]',
33
- danger: 'border-[#C0385D] bg-red-light'
34
- }
35
- },
36
- defaultVariants: {
37
- variant: 'info'
38
- }
39
- });
40
- const translations = {
41
- close: {
42
- nb: 'Lukk',
43
- sv: 'Stäng',
44
- en: 'Close'
45
- },
46
- showMore: {
47
- nb: 'Les mer',
48
- sv: 'Läs mer',
49
- en: 'Read more'
50
- },
51
- showLess: {
52
- nb: 'Vis mindre',
53
- sv: 'Dölj',
54
- en: 'Show less'
55
- }
56
- };
57
- const Alertbox = ({ children, role, className, variant = 'info', isDismissable = false, isDismissed, onDismiss, isExpandable })=>{
58
- const Icon = iconMap[variant];
59
- const { locale } = useLocale();
60
- const id = useId();
61
- const [isExpanded, setIsExpanded] = useState(false);
62
- const isCollapsed = isExpandable && !isExpanded;
63
- const [isUncontrolledVisible, setIsUncontrolledVisible] = useState(true);
64
- const isVisible = isDismissed !== undefined ? !isDismissed : isUncontrolledVisible;
65
- if (!isVisible) return;
66
- const close = ()=>{
67
- setIsUncontrolledVisible(false);
68
- if (onDismiss) onDismiss();
69
- };
70
- const isInDevMode = process.env.NODE_ENV !== 'production';
71
- if (isInDevMode && onDismiss && !isDismissable) {
72
- console.warn('Passing an `onDismiss` callback without setting the `isDismissable` prop to `true` will not have any effect.');
73
- }
74
- if (isInDevMode && !children) {
75
- console.error('`No children was passed to the <AlertBox/>` component.');
76
- return;
77
- }
78
- const [firstChild, ...restChildren] = Children.toArray(children);
79
- const lastChild = restChildren.pop();
80
- return /*#__PURE__*/ jsxs("div", {
81
- className: alertVariants({
82
- className,
83
- variant
84
- }),
85
- // The role prop is required to force consumers to consider and choose the appropriate alertbox role.
86
- // role="none" will not have any effect on a div, so it can be omitted.
87
- role: role === 'none' ? undefined : role,
88
- children: [
89
- /*#__PURE__*/ jsx(Icon, {}),
90
- firstChild,
91
- isDismissable && /*#__PURE__*/ jsx("button", {
92
- className: cx('-m-2 grid h-11 w-11 place-items-center rounded-xl', 'focus:outline-none focus:-outline-offset-8 focus:outline-black'),
93
- onClick: close,
94
- "aria-label": translations.close[locale],
95
- children: /*#__PURE__*/ jsx(Close, {})
96
- }),
97
- isExpandable && /*#__PURE__*/ jsxs("button", {
98
- 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:
99
- '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'),
100
- onClick: ()=>setIsExpanded((prevState)=>!prevState),
101
- "aria-expanded": isExpanded,
102
- "aria-controls": id,
103
- children: [
104
- isExpanded ? translations.showLess[locale] : translations.showMore[locale],
105
- /*#__PURE__*/ jsx(ChevronDown, {
106
- className: cx('transition-transform duration-150 motion-reduce:transition-none', isExpanded && 'rotate-180')
107
- })
108
- ]
109
- }),
110
- !isCollapsed && restChildren.length > 0 && /*#__PURE__*/ jsx("div", {
111
- className: "col-span-full grid gap-y-4",
112
- id: id,
113
- children: restChildren
114
- }),
115
- lastChild
116
- ]
117
- });
118
- };
119
-
120
- export { Alertbox as A };
@@ -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 _ };