@neo-design/mui 1.0.0-rc.7 → 1.0.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 CHANGED
@@ -14,10 +14,10 @@ entry point.
14
14
 
15
15
  ## Install
16
16
 
17
- While the version is a release candidate, install it with the `next` tag, together with the font:
17
+ Install it together with the font:
18
18
 
19
19
  ```sh
20
- npm i @neo-design/foundations@next @neo-design/mui@next @fontsource/noto-sans
20
+ npm i @neo-design/foundations @neo-design/mui @fontsource/noto-sans
21
21
  ```
22
22
 
23
23
  npm brings React, Material UI and Emotion as peers, in the versions this package supports —the `peerDependencies`
@@ -25,7 +25,7 @@ of [`package.json`](package.json)—. **With pnpm**, which does not let your app
25
25
  itself, name them:
26
26
 
27
27
  ```sh
28
- pnpm add @neo-design/foundations@next @neo-design/mui@next @fontsource/noto-sans \
28
+ pnpm add @neo-design/foundations @neo-design/mui @fontsource/noto-sans \
29
29
  @mui/material@6 @emotion/react @emotion/styled react react-dom
30
30
  ```
31
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neo-design/mui",
3
- "version": "1.0.0-rc.7",
3
+ "version": "1.0.0",
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.7"
32
+ "@neo-design/foundations": "1.0.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@emotion/react": "^11",
@@ -461,6 +461,52 @@ export const components = {
461
461
  '& .MuiButton-root': { inlineSize: '100%' },
462
462
  '&[data-scroll="top"], &[data-scroll="mid"]': { borderBlockStart: '1px solid var(--border--base--secondary)' },
463
463
  },
464
+
465
+ '&.MuiDrawer-paperAnchorRight': {
466
+ backgroundColor: 'var(--surface--base--default)',
467
+ inlineSize: '540px', maxInlineSize: '100vw', blockSize: '100dvh', borderRadius: 0,
468
+ display: 'flex', flexDirection: 'column', alignItems: 'flex-start',
469
+ '& > *': { alignSelf: 'stretch' },
470
+ '&:focus': { outline: 0 },
471
+ },
472
+ '& .NeoDrawer-header': {
473
+ display: 'flex', flexDirection: 'column', flex: '0 0 auto',
474
+ justifyContent: 'center', alignItems: 'flex-end',
475
+ '& > *': { alignSelf: 'stretch' },
476
+ paddingInline: 'var(--neo-space-xl-rem)', paddingBlock: 'var(--neo-space-lg-rem)', gap: 'var(--neo-space-md-rem)',
477
+ },
478
+ '&[data-scroll="top"] .NeoDrawer-header, &[data-scroll="mid"] .NeoDrawer-header, &[data-scroll="bottom"] .NeoDrawer-header': {
479
+ borderBlockEnd: '1px solid var(--border--base--secondary)',
480
+ },
481
+ '& .NeoDrawer-headerRow': {
482
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
483
+ blockSize: '48px', gap: 'var(--neo-space-none-rem)',
484
+ },
485
+ '& .NeoDrawer-title': {
486
+ ...theme.typography.titleSmBold, color: 'var(--text--base--default)',
487
+ flex: '1 1 auto', minInlineSize: 0, margin: 0,
488
+ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
489
+ },
490
+ '& .NeoDrawer-content': {
491
+ ...theme.typography.bodyLgRegular, color: 'var(--text--base--default)',
492
+ flex: '1 1 auto', overflowY: 'auto',
493
+ paddingInline: 'var(--neo-space-xl-rem)', paddingBlock: `0 var(--neo-space-lg-rem)`,
494
+ '&:focus-visible': { outline: 'none', boxShadow: focusRingInset.boxShadow },
495
+ },
496
+ '& .NeoDrawer-image': {
497
+ blockSize: '224px', overflow: 'hidden',
498
+ marginInline: 'calc(-1 * var(--neo-space-xl-rem))',
499
+ '& > img, & > picture, & > video': { inlineSize: '100%', blockSize: '100%', objectFit: 'cover', display: 'block' },
500
+ },
501
+ '& .NeoDrawer-actions': {
502
+ display: 'flex', flexDirection: 'column', gap: 'var(--neo-space-md-rem)', flex: '0 0 auto',
503
+ paddingInline: 'var(--neo-space-xl-rem)',
504
+ paddingBlock: `var(--neo-space-lg-rem) var(--neo-space-2xl-rem)`,
505
+ backgroundColor: 'var(--surface--base--default)',
506
+ '& > div': { display: 'contents' },
507
+ '& .MuiButton-root': { inlineSize: '100%' },
508
+ '&[data-scroll="top"], &[data-scroll="mid"]': { borderBlockStart: '1px solid var(--border--base--secondary)' },
509
+ },
464
510
  }),
