@neo-design/mui 1.0.0-rc.4

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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/package.json +38 -0
  4. package/theme/alert-icons.mjs +16 -0
  5. package/theme/baseline.mjs +17 -0
  6. package/theme/checkbox-icons.mjs +12 -0
  7. package/theme/chip-icon.mjs +8 -0
  8. package/theme/components.mjs +982 -0
  9. package/theme/custom-css.mjs +44 -0
  10. package/theme/index.d.ts +9 -0
  11. package/theme/index.mjs +7 -0
  12. package/theme/locale.mjs +11 -0
  13. package/theme/neo.d.ts +105 -0
  14. package/theme/select-icon.mjs +8 -0
  15. package/theme/slices.mjs +37 -0
  16. package/ui/Alert.mjs +39 -0
  17. package/ui/AspectRatio.mjs +26 -0
  18. package/ui/Badge.mjs +39 -0
  19. package/ui/Banner.mjs +57 -0
  20. package/ui/Button.mjs +31 -0
  21. package/ui/ButtonIcon.mjs +22 -0
  22. package/ui/Checkbox.mjs +30 -0
  23. package/ui/Chips.mjs +16 -0
  24. package/ui/ChipsGroup.mjs +53 -0
  25. package/ui/Combobox.mjs +41 -0
  26. package/ui/EmptyState.mjs +31 -0
  27. package/ui/Link.mjs +11 -0
  28. package/ui/Menu.mjs +73 -0
  29. package/ui/ModalDialog.mjs +14 -0
  30. package/ui/ModalFullscreen.mjs +20 -0
  31. package/ui/RadioButton.mjs +23 -0
  32. package/ui/RadioGroup.mjs +16 -0
  33. package/ui/Select.mjs +35 -0
  34. package/ui/Snackbar.mjs +29 -0
  35. package/ui/Spinner.mjs +12 -0
  36. package/ui/Tabs.mjs +46 -0
  37. package/ui/Tag.mjs +16 -0
  38. package/ui/TextArea.mjs +45 -0
  39. package/ui/TextField.mjs +47 -0
  40. package/ui/Toggle.mjs +27 -0
  41. package/ui/Tooltip.mjs +20 -0
  42. package/ui/_alias-props.mjs +26 -0
  43. package/ui/_css-layers.mjs +15 -0
  44. package/ui/_help.mjs +25 -0
  45. package/ui/_icon.mjs +43 -0
  46. package/ui/_inner-input.mjs +7 -0
  47. package/ui/_keyboard-focus.mjs +24 -0
  48. package/ui/_modal-base.mjs +95 -0
  49. package/ui/_overflow.mjs +21 -0
  50. package/ui/_reveal.mjs +17 -0
  51. package/ui/_select-value.mjs +18 -0
  52. package/ui/_warn.mjs +13 -0
  53. package/ui/index.d.ts +693 -0
  54. package/ui/index.mjs +35 -0
