@neo-design/mui 1.0.0-rc.6 → 1.0.0-rc.7
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/package.json +2 -2
- package/theme/components.mjs +67 -0
- package/ui/BottomSheet.mjs +14 -0
- package/ui/_modal-base.mjs +7 -27
- package/ui/_scroll-state.mjs +35 -0
- package/ui/_sheet-base.mjs +82 -0
- package/ui/index.d.ts +22 -1
- package/ui/index.mjs +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neo-design/mui",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.7",
|
|
4
4
|
"description": "Neo — the Material UI theme and the Neo* React components, on top of @neo-design/foundations.",
|
|
5
5
|
"homepage": "https://github.com/jzamorano-ui/neo-ds/blob/main/docs/dev/INTEGRATION.md",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"theme"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@neo-design/foundations": "1.0.0-rc.
|
|
32
|
+
"@neo-design/foundations": "1.0.0-rc.7"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@emotion/react": "^11",
|
package/theme/components.mjs
CHANGED
|
@@ -398,6 +398,73 @@ export const components = {
|
|
|
398
398
|
},
|
|
399
399
|
},
|
|
400
400
|
|
|
401
|
+
MuiDrawer: {
|
|
402
|
+
styleOverrides: {
|
|
403
|
+
paper: ({ theme }) => ({
|
|
404
|
+
'&.MuiDrawer-paperAnchorBottom': {
|
|
405
|
+
backgroundColor: 'var(--surface--base--default)',
|
|
406
|
+
borderStartStartRadius: 'var(--neo-radius-xl)', borderStartEndRadius: 'var(--neo-radius-xl)',
|
|
407
|
+
borderEndStartRadius: 0, borderEndEndRadius: 0,
|
|
408
|
+
maxHeight: '90dvh',
|
|
409
|
+
display: 'flex', flexDirection: 'column', alignItems: 'flex-start',
|
|
410
|
+
'& > *': { alignSelf: 'stretch' },
|
|
411
|
+
'&:focus': { outline: 0 },
|
|
412
|
+
},
|
|
413
|
+
'& .NeoBottomSheet-handle': {
|
|
414
|
+
inlineSize: '40px', blockSize: '4px', borderRadius: 'var(--neo-radius-pill)',
|
|
415
|
+
backgroundColor: 'var(--fill--base--medium)',
|
|
416
|
+
marginInline: 'auto', marginBlock: 'var(--neo-space-sm-rem)', flex: '0 0 auto',
|
|
417
|
+
},
|
|
418
|
+
'&[data-image="true"] .NeoBottomSheet-handle': { backgroundColor: 'var(--fill--base--default)' },
|
|
419
|
+
|
|
420
|
+
'& .NeoBottomSheet-header': {
|
|
421
|
+
display: 'flex', flexDirection: 'column', flex: '0 0 auto',
|
|
422
|
+
justifyContent: 'center', alignItems: 'flex-end',
|
|
423
|
+
'& > *': { alignSelf: 'stretch' },
|
|
424
|
+
paddingInline: 'var(--neo-space-lg-rem)', paddingBlock: `0 var(--neo-space-xs-rem)`,
|
|
425
|
+
},
|
|
426
|
+
'&[data-scroll="top"] .NeoBottomSheet-header, &[data-scroll="mid"] .NeoBottomSheet-header, &[data-scroll="bottom"] .NeoBottomSheet-header': {
|
|
427
|
+
borderBlockEnd: '1px solid var(--border--base--secondary)',
|
|
428
|
+
},
|
|
429
|
+
'& .NeoBottomSheet-headerRow': {
|
|
430
|
+
display: 'flex', alignItems: 'center', justifyContent: 'flex-end',
|
|
431
|
+
blockSize: '40px', gap: 'var(--neo-space-none-rem)',
|
|
432
|
+
},
|
|
433
|
+
'& .NeoBottomSheet-title': {
|
|
434
|
+
...theme.typography.bodyLgBold, color: 'var(--text--base--default)',
|
|
435
|
+
flex: '1 1 auto', minInlineSize: 0, margin: 0,
|
|
436
|
+
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
'& .NeoBottomSheet-headerImage': {
|
|
440
|
+
position: 'relative', blockSize: '160px', flex: '0 0 auto', overflow: 'hidden',
|
|
441
|
+
'& > img, & > picture, & > video': { inlineSize: '100%', blockSize: '100%', objectFit: 'cover', display: 'block' },
|
|
442
|
+
'& .NeoBottomSheet-handle': { position: 'absolute', insetInline: 0, insetBlockStart: 0 },
|
|
443
|
+
'& .NeoBottomSheet-close': {
|
|
444
|
+
position: 'absolute', insetBlockStart: 'var(--neo-space-lg-rem)', insetInlineEnd: 'var(--neo-space-lg-rem)',
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
'& .NeoBottomSheet-content': {
|
|
449
|
+
...theme.typography.bodyLgRegular, color: 'var(--text--base--default)',
|
|
450
|
+
flex: '1 1 auto', overflowY: 'auto', minBlockSize: '200px',
|
|
451
|
+
paddingInline: 'var(--neo-space-lg-rem)', paddingBlock: `0 var(--neo-space-lg-rem)`,
|
|
452
|
+
'&:focus-visible': { outline: 'none', boxShadow: focusRingInset.boxShadow },
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
'& .NeoBottomSheet-actions': {
|
|
456
|
+
display: 'flex', flexDirection: 'column', gap: 'var(--neo-space-sm-rem)', flex: '0 0 auto',
|
|
457
|
+
paddingInline: 'var(--neo-space-lg-rem)',
|
|
458
|
+
paddingBlock: `var(--neo-space-lg-rem) var(--neo-space-2xl-rem)`,
|
|
459
|
+
backgroundColor: 'var(--surface--base--default)',
|
|
460
|
+
'& > div': { display: 'contents' },
|
|
461
|
+
'& .MuiButton-root': { inlineSize: '100%' },
|
|
462
|
+
'&[data-scroll="top"], &[data-scroll="mid"]': { borderBlockStart: '1px solid var(--border--base--secondary)' },
|
|
463
|
+
},
|
|
464
|
+
}),
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
|
|
401
468
|
MuiCheckbox: {
|
|
402
469
|
defaultProps: { disableRipple: true, checkedIcon: _h(CheckboxCheckedIcon), indeterminateIcon: _h(CheckboxIndeterminateIcon) },
|
|
403
470
|
styleOverrides: {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
// packages/mui/ui/BottomSheet.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { withAliases } from './_alias-props.mjs';
|
|
5
|
+
import { BottomSheetBase } from './_sheet-base.mjs';
|
|
6
|
+
const h = React.createElement;
|
|
7
|
+
|
|
8
|
+
export const NeoBottomSheet = React.forwardRef(function NeoBottomSheet(rawProps, ref) {
|
|
9
|
+
const { open, onClose, title, image, actions, children, ...rest } = withAliases('NeoBottomSheet', rawProps);
|
|
10
|
+
return h(BottomSheetBase, {
|
|
11
|
+
open, onClose, title, image, actions, closable: true,
|
|
12
|
+
...rest, ref,
|
|
13
|
+
}, children);
|
|
14
|
+
});
|
package/ui/_modal-base.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import React from 'react';
|
|
|
4
4
|
import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, useMediaQuery } from '@mui/material';
|
|
5
5
|
import { NeoButtonIcon } from './ButtonIcon.mjs';
|
|
6
6
|
import { renderIcon } from './_icon.mjs';
|
|
7
|
+
import { useScrollState } from './_scroll-state.mjs';
|
|
7
8
|
const h = React.createElement;
|
|
8
9
|
|
|
9
10
|
const ICON_BY_TYPE = {
|
|
@@ -12,9 +13,12 @@ const ICON_BY_TYPE = {
|
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
const unaPorUna = (acciones) => {
|
|
16
|
+
export const unaPorUna = (acciones) => {
|
|
16
17
|
const lista = React.Children.toArray(acciones);
|
|
17
|
-
|
|
18
|
+
if (lista.length !== 1 || !React.isValidElement(lista[0])) return lista;
|
|
19
|
+
const [unico] = lista;
|
|
20
|
+
const pelado = unico.type === 'div' && !unico.props.className && !unico.props.style;
|
|
21
|
+
return unico.type === React.Fragment || pelado ? React.Children.toArray(unico.props.children) : lista;
|
|
18
22
|
};
|
|
19
23
|
|
|
20
24
|
export const ModalDialogBase = React.forwardRef(function ModalDialogBase({
|
|
@@ -27,31 +31,7 @@ export const ModalDialogBase = React.forwardRef(function ModalDialogBase({
|
|
|
27
31
|
const apiladas = fullScreen ? angosto : size === 'xs';
|
|
28
32
|
const accionesEnOrden = apiladas && actions ? unaPorUna(actions).reverse() : actions;
|
|
29
33
|
|
|
30
|
-
const
|
|
31
|
-
const [contentNode, setContentNode] = React.useState(null);
|
|
32
|
-
const setContent = React.useCallback((node) => { contentRef.current = node; setContentNode(node); }, []);
|
|
33
|
-
const [scroll, setScroll] = React.useState('none');
|
|
34
|
-
const [overflows, setOverflows] = React.useState(false);
|
|
35
|
-
const watchScroll = React.useCallback(() => {
|
|
36
|
-
const el = contentRef.current;
|
|
37
|
-
if (!el) return;
|
|
38
|
-
const extra = el.scrollHeight - el.clientHeight;
|
|
39
|
-
setOverflows(extra > 1);
|
|
40
|
-
if (extra <= 1) return setScroll('none');
|
|
41
|
-
if (el.scrollTop <= 1) return setScroll('top');
|
|
42
|
-
if (el.scrollTop >= extra - 1) return setScroll('bottom');
|
|
43
|
-
setScroll('mid');
|
|
44
|
-
}, []);
|
|
45
|
-
React.useEffect(() => {
|
|
46
|
-
const el = contentNode;
|
|
47
|
-
if (!el) return undefined;
|
|
48
|
-
watchScroll();
|
|
49
|
-
if (typeof ResizeObserver === 'undefined') return undefined;
|
|
50
|
-
const ro = new ResizeObserver(watchScroll);
|
|
51
|
-
ro.observe(el);
|
|
52
|
-
for (const h of el.children) ro.observe(h);
|
|
53
|
-
return () => ro.disconnect();
|
|
54
|
-
}, [watchScroll, open, children, contentNode]);
|
|
34
|
+
const { setRegion: setContent, watchScroll, scroll, overflows } = useScrollState([open, children]);
|
|
55
35
|
const titleId = `neo-modal-${auto}-title`;
|
|
56
36
|
const descriptionId = description ? `neo-modal-${auto}-description` : undefined;
|
|
57
37
|
const rowId = `neo-modal-${auto}-row`;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
// packages/mui/ui/_scroll-state.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
export function useScrollState(deps = []) {
|
|
6
|
+
const ref = React.useRef(null);
|
|
7
|
+
const [node, setNode] = React.useState(null);
|
|
8
|
+
const setRegion = React.useCallback((n) => { ref.current = n; setNode(n); }, []);
|
|
9
|
+
const [scroll, setScroll] = React.useState('none');
|
|
10
|
+
const [overflows, setOverflows] = React.useState(false);
|
|
11
|
+
|
|
12
|
+
const watchScroll = React.useCallback(() => {
|
|
13
|
+
const el = ref.current;
|
|
14
|
+
if (!el) return;
|
|
15
|
+
const extra = el.scrollHeight - el.clientHeight;
|
|
16
|
+
setOverflows(extra > 1);
|
|
17
|
+
if (extra <= 1) return setScroll('none');
|
|
18
|
+
if (el.scrollTop <= 1) return setScroll('top');
|
|
19
|
+
if (el.scrollTop >= extra - 1) return setScroll('bottom');
|
|
20
|
+
setScroll('mid');
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
const el = node;
|
|
25
|
+
if (!el) return undefined;
|
|
26
|
+
watchScroll();
|
|
27
|
+
if (typeof ResizeObserver === 'undefined') return undefined;
|
|
28
|
+
const ro = new ResizeObserver(watchScroll);
|
|
29
|
+
ro.observe(el);
|
|
30
|
+
for (const hijo of el.children) ro.observe(hijo);
|
|
31
|
+
return () => ro.disconnect();
|
|
32
|
+
}, [watchScroll, node, ...deps]);
|
|
33
|
+
|
|
34
|
+
return { setRegion, watchScroll, scroll, overflows };
|
|
35
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
// packages/mui/ui/_sheet-base.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { SwipeableDrawer } from '@mui/material';
|
|
5
|
+
import { NeoButtonIcon } from './ButtonIcon.mjs';
|
|
6
|
+
import { renderIcon } from './_icon.mjs';
|
|
7
|
+
import { useScrollState } from './_scroll-state.mjs';
|
|
8
|
+
import { unaPorUna } from './_modal-base.mjs';
|
|
9
|
+
const h = React.createElement;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export const BottomSheetBase = React.forwardRef(function BottomSheetBase({
|
|
13
|
+
open, onClose, onOpen, title, image, actions, children, closable = true,
|
|
14
|
+
'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, ...rest
|
|
15
|
+
}, ref) {
|
|
16
|
+
const auto = React.useId();
|
|
17
|
+
const titleId = title && !image ? `neo-sheet-${auto}-title` : undefined;
|
|
18
|
+
|
|
19
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
20
|
+
if (!title && !ariaLabel && !ariaLabelledBy) {
|
|
21
|
+
console.warn('[Neo] BottomSheet: NeoBottomSheet needs a name: pass `title`, or —when you pass `image`— give the content a heading and point `aria-labelledby` at it.');
|
|
22
|
+
}
|
|
23
|
+
if (!onClose) {
|
|
24
|
+
console.warn('[Neo] BottomSheet: NeoBottomSheet needs `onClose`: without it the sheet has no ✕, no swipe and no way out.');
|
|
25
|
+
}
|
|
26
|
+
if (title && image) {
|
|
27
|
+
console.warn('[Neo] BottomSheet: with `image` the header has no title slot — the master draws one or the other. The `title` will not be shown.');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { setRegion, watchScroll, scroll, overflows } = useScrollState([open, children]);
|
|
32
|
+
|
|
33
|
+
const accionesEnOrden = actions ? unaPorUna(actions).reverse() : actions;
|
|
34
|
+
|
|
35
|
+
const close = closable && onClose
|
|
36
|
+
? h(NeoButtonIcon, { key: 'x', onClick: onClose, 'aria-label': 'Cerrar',
|
|
37
|
+
variant: image ? 'primary' : 'tertiary', surface: image ? 'inverse' : 'default',
|
|
38
|
+
size: 'medium', className: 'NeoBottomSheet-close' },
|
|
39
|
+
renderIcon('system-close'))
|
|
40
|
+
: null;
|
|
41
|
+
|
|
42
|
+
const handle = h('div', { key: 'h', className: 'NeoBottomSheet-handle', 'aria-hidden': 'true' });
|
|
43
|
+
|
|
44
|
+
const header = image
|
|
45
|
+
? h('div', { key: 'hdr', className: 'NeoBottomSheet-headerImage' }, image, handle, close)
|
|
46
|
+
: h('header', { key: 'hdr', className: 'NeoBottomSheet-header' },
|
|
47
|
+
handle,
|
|
48
|
+
h('div', { className: 'NeoBottomSheet-headerRow' },
|
|
49
|
+
title ? h('h2', { id: titleId, className: 'NeoBottomSheet-title' }, title) : null,
|
|
50
|
+
close));
|
|
51
|
+
|
|
52
|
+
return h(SwipeableDrawer, {
|
|
53
|
+
ref,
|
|
54
|
+
anchor: 'bottom',
|
|
55
|
+
open: !!open,
|
|
56
|
+
onClose,
|
|
57
|
+
onOpen: onOpen || (() => {}),
|
|
58
|
+
disableSwipeToOpen: true,
|
|
59
|
+
slotProps: { backdrop: { 'aria-hidden': 'true' } },
|
|
60
|
+
className,
|
|
61
|
+
...rest,
|
|
62
|
+
PaperProps: {
|
|
63
|
+
...rest.PaperProps,
|
|
64
|
+
className: ['NeoBottomSheet', rest.PaperProps?.className].filter(Boolean).join(' '),
|
|
65
|
+
'data-image': image ? 'true' : 'false',
|
|
66
|
+
'data-scroll': scroll,
|
|
67
|
+
role: 'dialog',
|
|
68
|
+
'aria-modal': 'true',
|
|
69
|
+
'aria-labelledby': ariaLabelledBy || titleId,
|
|
70
|
+
'aria-label': (ariaLabelledBy || titleId) ? undefined : ariaLabel,
|
|
71
|
+
tabIndex: -1,
|
|
72
|
+
},
|
|
73
|
+
...(rest.slotProps ? { slotProps: { backdrop: { 'aria-hidden': 'true' }, ...rest.slotProps } } : {}),
|
|
74
|
+
},
|
|
75
|
+
header,
|
|
76
|
+
h('div', { key: 'c', className: 'NeoBottomSheet-content', ref: setRegion, onScroll: watchScroll,
|
|
77
|
+
'data-scroll': scroll,
|
|
78
|
+
...(overflows ? { tabIndex: 0, role: 'region', 'aria-labelledby': ariaLabelledBy || titleId } : null) },
|
|
79
|
+
children),
|
|
80
|
+
actions ? h('footer', { key: 'a', className: 'NeoBottomSheet-actions', 'data-scroll': scroll }, accionesEnOrden) : null,
|
|
81
|
+
);
|
|
82
|
+
});
|
package/ui/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
/// <reference path="../theme/neo.d.ts" />
|
|
10
10
|
import type { ReactNode, ReactElement, ForwardRefExoticComponent, RefAttributes, Ref, ChangeEvent, SyntheticEvent, MouseEvent, HTMLAttributes } from 'react';
|
|
11
11
|
import type { SxProps, Theme, ThemeOptions } from '@mui/material/styles';
|
|
12
|
-
import type { AlertProps, AutocompleteProps, BadgeProps, ButtonProps, CheckboxProps, ChipProps, CircularProgressProps, DialogProps, IconButtonProps, LinkProps, MenuProps, OutlinedInputProps, RadioProps, RadioGroupProps, SelectProps, SnackbarProps, SwitchProps, TabsProps, TooltipProps } from '@mui/material';
|
|
12
|
+
import type { AlertProps, AutocompleteProps, BadgeProps, ButtonProps, CheckboxProps, ChipProps, CircularProgressProps, DialogProps, IconButtonProps, LinkProps, MenuProps, OutlinedInputProps, RadioProps, RadioGroupProps, SelectProps, SnackbarProps, SwipeableDrawerProps, SwitchProps, TabsProps, TooltipProps } from '@mui/material';
|
|
13
13
|
|
|
14
14
|
/** A handler checked bivariantly, the way React types its own event handlers: one typed with MUI's type still fits. */
|
|
15
15
|
type NeoHandler<A extends unknown[]> = { bivarianceHack(...args: A): void }['bivarianceHack'];
|
|
@@ -103,6 +103,27 @@ export interface NeoBannerProps extends Partial<Omit<HTMLAttributes<HTMLElement>
|
|
|
103
103
|
}
|
|
104
104
|
export declare const NeoBanner: ForwardRefExoticComponent<NeoBannerProps & ({ title: ReactNode } | { 'aria-label': string } | { 'aria-labelledby': string }) & RefAttributes<HTMLElement>>;
|
|
105
105
|
|
|
106
|
+
/** Rises from the bottom edge on mobile with complementary content that can wait, or a short task, while the person can still see and return to the page underneath. NO: If the content demands a decision, or reports an error the person has to resolve, it is `modal-dialog`. If it is a long task or dense information, it is `modal-fullscreen`. On desktop the same content moves to a lateral drawer: the sheet is mobile-only. */
|
|
107
|
+
export interface NeoBottomSheetProps extends Partial<Omit<SwipeableDrawerProps, 'open' | 'onClose' | 'title' | 'image' | 'actions' | 'children' | 'className' | 'sx' | 'ref'>> {
|
|
108
|
+
/** The panel or the bubble, open (controlled). */
|
|
109
|
+
open?: boolean;
|
|
110
|
+
/** The close handler. Its presence draws the ✕. */
|
|
111
|
+
onClose?: (...args: unknown[]) => void;
|
|
112
|
+
/** The title. It is a prop and not content because `aria-labelledby` anchors to it. */
|
|
113
|
+
title?: ReactNode;
|
|
114
|
+
/** */
|
|
115
|
+
image?: ReactNode;
|
|
116
|
+
/** The call to action. One at most (C1). */
|
|
117
|
+
actions?: ReactNode;
|
|
118
|
+
/** The content. Whoever uses the component provides it; in the stories it comes from the minimal example. */
|
|
119
|
+
children?: ReactNode;
|
|
120
|
+
/** Classes from the consumer. They merge with the component ones; they do not replace them. */
|
|
121
|
+
className?: string;
|
|
122
|
+
/** The MUI `sx`: per-instance styles, with access to the theme. */
|
|
123
|
+
sx?: SxProps<Theme>;
|
|
124
|
+
}
|
|
125
|
+
export declare const NeoBottomSheet: ForwardRefExoticComponent<NeoBottomSheetProps & ({ title: ReactNode } | { 'aria-label': string } | { 'aria-labelledby': string }) & RefAttributes<HTMLElement>>;
|
|
126
|
+
|
|
106
127
|
/** Runs actions. NO: To navigate it is `link`; for an on/off option it is `toggle`. */
|
|
107
128
|
export interface NeoButtonProps extends Partial<Omit<ButtonProps, 'family' | 'variant' | 'surface' | 'size' | 'label' | 'iconLeft' | 'iconRight' | 'loading' | 'disabled' | 'children' | 'className' | 'sx' | 'color' | 'ref'>> {
|
|
108
129
|
/** semantic family Default: 'system'. */
|
package/ui/index.mjs
CHANGED
|
@@ -19,6 +19,7 @@ export { NeoEmptyState } from './EmptyState.mjs';
|
|
|
19
19
|
export { NeoMenu } from './Menu.mjs';
|
|
20
20
|
export { NeoModalDialog } from './ModalDialog.mjs';
|
|
21
21
|
export { NeoModalFullscreen } from './ModalFullscreen.mjs';
|
|
22
|
+
export { NeoBottomSheet } from './BottomSheet.mjs';
|
|
22
23
|
export { NeoTabs } from './Tabs.mjs';
|
|
23
24
|
export { NeoBadge } from './Badge.mjs';
|
|
24
25
|
export { NeoTag } from './Tag.mjs';
|