465
511
  },
466
512
  },
package/ui/Drawer.mjs ADDED
@@ -0,0 +1,14 @@
1
+ 'use client';
2
+ // packages/mui/ui/Drawer.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 { DrawerBase } from './_sheet-base.mjs';
6
+ const h = React.createElement;
7
+
8
+ export const NeoDrawer = React.forwardRef(function NeoDrawer(rawProps, ref) {
9
+ const { open, onClose, title, image, actions, children, ...rest } = withAliases('NeoDrawer', rawProps);
10
+ return h(DrawerBase, {
11
+ open, onClose, title, image, actions, closable: true,
12
+ ...rest, ref,
13
+ }, children);
14
+ });
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  // packages/mui/ui/_sheet-base.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
3
3
  import React from 'react';
4
- import { SwipeableDrawer } from '@mui/material';
4
+ import { SwipeableDrawer, Drawer } from '@mui/material';
5
5
  import { NeoButtonIcon } from './ButtonIcon.mjs';
6
6
  import { renderIcon } from './_icon.mjs';
7
7
  import { useScrollState } from './_scroll-state.mjs';
@@ -80,3 +80,60 @@ export const BottomSheetBase = React.forwardRef(function BottomSheetBase({
80
80
  actions ? h('footer', { key: 'a', className: 'NeoBottomSheet-actions', 'data-scroll': scroll }, accionesEnOrden) : null,
81
81
  );
82
82
  });
83
+
84
+
85
+ export const DrawerBase = React.forwardRef(function DrawerBase({
86
+ open, onClose, title, image, actions, children, closable = true,
87
+ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, ...rest
88
+ }, ref) {
89
+ const auto = React.useId();
90
+ const titleId = title ? `neo-drawer-${auto}-title` : undefined;
91
+
92
+ if (process.env.NODE_ENV !== 'production') {
93
+ if (!title) console.warn('[Neo] Drawer: NeoDrawer needs `title`: it is the drawer\'s name for a screen reader, and the header always shows it.');
94
+ if (!onClose) console.warn('[Neo] Drawer: NeoDrawer needs `onClose`: without it the drawer has no ✕ and no way out.');
95
+ }
96
+
97
+ const { setRegion, watchScroll, scroll, overflows } = useScrollState([open, children]);
98
+ const accionesEnOrden = actions ? unaPorUna(actions).reverse() : actions;
99
+
100
+ const close = closable && onClose
101
+ ? h(NeoButtonIcon, { key: 'x', onClick: onClose, 'aria-label': 'Cerrar', variant: 'tertiary', surface: 'default',
102
+ size: 'large', className: 'NeoDrawer-close' },
103
+ renderIcon('system-close'))
104
+ : null;
105
+
106
+ const header = h('header', { key: 'hdr', className: 'NeoDrawer-header' },
107
+ h('div', { className: 'NeoDrawer-headerRow' },
108
+ title ? h('h2', { id: titleId, className: 'NeoDrawer-title' }, title) : null,
109
+ close));
110
+
111
+ return h(Drawer, {
112
+ ref,
113
+ anchor: 'right',
114
+ open: !!open,
115
+ onClose,
116
+ slotProps: { backdrop: { 'aria-hidden': 'true' } },
117
+ className,
118
+ ...rest,
119
+ PaperProps: {
120
+ ...rest.PaperProps,
121
+ className: ['NeoDrawer', rest.PaperProps?.className].filter(Boolean).join(' '),
122
+ 'data-scroll': scroll,
123
+ role: 'dialog',
124
+ 'aria-modal': 'true',
125
+ 'aria-labelledby': ariaLabelledBy || titleId,
126
+ 'aria-label': (ariaLabelledBy || titleId) ? undefined : ariaLabel,
127
+ tabIndex: -1,
128
+ },
129
+ ...(rest.slotProps ? { slotProps: { backdrop: { 'aria-hidden': 'true' }, ...rest.slotProps } } : {}),
130
+ },
131
+ header,
132
+ h('div', { key: 'c', className: 'NeoDrawer-content', ref: setRegion, onScroll: watchScroll,
133
+ 'data-scroll': scroll,
134
+ ...(overflows ? { tabIndex: 0, role: 'region', 'aria-labelledby': ariaLabelledBy || titleId } : null) },
135
+ image ? h('div', { key: 'img', className: 'NeoDrawer-image' }, image) : null,
136
+ children),
137
+ actions ? h('footer', { key: 'a', className: 'NeoDrawer-actions', 'data-scroll': scroll }, accionesEnOrden) : null,
138
+ );
139
+ });
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, SwipeableDrawerProps, SwitchProps, TabsProps, TooltipProps } from '@mui/material';
12
+ import type { AlertProps, AutocompleteProps, BadgeProps, ButtonProps, CheckboxProps, ChipProps, CircularProgressProps, DialogProps, DrawerProps, 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'];
@@ -274,6 +274,27 @@ export interface NeoComboboxProps<T = string, Multiple extends boolean | undefin
274
274
  }