package/ui/_help.mjs ADDED
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+ // packages/mui/ui/_help.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
3
+ import React from 'react';
4
+ import { NeoTooltip } from './Tooltip.mjs';
5
+ import { renderIcon } from './_icon.mjs';
6
+ import { warn } from './_warn.mjs';
7
+
8
+ const h = React.createElement;
9
+
10
+ export const HELP_ICON = 'semantic-info';
11
+
12
+ export const helpIcon = ({ text, name, label, comp, prefix }) => {
13
+ if (!text) return null;
14
+ const derived = typeof label === 'string' && label.trim() ? `Ayuda sobre ${label.trim()}` : null;
15
+ const accessible = name || derived;
16
+ if (!accessible) {
17
+ warn(`${comp}: the help ⓘ has no accessible name — its \`label\` is not text and no `
18
+ + '`tooltipLabel` was passed. The button has no name for a screen reader.');
19
+ }
20
+ return h(NeoTooltip, { label: text, describeChild: true },
21
+ h('button', {
22
+ type: 'button', className: `${prefix}__icon--tooltip`,
23
+ 'aria-label': accessible || undefined, title: undefined,
24
+ }, renderIcon(HELP_ICON)));
25
+ };
package/ui/_icon.mjs ADDED
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+ // packages/mui/ui/_icon.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
3
+ import React from 'react';
4
+ import { ALIASES as ICON_ALIASES } from '@neo-design/foundations/icon-aliases.mjs';
5
+
6
+ const warned = new Set();
7
+ const check = (id) => {
8
+ if (process.env.NODE_ENV === 'production') return;
9
+ if (typeof document === 'undefined' || warned.has(id)) return;
10
+ queueMicrotask(() => {
11
+ if (warned.has(id) || document.getElementById(id)) return;
12
+ warned.add(id);
13
+ const hasSprite = !!document.querySelector('symbol[id], svg > defs symbol');
14
+ console.warn(hasSprite
15
+ ? `[Neo] icon: '#${id}' is not in the sprite. The 164 ids are in packages/foundations/icons/index.json — an id is <family>-<name>, e.g. #system-mail.`
16
+ : `[Neo] icon: '#${id}' does not draw and the sprite is not in the document. Inject it once, as high in the tree as possible — docs/dev/INTEGRATION.md §3.`);
17
+ });
18
+ };
19
+
20
+ const warnedAliases = new Set();
21
+ const translate = (id) => {
22
+ const newName = ICON_ALIASES[id];
23
+ if (!newName || newName === id) return id;
24
+ if (process.env.NODE_ENV !== 'production') {
25
+ if (!warnedAliases.has(id)) {
26
+ warnedAliases.add(id);
27
+ console.warn(`[Neo] icon: '#${id}' is now called '#${newName}', since the move to English. It still draws in this version and stops in the next one.`);
28
+ }
29
+ }
30
+ return newName;
31
+ };
32
+
33
+ export const renderIcon = (x, extraClass) => {
34
+ if (typeof x === 'string' && x) {
35
+ const id = translate(x.startsWith('#') ? x.slice(1) : x);
36
+ check(id);
37
+ return React.createElement('svg', { className: extraClass ? `icon ${extraClass}` : 'icon', 'aria-hidden': 'true' },
38
+ React.createElement('use', { href: `#${id}` }));
39
+ }
40
+ return extraClass && React.isValidElement(x)
41
+ ? React.cloneElement(x, { className: [x.props.className, extraClass].filter(Boolean).join(' ') })
42
+ : x;
43
+ };
@@ -0,0 +1,7 @@
1
+ // packages/mui/ui/_inner-input.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
2
+ import { version as muiVersion } from '@mui/material';
3
+
4
+ const MAJOR = Number(String(muiVersion).split('.')[0]) || 5;
5
+
6
+ export const innerInputProps = (attrs) =>
7
+ MAJOR >= 6 ? { slotProps: { input: attrs } } : { inputProps: attrs };
@@ -0,0 +1,24 @@
1
+ 'use client';
2
+ // packages/mui/ui/_keyboard-focus.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
3
+ import React from 'react';
4
+
5
+ let keyboard = true;
6
+ let listening = false;
7
+ const listen = () => {
8
+ if (listening || typeof document === 'undefined') return;
9
+ listening = true;
10
+ document.addEventListener('keydown', (e) => { if (!e.metaKey && !e.altKey && !e.ctrlKey) keyboard = true; }, true);
11
+ for (const type of ['pointerdown', 'mousedown', 'touchstart']) document.addEventListener(type, () => { keyboard = false; }, true);
12
+ };
13
+ const isText = (el) => Boolean(el) && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA');
14
+
15
+ export function useKeyboardFocus() {
16
+ const [active, setActive] = React.useState(false);
17
+ React.useEffect(listen, []);
18
+ return {
19
+ active,
20
+ onFocus: (e) => { if (isText(e.target)) setActive(keyboard); },
21
+ onBlur: (e) => { if (isText(e.target)) setActive(false); },
22
+ onPointerDown: () => setActive(false),
23
+ };
24
+ }
@@ -0,0 +1,95 @@
1
+ 'use client';
2
+ // packages/mui/ui/_modal-base.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
3
+ import React from 'react';
4
+ import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, useMediaQuery } from '@mui/material';
5
+ import { NeoButtonIcon } from './ButtonIcon.mjs';
6
+ import { renderIcon } from './_icon.mjs';
7
+ const h = React.createElement;
8
+
9
+ const ICON_BY_TYPE = {
10
+ info: 'semantic-info', success: 'semantic-success', warning: 'semantic-warning',
11
+ error: 'semantic-error', brand: 'brand-sparkles',
12
+ };
13
+
14
+
15
+ const unaPorUna = (acciones) => {
16
+ const lista = React.Children.toArray(acciones);
17
+ return lista.length === 1 && lista[0].type === React.Fragment ? React.Children.toArray(lista[0].props.children) : lista;
18
+ };
19
+
20
+ export const ModalDialogBase = React.forwardRef(function ModalDialogBase({
21
+ open, onClose, title, description, size = 'xs', type = 'default', closable = true,
22
+ actions, children, back, fullScreen = false, ...rest
23
+ }, ref) {
24
+ const auto = React.useId();
25
+
26
+ const angosto = useMediaQuery('(max-width: 719px)');
27
+ const apiladas = fullScreen ? angosto : size === 'xs';
28
+ const accionesEnOrden = apiladas && actions ? unaPorUna(actions).reverse() : actions;
29
+
30
+ const contentRef = React.useRef(null);
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]);
55
+ const titleId = `neo-modal-${auto}-title`;
56
+ const descriptionId = description ? `neo-modal-${auto}-description` : undefined;
57
+ const rowId = `neo-modal-${auto}-row`;
58
+
59
+ if (process.env.NODE_ENV !== 'production') {
60
+ if (!title) console.warn('[Neo] Modal: the title is required — it is the accessible name of the dialog.');
61
+ }
62
+
63
+ const iconNode = ICON_BY_TYPE[type];
64
+
65
+ const close = closable && onClose
66
+ ? h(NeoButtonIcon, { key: 'x', onClick: onClose, 'aria-label': 'Cerrar', variant: 'tertiary',
67
+ className: fullScreen ? 'NeoModalFullscreen-close' : 'NeoModalDialog-close',
68
+ size: fullScreen ? 'large' : 'medium' },
69
+ renderIcon('system-close'))
70
+ : null;
71
+
72
+ return h(Dialog, {
73
+ ref,
74
+ open: !!open, onClose, fullScreen,
75
+ PaperProps: { 'data-type': type },
76
+ maxWidth: fullScreen ? false : size,
77
+ fullWidth: !fullScreen,
78
+ slotProps: { backdrop: { 'aria-hidden': 'true' } },
79
+ 'aria-labelledby': titleId,
80
+ 'aria-describedby': descriptionId,
81
+
82
+ ...rest,
83
+ },
84
+ h(DialogTitle, { key: 't', id: rowId }, iconNode ? renderIcon(iconNode) : null,
85
+ back || null,
86
+ h('span', { id: titleId, className: fullScreen ? 'NeoModalFullscreen-title' : 'NeoModalDialog-title' }, title),
87
+ fullScreen ? close : null),
88
+ fullScreen ? null : close,
89
+ h(DialogContent, { key: 'c', ref: setContent, onScroll: watchScroll, 'data-scroll': scroll,
90
+ ...(overflows ? { tabIndex: 0, role: 'region', 'aria-labelledby': titleId } : null) },
91
+ description ? h(DialogContentText, { id: descriptionId, key: 'd' }, description) : null,
92
+ children),
93
+ actions ? h(DialogActions, { key: 'a', 'data-scroll': scroll }, accionesEnOrden) : null,
94
+ );
95
+ });
@@ -0,0 +1,21 @@
1
+ // packages/mui/ui/_overflow.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
2
+ export function watchOverflow(root, member) {
3
+ if (!root || typeof window === 'undefined') return () => {};
4
+ const item = member ? root.querySelector(member) : null;
5
+ const recorta = (n) => getComputedStyle(n).overflowX !== 'visible';
6
+ let row = item ? item.parentElement : null;
7
+ while (row && row !== root && !recorta(row)) row = row.parentElement;
8
+ if (!row || !recorta(row)) return () => {};
9
+ const mark = () => {
10
+ const start = row.scrollLeft > 1;
11
+ const end = row.scrollLeft + row.clientWidth < row.scrollWidth - 1;
12
+ const sides = [start && 'start', end && 'end'].filter(Boolean).join(' ');
13
+ if (sides) root.setAttribute('data-overflow', sides);
14
+ else root.removeAttribute('data-overflow');
15
+ };
16
+ mark();
17
+ row.addEventListener('scroll', mark, { passive: true });
18
+ const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(mark) : null;
19
+ if (ro) { ro.observe(row); if (item.parentElement !== row) ro.observe(item.parentElement); }
20
+ return () => { row.removeEventListener('scroll', mark); if (ro) ro.disconnect(); };
21
+ }
package/ui/_reveal.mjs ADDED
@@ -0,0 +1,17 @@
1
+ // packages/mui/ui/_reveal.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
2
+ export function reveal(node, member, { inline = 'center', settle = false } = {}) {
3
+ const item = node && node.closest ? node.closest(member) : null;
4
+ let row = item ? item.parentElement : null;
5
+ while (row && !/auto|scroll/.test(getComputedStyle(row).overflowX)) row = row.parentElement;
6
+ if (!row || row.scrollWidth <= row.clientWidth) return;
7
+ if (settle) {
8
+ const a = item.getBoundingClientRect();
9
+ const start = row.getBoundingClientRect().left + row.clientLeft;
10
+ const end = start + row.clientWidth;
11
+ if (a.right > end) row.scrollLeft += a.right - end + 1;
12
+ else if (a.left < start) row.scrollLeft -= start - a.left + 1;
13
+ }
14
+ const quiet = typeof window !== 'undefined' && window.matchMedia
15
+ && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
16
+ item.scrollIntoView({ inline, block: 'nearest', behavior: quiet ? 'auto' : 'smooth' });
17
+ }
@@ -0,0 +1,18 @@
1
+ 'use client';
2
+ // packages/mui/ui/_select-value.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
3
+ import React from 'react';
4
+ const h = React.createElement;
5
+
6
+ export const selectValueProps = ({ placeholder, renderValue, children, value, defaultValue }) => ({
7
+ displayEmpty: !!placeholder,
8
+ ...(value === undefined && defaultValue === undefined ? { defaultValue: '' } : {}),
9
+ renderValue: placeholder
10
+ ? (val) => {
11
+ if (val === '' || val == null) return h('span', { className: 'NeoSelect-placeholder' }, placeholder);
12
+ if (renderValue) return renderValue(val);
13
+ const selectedOption = React.Children.toArray(children)
14
+ .find((c) => React.isValidElement(c) && c.props.value === val);
15
+ return selectedOption ? selectedOption.props.children : val;
16
+ }
17
+ : renderValue,
18
+ });
package/ui/_warn.mjs ADDED
@@ -0,0 +1,13 @@
1
+ // packages/mui/ui/_warn.mjs — emitted without comments. The reasoning behind each decision lives in Neo's source repository.
2
+
3
+ export const warn = (m) => {
4
+ if (process.env.NODE_ENV !== 'production') console.warn('[Neo] ' + m);
5
+ };
6
+
7
+ export const hasAccessibleName = (comp, label, rest = {}) => {
8
+ const named = (label !== undefined && label !== null && String(label).trim() !== '')
9
+ || !!rest['aria-label'] || !!rest['aria-labelledby'];
10
+ if (!named) warn(`${comp}: no visible \`label\` and no \`aria-label\` — the control has NO ACCESSIBLE `
11
+ + 'NAME. It looks the same and cannot be operated with a screen reader.');
12
+ return named;
13
+ };