material-plus-ui 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CDGet <jooy2.contact@gmail.com> (https://cdget.com).
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # Material Plus
2
+
3
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jooy2/material-plus/blob/main/LICENSE) ![Programming Language Usage](https://img.shields.io/github/languages/top/jooy2/material-plus) [![npm downloads](https://img.shields.io/npm/dm/material-plus-ui.svg)](https://www.npmjs.com/package/material-plus-ui) [![npm latest package](https://img.shields.io/npm/v/material-plus-ui/latest.svg)](https://www.npmjs.com/package/material-plus-ui) ![npm bundle size](https://img.shields.io/bundlephobia/min/material-plus-ui)
4
+
5
+ ### πŸ“˜ [**material-plus.cdget.com**](https://material-plus.cdget.com)
6
+
7
+ Live previews and full props for every component. This README is just the quick start.
8
+
9
+ ---
10
+
11
+ **Material Plus is a React component library that extends Material UI.** It is the pile of components you end up writing in every MUI project β€” the field that handles IME composition properly, the icon wrapper that agrees with your set β€” extracted, documented and tested.
12
+
13
+ It is not a replacement for `@mui/material` and does not try to be one. Every component is built out of MUI, reads your `ThemeProvider`, and sits at the same height as the MUI control next to it in the same form.
14
+
15
+ - **MUI is a peer, not a copy.** `@mui/material` and Emotion stay your dependencies, so there is never a second MUI in the bundle or a second theme in the tree.
16
+ - **IME-safe by construction.** Korean, Japanese and Chinese composition survives whatever your `onChange` does with the value.
17
+ - **Bring your own icons.** `MPIcon` takes a component or an element from any set. `lucide-react` ships with the package, gathered in one readable constants file.
18
+ - **ESM only**, TypeScript declarations included, tree-shakeable β€” every component compiles to its own module.
19
+ - **One runtime dependency.** React 18 or 19, Node.js 18 or later.
20
+
21
+ ## Documentation
22
+
23
+ Everything is documented at **[material-plus.cdget.com](https://material-plus.cdget.com)**, where the previews are not screenshots β€” they are the components, running in the page.
24
+
25
+ | Page | What you will find |
26
+ | --- | --- |
27
+ | [**Getting started**](https://material-plus.cdget.com/guide/getting-started) | Install and setup, end to end. |
28
+ | [**All components**](https://material-plus.cdget.com/components/) | Every component, one page each: live previews and the full props table. |
29
+ | [**Changelog**](https://material-plus.cdget.com/changelog) | What changed in each release. |
30
+
31
+ Also available in Korean / ν•œκ΅­μ–΄ λ¬Έμ„œ: **[material-plus.cdget.com/ko/](https://material-plus.cdget.com/ko/)**
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ npm install material-plus-ui
37
+ ```
38
+
39
+ ```bash
40
+ pnpm add material-plus-ui
41
+ ```
42
+
43
+ ### Peer dependencies
44
+
45
+ You already have all of these in an MUI project:
46
+
47
+ | Package | Versions |
48
+ | ----------------------------------- | ------------ |
49
+ | `@mui/material` | 6, 7, 8 or 9 |
50
+ | `@emotion/react`, `@emotion/styled` | 11 |
51
+ | `react`, `react-dom` | 18 or 19 |
52
+
53
+ Only MUI 9 is exercised in CI, though the components use APIs stable since MUI 5.
54
+
55
+ ### Setup
56
+
57
+ Add one line to your app's CSS entry point:
58
+
59
+ ```css
60
+ @import 'material-plus-ui/styles.css';
61
+ ```
62
+
63
+ This is finished CSS β€” no PostCSS plugin, no `@source`, no build-side configuration. It deliberately contains **no reset**: `CssBaseline` from `@mui/material` is already one, and Tailwind's Preflight would restyle every MUI component on your page rather than only the ones from here.
64
+
65
+ If Tailwind v4 is already in your project, import the token sheet instead:
66
+
67
+ ```css
68
+ @import 'tailwindcss';
69
+ @import 'material-plus-ui/tailwind.css';
70
+ ```
71
+
72
+ ## Usage
73
+
74
+ Material Plus components go anywhere an MUI component goes, inside the same provider.
75
+
76
+ ```tsx
77
+ import { useState } from 'react';
78
+ import CssBaseline from '@mui/material/CssBaseline';
79
+ import { ThemeProvider, createTheme } from '@mui/material/styles';
80
+ import { MPTextField, MPIcon, ICONS } from 'material-plus-ui';
81
+
82
+ const theme = createTheme({ palette: { mode: 'light' } });
83
+
84
+ export default function App() {
85
+ const [email, setEmail] = useState('');
86
+
87
+ return (
88
+ <ThemeProvider theme={theme}>
89
+ <CssBaseline />
90
+ <MPTextField
91
+ label="Email"
92
+ type="email"
93
+ value={email}
94
+ onChange={setEmail}
95
+ startIcon={<MPIcon icon={ICONS.search} size={18} />}
96
+ />
97
+ </ThemeProvider>
98
+ );
99
+ }
100
+ ```
101
+
102
+ ## Components
103
+
104
+ | Component | What it is |
105
+ | --- | --- |
106
+ | [`MPTextField`](https://material-plus.cdget.com/components/inputs/text-field) | A text field that survives an IME, with the label, helper text, adornments and password toggle already assembled. |
107
+ | [`MPIcon`](https://material-plus.cdget.com/components/display/icon) | A glyph at a known size in a known colour, from whichever icon set you use. |
108
+
109
+ ### Why `MPTextField` exists
110
+
111
+ A controlled `<input>` renders from its `value`. While an input method is composing, the browser holds a **provisional** string in the element that has not been committed β€” and writing `value` back over it in that moment throws away the syllable in progress and jumps the caret. Trimming, upper-casing or validating in your `onChange` is enough to trigger it.
112
+
113
+ `MPTextField` stops rendering `value` for the duration of a composition and shows what the element actually contains, while `onChange` keeps firing for every keystroke. When the composition ends, the field is controlled again.
114
+
115
+ ```tsx
116
+ // Upper-cases everything β€” and a Korean syllable still composes correctly.
117
+ <MPTextField value={name} onChange={(next) => setName(next.toUpperCase())} />
118
+ ```
119
+
120
+ ## Contributing
121
+
122
+ Issues and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
123
+
124
+ ```bash
125
+ npm install # also builds, via `prepare`
126
+ npm run test # vitest, in a real browser
127
+ npm run typecheck # src, test and docs
128
+ npm run docs:dev # the documentation site
129
+ ```
130
+
131
+ ## License
132
+
133
+ [MIT](LICENSE) Β© [CDGet](https://cdget.com)
@@ -0,0 +1,74 @@
1
+ import * as React from 'react';
2
+ import type { MPIconGlyph } from '../../types';
3
+ export interface MPIconProps extends Omit<React.ComponentPropsWithoutRef<'span'>, 'color'> {
4
+ /**
5
+ * The glyph to draw: a component from an icon set, or an element of your own.
6
+ *
7
+ * It is a prop rather than `children` because the two things you always want
8
+ * to decide about an icon β€” how big it is and what colour it is β€” are the two
9
+ * things you cannot reach once it is a child. As a prop it is content this
10
+ * component *sizes*, not content it merely wraps.
11
+ *
12
+ * ```tsx
13
+ * <MPIcon icon={ICONS.search} /> // a component from an icon set
14
+ * <MPIcon icon={<MySvgLogo />} /> // an element of your own
15
+ * ```
16
+ */
17
+ icon: MPIconGlyph;
18
+ /**
19
+ * The box the glyph is drawn in. A number is CSS pixels; a string is any CSS
20
+ * length, so `'1em'` tracks the surrounding text.
21
+ *
22
+ * Left unset the glyph draws at its own natural size β€” 24px for a lucide
23
+ * icon, whatever the artwork says for an element. That default is deliberate:
24
+ * an icon set already agrees with itself about size, and a wrapper that
25
+ * overrode it would be a second opinion nobody asked for.
26
+ */
27
+ size?: number | string;
28
+ /**
29
+ * The glyph's colour, as any CSS colour. Left unset the icon inherits the
30
+ * colour of whatever it sits in β€” a button's label, a muted caption, an
31
+ * error message β€” which is right far more often than a colour of its own.
32
+ */
33
+ color?: string;
34
+ /**
35
+ * Stroke width, forwarded to glyphs given as a component. Ignored by an
36
+ * element, which is already drawn.
37
+ */
38
+ strokeWidth?: number | string;
39
+ /**
40
+ * Centres the icon in its grid or flex track. Carried over from the pattern
41
+ * this component replaces, where an icon in a table cell or a form row is
42
+ * laid out by its parent and has no other way to say where it sits.
43
+ */
44
+ center?: boolean;
45
+ /**
46
+ * What the icon says, for a reader who cannot see it.
47
+ *
48
+ * Without it the icon is hidden from the accessibility tree, which is the
49
+ * right default: most icons sit beside a word that already says the same
50
+ * thing, and reading both aloud is worse than reading one. Pass this only
51
+ * when the glyph carries the meaning on its own.
52
+ */
53
+ label?: string;
54
+ }
55
+ /**
56
+ * An icon at a known size, in a known colour.
57
+ *
58
+ * Material Plus draws no icons of its own. Which set an application uses is its
59
+ * decision, and one it has usually already made β€” so this component takes
60
+ * whatever that set hands back and gives it the size and colour axes the rest
61
+ * of the library has.
62
+ *
63
+ * The glyph goes in a box, and the box is what does the work: it is
64
+ * `inline-flex`, so an icon lines up with text rather than sitting on the
65
+ * baseline like an image; it carries the `font-size` as well as the width and
66
+ * height, so an `<svg>` sized in `em` comes out the same as one sized in `px`;
67
+ * and it owns the accessibility, so a decorative glyph leaves the tree entirely
68
+ * instead of being announced as "graphic".
69
+ *
70
+ * A component glyph is *also* handed the size and colour directly, because a
71
+ * set like lucide draws to its `size` prop and would otherwise render at its
72
+ * own 24px inside a 16px box.
73
+ */
74
+ export declare const MPIcon: React.ForwardRefExoticComponent<MPIconProps & React.RefAttributes<HTMLSpanElement>>;
@@ -0,0 +1 @@
1
+ import{jsx as e}from"react/jsx-runtime";import*as i from"react";function o(e){return!i.isValidElement(e)}export const MPIcon=i.forwardRef(function({icon:r,size:t,color:l,strokeWidth:n,center:s=!1,label:c,className:a,style:d,...f},m){const u=["mp-icon inline-flex shrink-0 items-center justify-center align-middle",void 0===t?"":"[&>svg]:block [&>svg]:size-full [&>img]:block [&>img]:size-full",s?"justify-self-center":"",a??""].filter(Boolean).join(" ");return e("span",{ref:m,className:u,style:{...void 0===t?{}:{width:t,height:t,fontSize:t},...void 0===l?{}:{color:l},...d},role:c?"img":void 0,"aria-label":c,"aria-hidden":!c||void 0,...f,children:o(r)?i.createElement(r,{size:t,color:void 0===l?"currentColor":l,strokeWidth:n}):r})});
@@ -0,0 +1,2 @@
1
+ export { MPIcon } from './MPIcon';
2
+ export type { MPIconProps } from './MPIcon';
@@ -0,0 +1 @@
1
+ export{MPIcon}from"./MPIcon";
@@ -0,0 +1,130 @@
1
+ import * as React from 'react';
2
+ /** The input modes this field is built for. */
3
+ export type MPTextFieldType = 'email' | 'password' | 'text';
4
+ export interface MPTextFieldProps {
5
+ /**
6
+ * The field's text. This is a controlled component: what is shown is what is
7
+ * passed, except during composition β€” see `onChange`.
8
+ */
9
+ value: string;
10
+ /** Name of the form control, as on a native `<input>`. */
11
+ name?: string;
12
+ /**
13
+ * Which control to draw. `password` is the one that changes behaviour rather
14
+ * than only appearance: it grows a reveal toggle in the trailing adornment.
15
+ * @default 'text'
16
+ */
17
+ type?: MPTextFieldType;
18
+ /** Placeholder shown while the field is empty. */
19
+ placeholder?: string;
20
+ /** Native `autocomplete` token β€” `'email'`, `'current-password'`, `'off'`. */
21
+ autoComplete?: string;
22
+ /**
23
+ * Label above the field. Always drawn shrunk and notched rather than floating
24
+ * over the placeholder, so a field with a label and a field without one sit
25
+ * at the same height in a form.
26
+ */
27
+ label?: string;
28
+ /** Marks the field required, both to assistive technology and to the label. */
29
+ required?: boolean;
30
+ /** Greys the field out and stops it taking input. */
31
+ disabled?: boolean;
32
+ /**
33
+ * Draws the field at MUI's `medium` size instead of `small`.
34
+ * @default false
35
+ */
36
+ large?: boolean;
37
+ /**
38
+ * Focuses the field on mount β€” except on a small screen, where it would
39
+ * summon the on-screen keyboard over the page the reader has just arrived at.
40
+ * @default false
41
+ */
42
+ autoFocus?: boolean;
43
+ /** Stretches the field to the width of its container. */
44
+ fullWidth?: boolean;
45
+ /**
46
+ * Shows the value without allowing edits. Unlike `disabled` the text stays
47
+ * selectable and the field stays in the tab order, which is what you want for
48
+ * a value the reader may need to copy.
49
+ */
50
+ readOnly?: boolean;
51
+ /**
52
+ * Swallows the Enter key instead of letting it insert a newline. Only
53
+ * meaningful with `rows`, since a single-line field has no newline to insert
54
+ * β€” there, Enter is already prevented so it cannot submit a surrounding form
55
+ * twice.
56
+ */
57
+ disableEnterKey?: boolean;
58
+ /**
59
+ * The message under the field. Its presence is also what puts the field into
60
+ * its error state, so there is no way to show an error with no explanation.
61
+ * @default ''
62
+ */
63
+ errorMessage?: string;
64
+ /** Renders a `<textarea>` of this many rows instead of an `<input>`. */
65
+ rows?: number;
66
+ /** Caps the number of characters the field will accept. */
67
+ maxLength?: number;
68
+ /** Content placed before the text β€” an `MPIcon`, usually. */
69
+ startIcon?: React.ReactNode;
70
+ /**
71
+ * Lets the reader drag a multiline field taller. Ignored without `rows`.
72
+ * @default false
73
+ */
74
+ resizable?: boolean;
75
+ /**
76
+ * The id put on the control and pointed at by the label. Derived from `name`
77
+ * when omitted, which is enough as long as no two fields on a page share a
78
+ * name.
79
+ */
80
+ id?: string;
81
+ /**
82
+ * Called with the field's text on every change, including each keystroke of
83
+ * an in-progress composition. The field shows what was actually typed until
84
+ * the composition ends, so a parent is free to normalise, truncate or reject
85
+ * the value it is handed without the caret jumping mid-syllable.
86
+ */
87
+ onChange?: (value: string) => void;
88
+ /**
89
+ * Called when Enter is pressed. On a single-line field Enter is then
90
+ * swallowed, so a form is submitted once rather than also natively.
91
+ */
92
+ onSubmit?: () => void;
93
+ /**
94
+ * Called before every change, ahead of `onChange`. For clearing a
95
+ * form-level error that a further edit has made stale.
96
+ */
97
+ onFormReset?: () => void;
98
+ }
99
+ /**
100
+ * A Material UI text field that survives an IME.
101
+ *
102
+ * The field is `@mui/material`'s `OutlinedInput`, and everything MUI decides
103
+ * about how a field looks β€” the notched outline, the sizes, the palette, the
104
+ * error colour β€” it still decides here. What this component adds is the
105
+ * handling around a controlled input that a language with an input method
106
+ * exposes, plus the parts of a field that are always assembled by hand
107
+ * anyway: the label, the helper text, the adornments and the password toggle.
108
+ *
109
+ * ## The composition problem
110
+ *
111
+ * A controlled `<input>` is rendered from its `value` prop. While an IME is
112
+ * composing β€” Korean, Japanese, Chinese, and dead-key sequences in several
113
+ * European layouts β€” the browser is holding a *provisional* string in the
114
+ * element that has not been committed yet. Writing a `value` back over it in
115
+ * that moment destroys the composition: the syllable in progress is thrown
116
+ * away, and the caret jumps. Anything the parent does to the value in its
117
+ * `onChange` β€” trimming, upper-casing, validating, or simply re-rendering
118
+ * slowly β€” is enough to trigger it.
119
+ *
120
+ * So while a composition is running the field shows its own copy of what the
121
+ * element contains and stops rendering `value`. `onChange` still fires for
122
+ * every keystroke, so a parent sees the text as it is typed. When the
123
+ * composition ends the copy is dropped and the field is controlled again.
124
+ *
125
+ * This is the whole reason the component exists. It is also why `value` and
126
+ * `onChange` are a plain string rather than an event: an event's `target` is
127
+ * the element mid-composition, which is precisely the value that must not be
128
+ * trusted.
129
+ */
130
+ export declare const MPTextField: React.ForwardRefExoticComponent<MPTextFieldProps & React.RefAttributes<HTMLInputElement | HTMLTextAreaElement>>;
@@ -0,0 +1 @@
1
+ import{jsx as e,jsxs as t}from"react/jsx-runtime";import*as o from"react";import r from"@mui/material/FormControl";import n from"@mui/material/FormHelperText";import i from"@mui/material/IconButton";import a from"@mui/material/InputAdornment";import l from"@mui/material/InputLabel";import s from"@mui/material/OutlinedInput";import m from"@mui/material/useMediaQuery";import{useTheme as u}from"@mui/material/styles";import{MPIcon as d}from"../icon/MPIcon";import{VisibilityIcon as p,VisibilityOffIcon as c}from"../../constants/icons";export const MPTextField=o.forwardRef(function({value:f,name:h,type:x="text",placeholder:y,autoComplete:b,label:g,onChange:v,onFormReset:C,onSubmit:w,errorMessage:I="",required:F=!1,fullWidth:M=!1,readOnly:k=!1,autoFocus:z=!1,large:E=!1,disabled:S=!1,disableEnterKey:D=!1,rows:R,maxLength:j,startIcon:A,resizable:L=!1,id:O},P){const q=o.useRef(null),K=u(),T=m(K.breakpoints.down("sm")),[U,W]=o.useState(!1),[B,H]=o.useState(!1),[Q,$]=o.useState(f),G=o.useId(),J=O??`mp-text-field-${h??G}`,N=!!R,V=o.useCallback(e=>{q.current=e,"function"==typeof P?P(e):P&&(P.current=e)},[P]),X=e=>{e.preventDefault()};return o.useEffect(()=>{B||$(f)},[f,B]),o.useEffect(()=>{z&&!T&&q.current?.focus()},[]),t(r,{required:F,fullWidth:M,error:I.length>0,children:[g&&e(l,{size:"small",color:"secondary",shrink:!0,htmlFor:J,children:g}),e(s,{id:J,autoComplete:b,inputRef:V,type:U?"text":x,label:g,notched:!!g,rows:R,multiline:N,color:k?"secondary":void 0,readOnly:k,placeholder:y,startAdornment:A?e(a,{position:"start",children:A}):null,inputProps:j?{maxLength:j}:void 0,endAdornment:"password"===x?e(a,{position:"end",children:e(i,{"aria-label":U?"hide the password":"display the password",onClick:()=>W(e=>!e),onMouseDown:X,onMouseUp:X,edge:"end",disabled:S,children:e(d,{icon:U?p:c,size:E?22:20})})}):null,size:E?"medium":"small",sx:L&&N?{"& textarea":{resize:"vertical"}}:void 0,name:h,disabled:S,value:B?Q:f,onChange:e=>{C?.();const t=e.target.value;B&&$(t),v?.(t)},onCompositionStart:()=>{H(!0),$(f)},onCompositionUpdate:e=>{$(e.target.value)},onCompositionEnd:e=>{H(!1);const t=e.target.value;v?.(t),$(t)},onKeyDown:e=>{"Enter"===e.key&&(w?.(),N&&!D||e.preventDefault())}}),I.length>0&&e(n,{error:!0,children:I})]})});
@@ -0,0 +1,2 @@
1
+ export { MPTextField } from './MPTextField';
2
+ export type { MPTextFieldProps, MPTextFieldType } from './MPTextField';
@@ -0,0 +1 @@
1
+ export{MPTextField}from"./MPTextField";
@@ -0,0 +1,55 @@
1
+ /** The glyph a password field shows while the password is legible. */
2
+ export declare const VisibilityIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
3
+ /** The glyph a password field shows while the password is masked. */
4
+ export declare const VisibilityOffIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
5
+ /** Dismiss: a chip, a dialog, an alert. */
6
+ export declare const CloseIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
7
+ /** A ticked option, a completed step. */
8
+ export declare const CheckIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
9
+ /** Disclosure, pointing down. */
10
+ export declare const ChevronDownIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
11
+ /** Disclosure, pointing up. */
12
+ export declare const ChevronUpIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
13
+ /** Previous, in a pager or a carousel. */
14
+ export declare const ChevronLeftIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
15
+ /** Next, in a pager or a carousel. */
16
+ export declare const ChevronRightIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
17
+ /** Search, for a field's leading adornment. */
18
+ export declare const SearchIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
19
+ /** Copy to clipboard. */
20
+ export declare const CopyIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
21
+ /** Severity: something worth knowing. */
22
+ export declare const InfoIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
23
+ /** Severity: it worked. */
24
+ export declare const SuccessIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
25
+ /** Severity: proceed carefully. */
26
+ export declare const WarningIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
27
+ /** Severity: it did not work. */
28
+ export declare const ErrorIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
29
+ /**
30
+ * The same glyphs, keyed by role.
31
+ *
32
+ * Kebab-case keys so a name can be carried in a prop, a config file or a
33
+ * database column without a second spelling to translate.
34
+ *
35
+ * Importing this pulls in every glyph above β€” see the note at the top of the
36
+ * file. Reach for the named exports when only one or two are needed.
37
+ */
38
+ export declare const ICONS: {
39
+ readonly visibility: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
40
+ readonly 'visibility-off': import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
41
+ readonly close: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
42
+ readonly check: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
43
+ readonly 'chevron-down': import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
44
+ readonly 'chevron-up': import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
45
+ readonly 'chevron-left': import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
46
+ readonly 'chevron-right': import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
47
+ readonly search: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
48
+ readonly copy: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
49
+ readonly info: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
50
+ readonly success: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
51
+ readonly warning: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
52
+ readonly error: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
53
+ };
54
+ /** Every role name `ICONS` answers to. */
55
+ export type MPIconName = keyof typeof ICONS;
@@ -0,0 +1 @@
1
+ import{Check as o,ChevronDown as n,ChevronLeft as c,ChevronRight as r,ChevronUp as e,CircleAlert as t,CircleCheck as i,Copy as s,Eye as I,EyeOff as p,Info as h,Search as x,TriangleAlert as C,X as v}from"lucide-react";export const VisibilityIcon=I;export const VisibilityOffIcon=p;export const CloseIcon=v;export const CheckIcon=o;export const ChevronDownIcon=n;export const ChevronUpIcon=e;export const ChevronLeftIcon=c;export const ChevronRightIcon=r;export const SearchIcon=x;export const CopyIcon=s;export const InfoIcon=h;export const SuccessIcon=i;export const WarningIcon=C;export const ErrorIcon=t;export const ICONS={visibility:VisibilityIcon,"visibility-off":VisibilityOffIcon,close:CloseIcon,check:CheckIcon,"chevron-down":ChevronDownIcon,"chevron-up":ChevronUpIcon,"chevron-left":ChevronLeftIcon,"chevron-right":ChevronRightIcon,search:SearchIcon,copy:CopyIcon,info:InfoIcon,success:SuccessIcon,warning:WarningIcon,error:ErrorIcon};
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export * from './constants/icons';
3
+ export * from './components/icon';
4
+ export * from './components/text-field';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export*from"./types";export*from"./constants/icons";export*from"./components/icon";export*from"./components/text-field";
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
+ .fixed{position:fixed}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.resize{resize:both}.items-center{align-items:center}.justify-center{justify-content:center}.justify-self-center{justify-self:center}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.align-middle{vertical-align:middle}.\[\&\>img\]\:block>img{display:block}.\[\&\>img\]\:size-full>img{width:100%;height:100%}.\[\&\>svg\]\:block>svg{display:block}.\[\&\>svg\]\:size-full>svg{width:100%;height:100%}:root{--mp-duration:.25s}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Material Plus design tokens, and the source declaration the components need.
3
+ *
4
+ * This file ships as `material-plus-ui/tailwind.css`, for a project that runs
5
+ * Tailwind itself and wants the components' utilities generated in the same
6
+ * pass as its own:
7
+ *
8
+ * @import 'tailwindcss';
9
+ * @import 'material-plus-ui/tailwind.css';
10
+ *
11
+ * A project with no Tailwind imports `material-plus-ui/styles.css` instead, which
12
+ * is this file plus the compiled utilities, built by `scripts/build-styles.mjs`
13
+ * from `src/standalone.css`. The tokens below are the same in both.
14
+ *
15
+ * ## What is deliberately not here
16
+ *
17
+ * **Colour, typography and dark mode.** Material UI owns all three. A component
18
+ * in this library takes its palette from the surrounding `ThemeProvider`, the
19
+ * same as the `@mui/material` component beside it, so a token family here would
20
+ * be a second theme that could disagree with the first. Anything a component
21
+ * needs from the theme it reads through MUI, not from a custom property.
22
+ *
23
+ * **A reset.** MUI's `CssBaseline` is one, and every application using this
24
+ * library already has it. See `src/standalone.css`.
25
+ *
26
+ * What Tailwind is for here is layout and the handful of structural utilities a
27
+ * component needs that `sx` would express less clearly β€” so the tokens below
28
+ * stay to that job, and the file is short on purpose.
29
+ */
30
+
31
+ /*
32
+ * The components' classes are Tailwind utilities, so Tailwind has to see the
33
+ * files that spell them out β€” otherwise a component renders with none of them.
34
+ *
35
+ * `@source` resolves relative to the file it is written in, not to whatever
36
+ * imported it. In a consumer's install that is `node_modules/material-plus-ui/dist/`,
37
+ * next to the compiled components; in this repository it is `src/`, next to the
38
+ * `.tsx`. One line covers both, and an explicitly registered source is scanned
39
+ * even inside `node_modules`, which automatic detection skips.
40
+ *
41
+ * This is why consumers do not write an `@source '../node_modules/material-plus-ui'`
42
+ * of their own β€” a path that would depend on where their CSS file happens to sit.
43
+ *
44
+ * The same line is what the standalone build scans: `src/standalone.css` pulls
45
+ * this file in from `src/`, so `.` means the components' `.tsx` there. One
46
+ * declaration, three contexts.
47
+ */
48
+ @source '.';
49
+
50
+ /* ---------------------------------------------------------------------------
51
+ * Tokens
52
+ *
53
+ * Only what a component actually reads. A token nobody uses is a promise to
54
+ * keep supporting a name, so they are added here as components need them
55
+ * rather than in anticipation.
56
+ * ------------------------------------------------------------------------- */
57
+
58
+ :root {
59
+ /* How long a Material Plus transition runs. Matches MUI's own `short`
60
+ duration so a control from this library and one from `@mui/material`
61
+ settle together rather than a frame apart. */
62
+ --mp-duration: 250ms;
63
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The prop vocabulary shared across Material Plus components.
3
+ *
4
+ * This library sits on top of Material UI rather than beside it, so the rule
5
+ * here is the opposite of the usual one for a component library: **where MUI
6
+ * already has a word, use MUI's word.** A `size` of `'small'`, a `color` of
7
+ * `'primary'` and an `error` boolean mean on an `MP*` component exactly what
8
+ * they mean on the `@mui/material` component next to it in the same form. A
9
+ * second vocabulary would make a consumer translate between two libraries on
10
+ * every line, which is the thing this package exists to avoid.
11
+ *
12
+ * What lives here is only what MUI has no word for.
13
+ */
14
+ import type * as React from 'react';
15
+ /**
16
+ * The size ladder for controls, spelled the way MUI spells it.
17
+ *
18
+ * Re-declared rather than imported from `@mui/material` on purpose: it is two
19
+ * string literals, and importing it would put a type-only dependency on a
20
+ * peer's internal module path in every component's declaration file.
21
+ */
22
+ export type MPSize = 'small' | 'medium';
23
+ /** A palette role. The same six names `@mui/material` uses. */
24
+ export type MPColor = 'primary' | 'secondary' | 'success' | 'error' | 'info' | 'warning';
25
+ /**
26
+ * The props a glyph component is handed when Material Plus renders one.
27
+ *
28
+ * Deliberately the shape `lucide-react` icons already take, which is also the
29
+ * shape most icon sets settle on β€” a size, a colour and a stroke. Any component
30
+ * accepting these can be passed as an `icon`, so an application is not tied to
31
+ * the set this library happens to depend on.
32
+ */
33
+ export interface MPIconGlyphProps {
34
+ size?: number | string;
35
+ color?: string;
36
+ strokeWidth?: number | string;
37
+ className?: string;
38
+ [key: string]: unknown;
39
+ }
40
+ /**
41
+ * Something that can be drawn as an icon.
42
+ *
43
+ * Two forms, because icon sets hand back two different things:
44
+ *
45
+ * - **A component** β€” `<MPIcon icon={ICONS.close} />`. This is what
46
+ * `lucide-react`, `react-icons` and most sets export, and it is the form that
47
+ * lets `MPIcon` pass a size and a colour *into* the glyph rather than trying
48
+ * to style an element from the outside.
49
+ * - **An element** β€” `<MPIcon icon={<svg>…</svg>} />`. A drawing of your own, a
50
+ * `@mui/icons-material` glyph already constructed, an `<img>`. It is sized by
51
+ * the box it is laid into.
52
+ *
53
+ * Note that this is *not* `React.ReactNode`. A component and an element are
54
+ * rendered differently, and the distinction has to survive into the type or a
55
+ * caller passing `ICONS.close` would be told to pass `<ICONS.close />` instead.
56
+ */
57
+ export type MPIconGlyph = React.ComponentType<MPIconGlyphProps> | React.ReactElement;
package/dist/types.js ADDED
File without changes
package/package.json ADDED
@@ -0,0 +1,154 @@
1
+ {
2
+ "name": "material-plus-ui",
3
+ "version": "0.0.1",
4
+ "description": "A React component library that extends Material UI β€” the components MUI does not ship, and wider versions of the ones it does. Built on @mui/material, Base UI and Tailwind CSS v4. ESM only, types included.",
5
+ "type": "module",
6
+ "types": "dist/index.d.ts",
7
+ "author": "CDGet <jooy2.contact@gmail.com>",
8
+ "license": "MIT",
9
+ "homepage": "https://material-plus.cdget.com",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/jooy2/material-plus.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/jooy2/material-plus/issues"
16
+ },
17
+ "scripts": {
18
+ "build": "npm run format:fix && tsc --project tsconfig.prod.json && npm run minify && node scripts/build-styles.mjs",
19
+ "lint": "eslint . --ext .js,.ts .",
20
+ "lint:fix": "eslint . --ext .js,.ts --fix .",
21
+ "minify": "terser-glob 'dist/**/*.js' --config-file terser.config.json",
22
+ "prepare": "npm run build",
23
+ "format": "prettier .",
24
+ "format:fix": "prettier . --write",
25
+ "docs:changelog": "node scripts/copy-changelog.mjs",
26
+ "docs:dev": "npm run build && npm run docs:changelog && vitepress dev docs",
27
+ "docs:build": "npm run build && npm run docs:changelog && vitepress build docs",
28
+ "docs:serve": "npm run build && npm run docs:build && vitepress serve docs",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
31
+ "typecheck": "tsc --project tsconfig.prod.json --noEmit && tsc --project test/tsconfig.json && tsc --project docs/tsconfig.json"
32
+ },
33
+ "engines": {
34
+ "node": ">=18.0.0"
35
+ },
36
+ "main": "dist/index.js",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/index.d.ts",
40
+ "module": "./dist/index.js",
41
+ "default": "./dist/index.js"
42
+ },
43
+ "./types": {
44
+ "types": "./dist/types.d.ts",
45
+ "module": "./dist/types.js",
46
+ "default": "./dist/types.js"
47
+ },
48
+ "./constants/icons": {
49
+ "types": "./dist/constants/icons.d.ts",
50
+ "module": "./dist/constants/icons.js",
51
+ "default": "./dist/constants/icons.js"
52
+ },
53
+ "./styles.css": "./dist/styles.css",
54
+ "./tailwind.css": "./dist/tailwind.css"
55
+ },
56
+ "typesVersions": {
57
+ "*": {
58
+ "index.d.ts": [
59
+ "dist/index.d.ts"
60
+ ]
61
+ }
62
+ },
63
+ "keywords": [
64
+ "material-plus-ui",
65
+ "material-plus",
66
+ "material-ui",
67
+ "mui",
68
+ "mui-extensions",
69
+ "react",
70
+ "reactjs",
71
+ "react-components",
72
+ "react-ui",
73
+ "ui",
74
+ "ui-kit",
75
+ "ui-library",
76
+ "ui-components",
77
+ "component-library",
78
+ "components",
79
+ "design-system",
80
+ "frontend",
81
+ "web",
82
+ "typescript",
83
+ "tsx",
84
+ "esm",
85
+ "accessible",
86
+ "accessibility",
87
+ "a11y",
88
+ "aria",
89
+ "dark-mode",
90
+ "theming",
91
+ "tailwindcss",
92
+ "base-ui",
93
+ "lucide",
94
+ "icon",
95
+ "textfield",
96
+ "input",
97
+ "form",
98
+ "ime",
99
+ "composition"
100
+ ],
101
+ "devDependencies": {
102
+ "@base-ui/react": "^1.7.0",
103
+ "@emotion/react": "^11.14.0",
104
+ "@emotion/styled": "^11.14.1",
105
+ "@eslint/js": "^9.39.1",
106
+ "@mui/material": "^9.3.0",
107
+ "@tailwindcss/postcss": "^4.3.3",
108
+ "@types/node": "^26.1.2",
109
+ "@types/react": "^19.2.18",
110
+ "@types/react-dom": "^19.2.4",
111
+ "@typescript-eslint/parser": "^8.66.0",
112
+ "@vitejs/plugin-react": "^6.0.5",
113
+ "@vitest/browser": "^4.1.10",
114
+ "@vitest/browser-playwright": "^4.1.10",
115
+ "eslint": "^9.39.1",
116
+ "eslint-config-prettier": "^10.1.8",
117
+ "eslint-plugin-n": "^18.2.2",
118
+ "globals": "^17.9.0",
119
+ "jiti": "^2.7.0",
120
+ "playwright": "^1.62.1",
121
+ "postcss": "^8.5.25",
122
+ "prettier": "^3.9.6",
123
+ "prettier-plugin-tailwindcss": "^0.8.1",
124
+ "react": "^19.2.8",
125
+ "react-dom": "^19.2.8",
126
+ "tailwindcss": "^4.3.3",
127
+ "terser-glob": "^1.2.1",
128
+ "tsx": "^4.23.6",
129
+ "typescript": "^6.0.3",
130
+ "typescript-eslint": "^8.66.0",
131
+ "vite": "^8.2.0",
132
+ "vitepress": "^1.6.4",
133
+ "vitepress-i18n": "^1.3.5",
134
+ "vitepress-sidebar": "^1.36.1",
135
+ "vitest": "^4.1.10",
136
+ "vitest-browser-react": "^2.2.0"
137
+ },
138
+ "dependencies": {
139
+ "lucide-react": "^1.28.0"
140
+ },
141
+ "peerDependencies": {
142
+ "@emotion/react": "^11.0.0",
143
+ "@emotion/styled": "^11.0.0",
144
+ "@mui/material": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
145
+ "@types/react": "^18.0.0 || ^19.0.0",
146
+ "react": "^18.0.0 || ^19.0.0",
147
+ "react-dom": "^18.0.0 || ^19.0.0"
148
+ },
149
+ "peerDependenciesMeta": {
150
+ "@types/react": {
151
+ "optional": true
152
+ }
153
+ }
154
+ }