275
275
  export declare const NeoCombobox: <T = string, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false>(props: NeoComboboxProps<T, Multiple, DisableClearable, FreeSolo> & ({ label: ReactNode } | { 'aria-label': string } | { 'aria-labelledby': string }) & RefAttributes<HTMLElement>) => ReactElement | null;
276
276
 
277
+ /** Enters from the right edge on the web with complementary or extensive content that the person opens on demand, while the page underneath stays in view. 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 with steps, it is `modal-fullscreen`. On mobile the same content goes in a `bottom-sheet`: the drawer is web-only. */
278
+ export interface NeoDrawerProps extends Partial<Omit<DrawerProps, 'open' | 'onClose' | 'title' | 'image' | 'actions' | 'children' | 'className' | 'sx' | 'ref'>> {
279
+ /** The panel or the bubble, open (controlled). */
280
+ open?: boolean;
281
+ /** The close handler. Its presence draws the ✕. */
282
+ onClose?: (...args: unknown[]) => void;
283
+ /** The title. It is a prop and not content because `aria-labelledby` anchors to it. */
284
+ title: ReactNode;
285
+ /** */
286
+ image?: ReactNode;
287
+ /** The call to action. One at most (C1). */
288
+ actions?: ReactNode;
289
+ /** The content. Whoever uses the component provides it; in the stories it comes from the minimal example. */
290
+ children?: ReactNode;
291
+ /** Classes from the consumer. They merge with the component ones; they do not replace them. */
292
+ className?: string;
293
+ /** The MUI `sx`: per-instance styles, with access to the theme. */
294
+ sx?: SxProps<Theme>;
295
+ }
296
+ export declare const NeoDrawer: ForwardRefExoticComponent<NeoDrawerProps & RefAttributes<HTMLElement>>;
297
+
277
298
  /** Takes the place of content that does not exist yet and says what can be done about it. NO: To communicate something that just happened, which is `alert`. For the wait while data loads, which is `spinner`: empty and loading are not the same, and mixing them up leaves the person waiting for something that is not coming. */
278
299
  export interface NeoEmptyStateProps extends Partial<Omit<HTMLAttributes<HTMLElement>, 'icon' | 'title' | 'description' | 'actions' | 'headingLevel' | 'className' | 'ref'>> {
279
300
  /** The icon node. Use the system icons (icons/), not loose SVGs. */
package/ui/index.mjs CHANGED
@@ -20,6 +20,7 @@ export { NeoMenu } from './Menu.mjs';
20
20
  export { NeoModalDialog } from './ModalDialog.mjs';
21
21
  export { NeoModalFullscreen } from './ModalFullscreen.mjs';
22
22
  export { NeoBottomSheet } from './BottomSheet.mjs';
23
+ export { NeoDrawer } from './Drawer.mjs';
23
24
  export { NeoTabs } from './Tabs.mjs';
24
25
  export { NeoBadge } from './Badge.mjs';
25
26
  export { NeoTag } from './Tag.mjs';