@obosbbl/grunnmuren-react 3.7.0 → 3.8.0
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 +17 -5
- package/dist/__stories__/form-validation.stories.cjs +2 -0
- package/dist/__stories__/form-validation.stories.js +2 -0
- package/dist/__stories__/layout.stories.cjs +9 -3
- package/dist/__stories__/layout.stories.js +9 -3
- package/dist/index.d.mts +22 -3
- package/dist/index.mjs +185 -11
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
# @obosbbl/grunnmuren-react
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@obosbbl/grunnmuren-react)
|
|
4
4
|
|
|
5
5
|
Grunnmuren React components.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
+
`@obosbbl/grunnmuren-react` declares `@obosbbl/grunnmuren-tailwind` as a peer dependency — the components rely on Tailwind tokens and utilities defined in that package. Install both:
|
|
10
|
+
|
|
9
11
|
```sh
|
|
10
12
|
# npm
|
|
11
|
-
npm install @obosbbl/grunnmuren-react@
|
|
13
|
+
npm install @obosbbl/grunnmuren-react @obosbbl/grunnmuren-tailwind
|
|
12
14
|
|
|
13
15
|
# pnpm
|
|
14
|
-
pnpm add @obosbbl/grunnmuren-react@
|
|
16
|
+
pnpm add @obosbbl/grunnmuren-react @obosbbl/grunnmuren-tailwind
|
|
15
17
|
```
|
|
16
18
|
|
|
19
|
+
Then import the Tailwind preset in your global stylesheet. The preset already does `@import 'tailwindcss';` internally, so you should **not** import Tailwind separately:
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
/* globals.css */
|
|
23
|
+
@import '@obosbbl/grunnmuren-tailwind';
|
|
24
|
+
|
|
25
|
+
/* Register the React package as a Tailwind source so the utilities used by the components are emitted. */
|
|
26
|
+
@source "../../node_modules/@obosbbl/grunnmuren-react/dist";
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
See [`@obosbbl/grunnmuren-tailwind`](../tailwind/) for the full preset documentation.
|
|
30
|
+
|
|
17
31
|
## Setup
|
|
18
32
|
|
|
19
33
|
### Internationalization
|
|
@@ -200,8 +214,6 @@ The plugin works with several different bundlers. See [React Aria's bundle size
|
|
|
200
214
|
|
|
201
215
|
## Usage
|
|
202
216
|
|
|
203
|
-
Before you start using the components you need to configure the [Tailwind preset](../tailwind/). Remember to add this package to the content scan.
|
|
204
|
-
|
|
205
217
|
```js
|
|
206
218
|
import { Button } from '@obosbbl/grunnmuren-react';
|
|
207
219
|
|
|
@@ -215,11 +215,13 @@ function Button({ ref = null, ...props }) {
|
|
|
215
215
|
return isLinkProps(restProps) ? /*#__PURE__*/ jsxRuntime.jsx(Link.Link, {
|
|
216
216
|
...restProps,
|
|
217
217
|
className: className,
|
|
218
|
+
"data-slot": "button",
|
|
218
219
|
ref: ref,
|
|
219
220
|
children: children
|
|
220
221
|
}) : /*#__PURE__*/ jsxRuntime.jsx(Button$1.Button, {
|
|
221
222
|
...restProps,
|
|
222
223
|
className: className,
|
|
224
|
+
"data-slot": "button",
|
|
223
225
|
isPending: isPending,
|
|
224
226
|
ref: ref,
|
|
225
227
|
children: children
|
|
@@ -213,11 +213,13 @@ function Button({ ref = null, ...props }) {
|
|
|
213
213
|
return isLinkProps(restProps) ? /*#__PURE__*/ jsx(Link, {
|
|
214
214
|
...restProps,
|
|
215
215
|
className: className,
|
|
216
|
+
"data-slot": "button",
|
|
216
217
|
ref: ref,
|
|
217
218
|
children: children
|
|
218
219
|
}) : /*#__PURE__*/ jsx(Button$1, {
|
|
219
220
|
...restProps,
|
|
220
221
|
className: className,
|
|
222
|
+
"data-slot": "button",
|
|
221
223
|
isPending: isPending,
|
|
222
224
|
ref: ref,
|
|
223
225
|
children: children
|
|
@@ -307,7 +307,11 @@ const oneColumnLayout = [
|
|
|
307
307
|
// Aligns <Content> and any element beside it (e.g. <Media>, <Badge>, <CTA> etc.) to the bottom of the <Content> container
|
|
308
308
|
'lg:items-end'
|
|
309
309
|
];
|
|
310
|
-
|
|
310
|
+
// Use `not-has-[video]` (tag selector) so that aspect-ratio rules also skip for video players
|
|
311
|
+
// that don't expose `data-slot="video"` (e.g., MuxPlayer).
|
|
312
|
+
// Aspect-ratio is applied to the `<Media>` element itself (not the img inside) so that the
|
|
313
|
+
// Media box fills the grid column and consumers don't need `!important` overrides when nesting.
|
|
314
|
+
const nonFullBleedAspectRatiosForSmallScreens = '*:data-[slot=media]:not-has-[video]:aspect-[1/1] sm:*:data-[slot=media]:not-has-[video]:aspect-4/3 md:*:data-[slot=media]:not-has-[video]:aspect-3/2';
|
|
311
315
|
const variants = cva.cva({
|
|
312
316
|
base: [
|
|
313
317
|
'container px-0',
|
|
@@ -332,7 +336,7 @@ const variants = cva.cva({
|
|
|
332
336
|
standard: [
|
|
333
337
|
oneColumnLayout,
|
|
334
338
|
nonFullBleedAspectRatiosForSmallScreens,
|
|
335
|
-
'lg:*:data-[slot=media]:not-has-
|
|
339
|
+
'lg:*:data-[slot=media]:not-has-[video]:aspect-2/1'
|
|
336
340
|
],
|
|
337
341
|
'full-bleed': [
|
|
338
342
|
oneColumnLayout,
|
|
@@ -361,7 +365,7 @@ const variants = cva.cva({
|
|
|
361
365
|
'lg:*:data-[slot=content]:gap-y-7',
|
|
362
366
|
nonFullBleedAspectRatiosForSmallScreens,
|
|
363
367
|
// Set media aspect ratio to 1:1 (square)
|
|
364
|
-
'lg:*:data-[slot=media]:not-has-
|
|
368
|
+
'lg:*:data-[slot=media]:not-has-[video]:aspect-square'
|
|
365
369
|
]
|
|
366
370
|
}
|
|
367
371
|
},
|
|
@@ -373,6 +377,8 @@ const variants = cva.cva({
|
|
|
373
377
|
],
|
|
374
378
|
className: [
|
|
375
379
|
'*:data-[slot=media]:*:rounded-3xl',
|
|
380
|
+
// Make non-video media (image/picture) fill the aspect-ratio-constrained Media box
|
|
381
|
+
'*:data-[slot=media]:not-has-[video]:*:size-full',
|
|
376
382
|
'*:data-[slot=carousel]:relative **:data-[slot=carousel-container]:rounded-3xl **:data-[slot=carousel-controls]:absolute **:data-[slot=carousel-controls]:right-4 **:data-[slot=carousel-controls]:bottom-4'
|
|
377
383
|
]
|
|
378
384
|
}
|
|
@@ -305,7 +305,11 @@ const oneColumnLayout = [
|
|
|
305
305
|
// Aligns <Content> and any element beside it (e.g. <Media>, <Badge>, <CTA> etc.) to the bottom of the <Content> container
|
|
306
306
|
'lg:items-end'
|
|
307
307
|
];
|
|
308
|
-
|
|
308
|
+
// Use `not-has-[video]` (tag selector) so that aspect-ratio rules also skip for video players
|
|
309
|
+
// that don't expose `data-slot="video"` (e.g., MuxPlayer).
|
|
310
|
+
// Aspect-ratio is applied to the `<Media>` element itself (not the img inside) so that the
|
|
311
|
+
// Media box fills the grid column and consumers don't need `!important` overrides when nesting.
|
|
312
|
+
const nonFullBleedAspectRatiosForSmallScreens = '*:data-[slot=media]:not-has-[video]:aspect-[1/1] sm:*:data-[slot=media]:not-has-[video]:aspect-4/3 md:*:data-[slot=media]:not-has-[video]:aspect-3/2';
|
|
309
313
|
const variants = cva({
|
|
310
314
|
base: [
|
|
311
315
|
'container px-0',
|
|
@@ -330,7 +334,7 @@ const variants = cva({
|
|
|
330
334
|
standard: [
|
|
331
335
|
oneColumnLayout,
|
|
332
336
|
nonFullBleedAspectRatiosForSmallScreens,
|
|
333
|
-
'lg:*:data-[slot=media]:not-has-
|
|
337
|
+
'lg:*:data-[slot=media]:not-has-[video]:aspect-2/1'
|
|
334
338
|
],
|
|
335
339
|
'full-bleed': [
|
|
336
340
|
oneColumnLayout,
|
|
@@ -359,7 +363,7 @@ const variants = cva({
|
|
|
359
363
|
'lg:*:data-[slot=content]:gap-y-7',
|
|
360
364
|
nonFullBleedAspectRatiosForSmallScreens,
|
|
361
365
|
// Set media aspect ratio to 1:1 (square)
|
|
362
|
-
'lg:*:data-[slot=media]:not-has-
|
|
366
|
+
'lg:*:data-[slot=media]:not-has-[video]:aspect-square'
|
|
363
367
|
]
|
|
364
368
|
}
|
|
365
369
|
},
|
|
@@ -371,6 +375,8 @@ const variants = cva({
|
|
|
371
375
|
],
|
|
372
376
|
className: [
|
|
373
377
|
'*:data-[slot=media]:*:rounded-3xl',
|
|
378
|
+
// Make non-video media (image/picture) fill the aspect-ratio-constrained Media box
|
|
379
|
+
'*:data-[slot=media]:not-has-[video]:*:size-full',
|
|
374
380
|
'*:data-[slot=carousel]:relative **:data-[slot=carousel-container]:rounded-3xl **:data-[slot=carousel-controls]:absolute **:data-[slot=carousel-controls]:right-4 **:data-[slot=carousel-controls]:bottom-4'
|
|
375
381
|
]
|
|
376
382
|
}
|
package/dist/index.d.mts
CHANGED
|
@@ -439,7 +439,7 @@ type HeadingProps = Omit<HTMLProps<HTMLHeadingElement>, 'size'> & VariantProps<t
|
|
|
439
439
|
};
|
|
440
440
|
declare const HeadingContext: react.Context<ContextValue<Partial<HeadingProps>, HTMLHeadingElement>>;
|
|
441
441
|
declare const headingVariants: (props?: ({
|
|
442
|
-
size?: "
|
|
442
|
+
size?: "s" | "xl" | "l" | "m" | "xs" | undefined;
|
|
443
443
|
} & ({
|
|
444
444
|
class?: cva.ClassValue;
|
|
445
445
|
className?: never;
|
|
@@ -661,6 +661,25 @@ type NumberFieldProps = {
|
|
|
661
661
|
} & Omit<NumberFieldProps$1, 'className' | 'isReadOnly' | 'isDisabled' | 'children' | 'style' | 'hideStepper'>;
|
|
662
662
|
declare function NumberField(props: NumberFieldProps): react_jsx_runtime.JSX.Element;
|
|
663
663
|
|
|
664
|
+
type PaginationProps = {
|
|
665
|
+
/** The current page (1-indexed). */
|
|
666
|
+
page: number;
|
|
667
|
+
/** The total number of pages. */
|
|
668
|
+
count: number;
|
|
669
|
+
/** Given a page number, returns the href for navigating to that page. The
|
|
670
|
+
* value is set as the `href` attribute on the rendered link, enabling
|
|
671
|
+
* right-click, middle-click and SEO. Client-side routing is set up via
|
|
672
|
+
* `routerOptions` on `<GrunnmurenProvider />`. */
|
|
673
|
+
getItemHref: (page: number) => string;
|
|
674
|
+
/** Optional callback fired when the user activates a page link. Useful for
|
|
675
|
+
* keeping local state in sync with the URL. Navigation still happens via
|
|
676
|
+
* the link's href. */
|
|
677
|
+
onChange?: (page: number) => void;
|
|
678
|
+
/** Additional class name for the root `<ol>`. */
|
|
679
|
+
className?: string;
|
|
680
|
+
};
|
|
681
|
+
declare const Pagination: (props: PaginationProps) => react_jsx_runtime.JSX.Element;
|
|
682
|
+
|
|
664
683
|
type RadioProps = {
|
|
665
684
|
children: React.ReactNode;
|
|
666
685
|
/** Additional CSS className for the element. */
|
|
@@ -943,5 +962,5 @@ type VideoLoopProps = {
|
|
|
943
962
|
};
|
|
944
963
|
declare const VideoLoop: ({ src, format, alt, className }: VideoLoopProps) => react_jsx_runtime.JSX.Element;
|
|
945
964
|
|
|
946
|
-
export { Accordion, AccordionItem, Alertbox, Avatar, Backlink, Badge, Breadcrumb, Breadcrumbs, Button, ButtonContext, Caption, Card, CardLink, Checkbox, CheckboxGroup, Combobox, ListBoxHeader as ComboboxHeader, ListBoxItem as ComboboxItem, ListBoxSection as ComboboxSection, Content, ContentContext, DateFormatter, Description, Disclosure, DisclosureButton, DisclosurePanel, DisclosureStateContext, ErrorMessage, Footer, GrunnmurenProvider, Heading, HeadingContext, Label, Link, LinkList, LinkListContainer, LinkListContext, LinkListItem, Media, MediaContext, NumberField, Radio, RadioGroup, Select, ListBoxHeader as SelectHeader, ListBoxItem as SelectItem, ListBoxSection as SelectSection, Tab, TabList, TabPanel, Tabs, Tag, TagGroup, TagList, TextArea, TextField, Carousel as UNSAFE_Carousel, CarouselButton as UNSAFE_CarouselButton, CarouselContext as UNSAFE_CarouselContext, CarouselControls as UNSAFE_CarouselControls, CarouselItem as UNSAFE_CarouselItem, CarouselItems as UNSAFE_CarouselItems, CarouselItemsContainer as UNSAFE_CarouselItemsContainer, CarouselItemsContainer as UNSAFE_CarouselItemsContainerProps, Dialog as UNSAFE_Dialog, DialogTrigger as UNSAFE_DialogTrigger, Drawer as UNSAFE_Drawer, FileUpload as UNSAFE_FileUpload,
|
|
947
|
-
export type { AccordionItemProps, AccordionProps, Props as AlertboxProps, AvatarProps, BacklinkProps, BadgeProps, BreadcrumbProps, BreadcrumbsProps, ButtonProps, CaptionProps, CardLinkProps, CardProps, CheckboxGroupProps, CheckboxProps, ComboboxProps, ContentProps, DateFormatterProps, DescriptionProps, DisclosureButtonProps, DisclosurePanelProps, DisclosureProps, ErrorMessageProps, FooterProps, GrunnmurenProviderProps, HeadingProps, LinkListContainerProps, LinkListContextValue, LinkListItemProps, LinkListProps, LinkProps, Locale, MediaProps, NumberFieldProps, RadioGroupProps, RadioProps, SelectProps, TabListProps, TabPanelProps, TabProps, TabsProps, TagGroupProps, TagListProps, TagProps, TextAreaProps, TextFieldProps, CarouselButtonProps as UNSAFE_CarouselButtonProps, CarouselContextValue as UNSAFE_CarouselContextValue, CarouselControlsProps as UNSAFE_CarouselControlsProps, CarouselItemProps as UNSAFE_CarouselItemProps, CarouselItemsProps as UNSAFE_CarouselItemsProps, CarouselProps as UNSAFE_CarouselProps, CarouselElement as UNSAFE_CarouselRef, DialogProps as UNSAFE_DialogProps, DialogTriggerProps as UNSAFE_DialogTriggerProps, DrawerProps as UNSAFE_DrawerProps, FileUploadProps as UNSAFE_FileUploadProps,
|
|
965
|
+
export { Accordion, AccordionItem, Alertbox, Avatar, Backlink, Badge, Breadcrumb, Breadcrumbs, Button, ButtonContext, Caption, Card, CardLink, Checkbox, CheckboxGroup, Combobox, ListBoxHeader as ComboboxHeader, ListBoxItem as ComboboxItem, ListBoxSection as ComboboxSection, Content, ContentContext, DateFormatter, Description, Disclosure, DisclosureButton, DisclosurePanel, DisclosureStateContext, ErrorMessage, Footer, GrunnmurenProvider, Heading, HeadingContext, Hero, HeroContext, Label, Link, LinkList, LinkListContainer, LinkListContext, LinkListItem, Media, MediaContext, NumberField, Radio, RadioGroup, Select, ListBoxHeader as SelectHeader, ListBoxItem as SelectItem, ListBoxSection as SelectSection, Tab, TabList, TabPanel, Tabs, Tag, TagGroup, TagList, TextArea, TextField, Carousel as UNSAFE_Carousel, CarouselButton as UNSAFE_CarouselButton, CarouselContext as UNSAFE_CarouselContext, CarouselControls as UNSAFE_CarouselControls, CarouselItem as UNSAFE_CarouselItem, CarouselItems as UNSAFE_CarouselItems, CarouselItemsContainer as UNSAFE_CarouselItemsContainer, CarouselItemsContainer as UNSAFE_CarouselItemsContainerProps, Dialog as UNSAFE_Dialog, DialogTrigger as UNSAFE_DialogTrigger, Drawer as UNSAFE_Drawer, FileUpload as UNSAFE_FileUpload, Modal as UNSAFE_Modal, Pagination as UNSAFE_Pagination, ResizableTableContainer as UNSAFE_ResizableTableContainer, Step as UNSAFE_Step, Stepper as UNSAFE_Stepper, Table as UNSAFE_Table, TableBody as UNSAFE_TableBody, TableCell as UNSAFE_TableCell, TableColumn as UNSAFE_TableColumn, TableColumnResizer as UNSAFE_TableColumnResizer, UNSAFE_TableContainer, TableHeader as UNSAFE_TableHeader, TableRow as UNSAFE_TableRow, VideoLoop, _useLocale as useLocale };
|
|
966
|
+
export type { AccordionItemProps, AccordionProps, Props as AlertboxProps, AvatarProps, BacklinkProps, BadgeProps, BreadcrumbProps, BreadcrumbsProps, ButtonProps, CaptionProps, CardLinkProps, CardProps, CheckboxGroupProps, CheckboxProps, ComboboxProps, ContentProps, DateFormatterProps, DescriptionProps, DisclosureButtonProps, DisclosurePanelProps, DisclosureProps, ErrorMessageProps, FooterProps, GrunnmurenProviderProps, HeadingProps, HeroContextValue, HeroProps, LinkListContainerProps, LinkListContextValue, LinkListItemProps, LinkListProps, LinkProps, Locale, MediaProps, NumberFieldProps, RadioGroupProps, RadioProps, SelectProps, TabListProps, TabPanelProps, TabProps, TabsProps, TagGroupProps, TagListProps, TagProps, TextAreaProps, TextFieldProps, CarouselButtonProps as UNSAFE_CarouselButtonProps, CarouselContextValue as UNSAFE_CarouselContextValue, CarouselControlsProps as UNSAFE_CarouselControlsProps, CarouselItemProps as UNSAFE_CarouselItemProps, CarouselItemsProps as UNSAFE_CarouselItemsProps, CarouselProps as UNSAFE_CarouselProps, CarouselElement as UNSAFE_CarouselRef, DialogProps as UNSAFE_DialogProps, DialogTriggerProps as UNSAFE_DialogTriggerProps, DrawerProps as UNSAFE_DrawerProps, FileUploadProps as UNSAFE_FileUploadProps, ModalProps as UNSAFE_ModalProps, PaginationProps as UNSAFE_PaginationProps, ResizableTableContainerProps as UNSAFE_ResizableTableContainerProps, StepProps as UNSAFE_StepProps, StepperProps as UNSAFE_StepperProps, TableBodyProps as UNSAFE_TableBodyProps, TableCellProps as UNSAFE_TableCellProps, TableColumnProps as UNSAFE_TableColumnProps, TableColumnResizerProps as UNSAFE_TableColumnResizerProps, UNSAFE_TableContainerProps, TableHeaderProps as UNSAFE_TableHeaderProps, TableProps as UNSAFE_TableProps, TableRowProps as UNSAFE_TableRowProps };
|
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { RouterProvider } from 'react-aria-components';
|
|
|
3
3
|
export { Form, Group } from 'react-aria-components';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import { cva, cx, compose } from 'cva';
|
|
6
|
-
import { createContext, useContext, useId, useRef, Children, useState, useEffect, useMemo, useCallback, useImperativeHandle, isValidElement, use, cloneElement } from 'react';
|
|
6
|
+
import { createContext, useContext, useId, useRef, Children, useState, useEffect, useMemo, useCallback, useImperativeHandle, isValidElement, useLayoutEffect, use, cloneElement } from 'react';
|
|
7
7
|
import { useContextProps, Provider, DEFAULT_SLOT, useSlottedContext } from 'react-aria-components/slots';
|
|
8
8
|
import { ChevronDown, Error, Warning, CheckCircle, InfoCircle, Close, User, ChevronLeft, Download, LinkExternal, ArrowRight, ChevronRight, LoadingSpinner, Check, Trash, Edit, PlayerPause, PlayerPlay } from '@obosbbl/grunnmuren-icons-react';
|
|
9
9
|
import { ButtonContext as ButtonContext$1, Button as Button$1 } from 'react-aria-components/Button';
|
|
@@ -397,6 +397,21 @@ const translations$1 = {
|
|
|
397
397
|
sv: 'Nästa',
|
|
398
398
|
en: 'Next'
|
|
399
399
|
},
|
|
400
|
+
pagination: {
|
|
401
|
+
nb: 'Paginering',
|
|
402
|
+
sv: 'Paginering',
|
|
403
|
+
en: 'Pagination'
|
|
404
|
+
},
|
|
405
|
+
previousPage: {
|
|
406
|
+
nb: 'Forrige side',
|
|
407
|
+
sv: 'Föregående sida',
|
|
408
|
+
en: 'Previous page'
|
|
409
|
+
},
|
|
410
|
+
nextPage: {
|
|
411
|
+
nb: 'Neste side',
|
|
412
|
+
sv: 'Nästa sida',
|
|
413
|
+
en: 'Next page'
|
|
414
|
+
},
|
|
400
415
|
externalLink: {
|
|
401
416
|
nb: '(ekstern lenke)',
|
|
402
417
|
sv: '(extern länk)',
|
|
@@ -945,11 +960,13 @@ function Button({ ref = null, ...props }) {
|
|
|
945
960
|
return isLinkProps(restProps) ? /*#__PURE__*/ jsx(Link$1, {
|
|
946
961
|
...restProps,
|
|
947
962
|
className: className,
|
|
963
|
+
"data-slot": "button",
|
|
948
964
|
ref: ref,
|
|
949
965
|
children: children
|
|
950
966
|
}) : /*#__PURE__*/ jsx(Button$1, {
|
|
951
967
|
...restProps,
|
|
952
968
|
className: className,
|
|
969
|
+
"data-slot": "button",
|
|
953
970
|
isPending: isPending,
|
|
954
971
|
ref: ref,
|
|
955
972
|
children: children
|
|
@@ -1188,7 +1205,11 @@ const oneColumnLayout = [
|
|
|
1188
1205
|
// Aligns <Content> and any element beside it (e.g. <Media>, <Badge>, <CTA> etc.) to the bottom of the <Content> container
|
|
1189
1206
|
'lg:items-end'
|
|
1190
1207
|
];
|
|
1191
|
-
|
|
1208
|
+
// Use `not-has-[video]` (tag selector) so that aspect-ratio rules also skip for video players
|
|
1209
|
+
// that don't expose `data-slot="video"` (e.g., MuxPlayer).
|
|
1210
|
+
// Aspect-ratio is applied to the `<Media>` element itself (not the img inside) so that the
|
|
1211
|
+
// Media box fills the grid column and consumers don't need `!important` overrides when nesting.
|
|
1212
|
+
const nonFullBleedAspectRatiosForSmallScreens = '*:data-[slot=media]:not-has-[video]:aspect-[1/1] sm:*:data-[slot=media]:not-has-[video]:aspect-4/3 md:*:data-[slot=media]:not-has-[video]:aspect-3/2';
|
|
1192
1213
|
const variants = cva({
|
|
1193
1214
|
base: [
|
|
1194
1215
|
'container px-0',
|
|
@@ -1213,7 +1234,7 @@ const variants = cva({
|
|
|
1213
1234
|
standard: [
|
|
1214
1235
|
oneColumnLayout,
|
|
1215
1236
|
nonFullBleedAspectRatiosForSmallScreens,
|
|
1216
|
-
'lg:*:data-[slot=media]:not-has-
|
|
1237
|
+
'lg:*:data-[slot=media]:not-has-[video]:aspect-2/1'
|
|
1217
1238
|
],
|
|
1218
1239
|
'full-bleed': [
|
|
1219
1240
|
oneColumnLayout,
|
|
@@ -1242,7 +1263,7 @@ const variants = cva({
|
|
|
1242
1263
|
'lg:*:data-[slot=content]:gap-y-7',
|
|
1243
1264
|
nonFullBleedAspectRatiosForSmallScreens,
|
|
1244
1265
|
// Set media aspect ratio to 1:1 (square)
|
|
1245
|
-
'lg:*:data-[slot=media]:not-has-
|
|
1266
|
+
'lg:*:data-[slot=media]:not-has-[video]:aspect-square'
|
|
1246
1267
|
]
|
|
1247
1268
|
}
|
|
1248
1269
|
},
|
|
@@ -1254,6 +1275,8 @@ const variants = cva({
|
|
|
1254
1275
|
],
|
|
1255
1276
|
className: [
|
|
1256
1277
|
'*:data-[slot=media]:*:rounded-3xl',
|
|
1278
|
+
// Make non-video media (image/picture) fill the aspect-ratio-constrained Media box
|
|
1279
|
+
'*:data-[slot=media]:not-has-[video]:*:size-full',
|
|
1257
1280
|
'*:data-[slot=carousel]:relative **:data-[slot=carousel-container]:rounded-3xl **:data-[slot=carousel-controls]:absolute **:data-[slot=carousel-controls]:right-4 **:data-[slot=carousel-controls]:bottom-4'
|
|
1258
1281
|
]
|
|
1259
1282
|
}
|
|
@@ -1354,7 +1377,7 @@ const Modal = ({ isDismissable = true, isOpen, onOpenChange, defaultOpen, classN
|
|
|
1354
1377
|
fullscreen: fullscreen,
|
|
1355
1378
|
children: /*#__PURE__*/ jsx(Modal$1, {
|
|
1356
1379
|
...restProps,
|
|
1357
|
-
className: ({ isEntering, isExiting })=>cx(className, 'overflow-auto bg-white text-left shadow-xl', fullscreen ? 'fixed inset-0' : 'w-full max-w-md rounded-2xl
|
|
1380
|
+
className: ({ isEntering, isExiting })=>cx(className, 'overflow-auto bg-white text-left shadow-xl', fullscreen ? 'fixed inset-0' : 'w-full max-w-md rounded-2xl align-middle', isEntering && 'zoom-in-95 animate-in duration-300 ease-out', isExiting && 'zoom-out-95 animate-out duration-200 ease-in', // Using the motion-safe class does not work, so we use motion-reduce to overwrite instead
|
|
1358
1381
|
'motion-reduce:animate-none')
|
|
1359
1382
|
})
|
|
1360
1383
|
})
|
|
@@ -1362,7 +1385,7 @@ const Modal = ({ isDismissable = true, isOpen, onOpenChange, defaultOpen, classN
|
|
|
1362
1385
|
};
|
|
1363
1386
|
const Dialog = ({ className, children, ...restProps })=>/*#__PURE__*/ jsx(Dialog$1, {
|
|
1364
1387
|
...restProps,
|
|
1365
|
-
className: cx(className, 'relative flex flex-col gap-y-5 outline-none', // Footer
|
|
1388
|
+
className: cx(className, 'relative flex flex-col gap-y-5 p-4 outline-none', // Footer
|
|
1366
1389
|
'**:data-[slot="footer"]:flex **:data-[slot="footer"]:gap-x-2'),
|
|
1367
1390
|
children: ({ close })=>/*#__PURE__*/ jsx(Provider, {
|
|
1368
1391
|
values: [
|
|
@@ -2059,10 +2082,10 @@ const drawerVariants = cva({
|
|
|
2059
2082
|
],
|
|
2060
2083
|
variants: {
|
|
2061
2084
|
placement: {
|
|
2062
|
-
right: 'top-0 right-0 h-dvh w-full max-w-md rounded-l-2xl
|
|
2063
|
-
left: 'top-0 left-0 h-dvh w-full max-w-md rounded-r-2xl
|
|
2064
|
-
top: 'inset-x-0 top-0 max-h-[80dvh] w-full rounded-b-2xl
|
|
2065
|
-
bottom: 'inset-x-0 bottom-0 max-h-[80dvh] w-full rounded-t-2xl
|
|
2085
|
+
right: 'top-0 right-0 h-dvh w-full max-w-md rounded-l-2xl',
|
|
2086
|
+
left: 'top-0 left-0 h-dvh w-full max-w-md rounded-r-2xl',
|
|
2087
|
+
top: 'inset-x-0 top-0 max-h-[80dvh] w-full rounded-b-2xl',
|
|
2088
|
+
bottom: 'inset-x-0 bottom-0 max-h-[80dvh] w-full rounded-t-2xl'
|
|
2066
2089
|
},
|
|
2067
2090
|
isEntering: {
|
|
2068
2091
|
true: 'animate-in duration-300 ease-out'
|
|
@@ -2571,6 +2594,157 @@ function NumberField(props) {
|
|
|
2571
2594
|
});
|
|
2572
2595
|
}
|
|
2573
2596
|
|
|
2597
|
+
// Lives here, not in `translations`, because it interpolates the hidden range
|
|
2598
|
+
// — the static-string `translations` map doesn't support function values.
|
|
2599
|
+
const HIDDEN_PAGES_LABEL = {
|
|
2600
|
+
nb: (start, end)=>start === end ? `Skjuler side ${start}` : `Skjuler side ${start} til ${end}`,
|
|
2601
|
+
sv: (start, end)=>start === end ? `Döljer sida ${start}` : `Döljer sida ${start} till ${end}`,
|
|
2602
|
+
en: (start, end)=>start === end ? `Hides page ${start}` : `Hides pages ${start} to ${end}`
|
|
2603
|
+
};
|
|
2604
|
+
// Number of pages shown on each side of the current page on desktop. On
|
|
2605
|
+
// narrow containers, CSS hides the outermost visible pages via the
|
|
2606
|
+
// `data-outer` attribute so the entire pagination fits a 320px viewport
|
|
2607
|
+
// without changing the rendered DOM.
|
|
2608
|
+
const SIBLING_COUNT = 2;
|
|
2609
|
+
/**
|
|
2610
|
+
* Returns the page numbers (1-indexed) that should be rendered between the
|
|
2611
|
+
* always-visible first page and the next/prev buttons. Mirrors v1's ellipsis
|
|
2612
|
+
* behaviour: only a single (left) ellipsis is ever shown, and the last page
|
|
2613
|
+
* is not guaranteed to be rendered.
|
|
2614
|
+
*
|
|
2615
|
+
* Page 1 is filtered out because it's rendered separately as a fixed slot —
|
|
2616
|
+
* keeping it here would duplicate when the special-case extension lands the
|
|
2617
|
+
* window on it.
|
|
2618
|
+
*/ function getVisiblePages(currentPage, pageCount) {
|
|
2619
|
+
const end = Math.min(Math.max(2 + SIBLING_COUNT * 2, currentPage + SIBLING_COUNT), pageCount);
|
|
2620
|
+
let start = Math.max(Math.min(currentPage - SIBLING_COUNT, end - SIBLING_COUNT * 2), 1);
|
|
2621
|
+
// When `start` lands exactly SIBLING_COUNT pages past page 1, we have room
|
|
2622
|
+
// to render one extra page to the left without needing an ellipsis.
|
|
2623
|
+
if (start - SIBLING_COUNT === 0) {
|
|
2624
|
+
start = start - 1;
|
|
2625
|
+
}
|
|
2626
|
+
const pages = Array.from({
|
|
2627
|
+
length: end - start
|
|
2628
|
+
}, (_, i)=>start + i + 1);
|
|
2629
|
+
return pages.filter((p)=>p > 1);
|
|
2630
|
+
}
|
|
2631
|
+
/**
|
|
2632
|
+
* Returns the indices of the two visible pages farthest from `currentPage`.
|
|
2633
|
+
* These get marked with `data-outer` so the CSS container query can hide them
|
|
2634
|
+
* on narrow viewports, dropping the slot count from 8 to 6 without changing
|
|
2635
|
+
* the rendered DOM. Returns an empty set when there's nothing to gain.
|
|
2636
|
+
*/ function getOuterIndices(visiblePages, currentPage) {
|
|
2637
|
+
if (visiblePages.length <= 3) {
|
|
2638
|
+
return new Set();
|
|
2639
|
+
}
|
|
2640
|
+
return new Set(visiblePages.map((p, i)=>({
|
|
2641
|
+
i,
|
|
2642
|
+
distance: Math.abs(p - currentPage)
|
|
2643
|
+
})).sort((a, b)=>b.distance - a.distance).slice(0, 2).map((entry)=>entry.i));
|
|
2644
|
+
}
|
|
2645
|
+
const Pagination = (props)=>{
|
|
2646
|
+
const { page, count, getItemHref, onChange, className } = props;
|
|
2647
|
+
const locale = _useLocale();
|
|
2648
|
+
const currentPage = Math.max(1, Math.min(page, Math.max(1, count)));
|
|
2649
|
+
const pageCount = Math.max(1, count);
|
|
2650
|
+
const visiblePages = getVisiblePages(currentPage, pageCount);
|
|
2651
|
+
const outerIndices = getOuterIndices(visiblePages, currentPage);
|
|
2652
|
+
// Show the left ellipsis whenever there's a gap between page 1 and the
|
|
2653
|
+
// first visible page. This matches v1 for SIBLING_COUNT=2.
|
|
2654
|
+
const showLeftEllipsis = visiblePages.length > 0 && visiblePages[0] > 2;
|
|
2655
|
+
const canGoPrev = currentPage > 1;
|
|
2656
|
+
const canGoNext = currentPage < pageCount;
|
|
2657
|
+
// Prev/next switches element type (<a> with href ↔ <button> without) when
|
|
2658
|
+
// crossing a boundary. React replaces the DOM node, so the browser blurs
|
|
2659
|
+
// the focused element — restore it manually after the re-render.
|
|
2660
|
+
const prevButtonRef = useRef(null);
|
|
2661
|
+
const nextButtonRef = useRef(null);
|
|
2662
|
+
const restoreFocusToRef = useRef(null);
|
|
2663
|
+
useLayoutEffect(()=>{
|
|
2664
|
+
if (restoreFocusToRef.current === 'prev') {
|
|
2665
|
+
prevButtonRef.current?.focus();
|
|
2666
|
+
} else if (restoreFocusToRef.current === 'next') {
|
|
2667
|
+
nextButtonRef.current?.focus();
|
|
2668
|
+
}
|
|
2669
|
+
restoreFocusToRef.current = null;
|
|
2670
|
+
}, [
|
|
2671
|
+
currentPage
|
|
2672
|
+
]);
|
|
2673
|
+
return /*#__PURE__*/ jsxs("ol", {
|
|
2674
|
+
"aria-label": translations$1.pagination[locale],
|
|
2675
|
+
className: cx('gm-pagination', className),
|
|
2676
|
+
children: [
|
|
2677
|
+
/*#__PURE__*/ jsx("li", {
|
|
2678
|
+
children: /*#__PURE__*/ jsx(Button, {
|
|
2679
|
+
"aria-disabled": !canGoPrev,
|
|
2680
|
+
"aria-label": translations$1.previousPage[locale],
|
|
2681
|
+
color: "white",
|
|
2682
|
+
href: canGoPrev ? getItemHref(currentPage - 1) : undefined,
|
|
2683
|
+
isIconOnly: true,
|
|
2684
|
+
onPress: canGoPrev ? ()=>{
|
|
2685
|
+
restoreFocusToRef.current = 'prev';
|
|
2686
|
+
onChange?.(currentPage - 1);
|
|
2687
|
+
} : undefined,
|
|
2688
|
+
ref: prevButtonRef,
|
|
2689
|
+
children: /*#__PURE__*/ jsx(ChevronLeft, {})
|
|
2690
|
+
})
|
|
2691
|
+
}),
|
|
2692
|
+
/*#__PURE__*/ jsx(PageItem, {
|
|
2693
|
+
getItemHref: getItemHref,
|
|
2694
|
+
isActive: currentPage === 1,
|
|
2695
|
+
onChange: onChange,
|
|
2696
|
+
page: 1
|
|
2697
|
+
}),
|
|
2698
|
+
showLeftEllipsis && /*#__PURE__*/ jsxs("li", {
|
|
2699
|
+
"data-slot": "ellipsis",
|
|
2700
|
+
children: [
|
|
2701
|
+
/*#__PURE__*/ jsx("span", {
|
|
2702
|
+
"aria-hidden": "true",
|
|
2703
|
+
children: "…"
|
|
2704
|
+
}),
|
|
2705
|
+
/*#__PURE__*/ jsx("span", {
|
|
2706
|
+
children: HIDDEN_PAGES_LABEL[locale](2, visiblePages[0] - 1)
|
|
2707
|
+
})
|
|
2708
|
+
]
|
|
2709
|
+
}),
|
|
2710
|
+
visiblePages.map((p, i)=>/*#__PURE__*/ jsx(PageItem, {
|
|
2711
|
+
getItemHref: getItemHref,
|
|
2712
|
+
isActive: p === currentPage,
|
|
2713
|
+
isOuter: outerIndices.has(i),
|
|
2714
|
+
onChange: onChange,
|
|
2715
|
+
page: p
|
|
2716
|
+
}, p)),
|
|
2717
|
+
/*#__PURE__*/ jsx("li", {
|
|
2718
|
+
children: /*#__PURE__*/ jsx(Button, {
|
|
2719
|
+
"aria-disabled": !canGoNext,
|
|
2720
|
+
"aria-label": translations$1.nextPage[locale],
|
|
2721
|
+
color: "white",
|
|
2722
|
+
href: canGoNext ? getItemHref(currentPage + 1) : undefined,
|
|
2723
|
+
isIconOnly: true,
|
|
2724
|
+
onPress: canGoNext ? ()=>{
|
|
2725
|
+
restoreFocusToRef.current = 'next';
|
|
2726
|
+
onChange?.(currentPage + 1);
|
|
2727
|
+
} : undefined,
|
|
2728
|
+
ref: nextButtonRef,
|
|
2729
|
+
children: /*#__PURE__*/ jsx(ChevronRight, {})
|
|
2730
|
+
})
|
|
2731
|
+
})
|
|
2732
|
+
]
|
|
2733
|
+
});
|
|
2734
|
+
};
|
|
2735
|
+
const PageItem = ({ page, isActive, isOuter, getItemHref, onChange })=>{
|
|
2736
|
+
return /*#__PURE__*/ jsx("li", {
|
|
2737
|
+
"data-outer": isOuter || undefined,
|
|
2738
|
+
children: /*#__PURE__*/ jsx(Button, {
|
|
2739
|
+
"aria-current": isActive ? 'page' : undefined,
|
|
2740
|
+
color: isActive ? 'blue' : 'white',
|
|
2741
|
+
href: getItemHref(page),
|
|
2742
|
+
onPress: ()=>onChange?.(page),
|
|
2743
|
+
children: page
|
|
2744
|
+
})
|
|
2745
|
+
});
|
|
2746
|
+
};
|
|
2747
|
+
|
|
2574
2748
|
const defaultClasses = cx([
|
|
2575
2749
|
'relative -ml-2.5 inline-flex max-w-fit cursor-pointer items-start gap-4 py-2.5 pl-2.5 leading-7',
|
|
2576
2750
|
// the radio button itself
|
|
@@ -3476,4 +3650,4 @@ const VideoLoop = ({ src, format, alt, className })=>{
|
|
|
3476
3650
|
});
|
|
3477
3651
|
};
|
|
3478
3652
|
|
|
3479
|
-
export { Accordion, AccordionItem, Alertbox, Avatar, Backlink, Badge, Breadcrumb, Breadcrumbs, Button, ButtonContext, Caption, Card, CardLink, Checkbox, CheckboxGroup, Combobox, ListBoxHeader as ComboboxHeader, ListBoxItem as ComboboxItem, ListBoxSection as ComboboxSection, Content, ContentContext, DateFormatter, Description, Disclosure, DisclosureButton, DisclosurePanel, DisclosureStateContext, ErrorMessage, Footer, GrunnmurenProvider, Heading, HeadingContext, Label, Link, LinkList, LinkListContainer, LinkListContext, LinkListItem, Media, MediaContext, NumberField, Radio, RadioGroup, Select, ListBoxHeader as SelectHeader, ListBoxItem as SelectItem, ListBoxSection as SelectSection, Tab, TabList, TabPanel, Tabs, Tag, TagGroup, TagList, TextArea, TextField, Carousel as UNSAFE_Carousel, CarouselButton as UNSAFE_CarouselButton, CarouselContext as UNSAFE_CarouselContext, CarouselControls as UNSAFE_CarouselControls, CarouselItem as UNSAFE_CarouselItem, CarouselItems as UNSAFE_CarouselItems, CarouselItemsContainer as UNSAFE_CarouselItemsContainer, Dialog as UNSAFE_Dialog, DialogTrigger as UNSAFE_DialogTrigger, Drawer as UNSAFE_Drawer, FileUpload as UNSAFE_FileUpload,
|
|
3653
|
+
export { Accordion, AccordionItem, Alertbox, Avatar, Backlink, Badge, Breadcrumb, Breadcrumbs, Button, ButtonContext, Caption, Card, CardLink, Checkbox, CheckboxGroup, Combobox, ListBoxHeader as ComboboxHeader, ListBoxItem as ComboboxItem, ListBoxSection as ComboboxSection, Content, ContentContext, DateFormatter, Description, Disclosure, DisclosureButton, DisclosurePanel, DisclosureStateContext, ErrorMessage, Footer, GrunnmurenProvider, Heading, HeadingContext, Hero, HeroContext, Label, Link, LinkList, LinkListContainer, LinkListContext, LinkListItem, Media, MediaContext, NumberField, Radio, RadioGroup, Select, ListBoxHeader as SelectHeader, ListBoxItem as SelectItem, ListBoxSection as SelectSection, Tab, TabList, TabPanel, Tabs, Tag, TagGroup, TagList, TextArea, TextField, Carousel as UNSAFE_Carousel, CarouselButton as UNSAFE_CarouselButton, CarouselContext as UNSAFE_CarouselContext, CarouselControls as UNSAFE_CarouselControls, CarouselItem as UNSAFE_CarouselItem, CarouselItems as UNSAFE_CarouselItems, CarouselItemsContainer as UNSAFE_CarouselItemsContainer, Dialog as UNSAFE_Dialog, DialogTrigger as UNSAFE_DialogTrigger, Drawer as UNSAFE_Drawer, FileUpload as UNSAFE_FileUpload, Modal as UNSAFE_Modal, Pagination as UNSAFE_Pagination, ResizableTableContainer as UNSAFE_ResizableTableContainer, Step as UNSAFE_Step, Stepper as UNSAFE_Stepper, Table as UNSAFE_Table, TableBody as UNSAFE_TableBody, TableCell as UNSAFE_TableCell, TableColumn as UNSAFE_TableColumn, TableColumnResizer as UNSAFE_TableColumnResizer, UNSAFE_TableContainer, TableHeader as UNSAFE_TableHeader, TableRow as UNSAFE_TableRow, VideoLoop, _useLocale as useLocale };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obosbbl/grunnmuren-react",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Grunnmuren components in React",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"tailwindcss": "4.2.2"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"react": "^19"
|
|
36
|
+
"react": "^19",
|
|
37
|
+
"@obosbbl/grunnmuren-tailwind": "^2.4.11"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
39
40
|
"build": "bunchee"
|