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 +21 -0
- package/README.md +133 -0
- package/dist/components/icon/MPIcon.d.ts +74 -0
- package/dist/components/icon/MPIcon.js +1 -0
- package/dist/components/icon/index.d.ts +2 -0
- package/dist/components/icon/index.js +1 -0
- package/dist/components/text-field/MPTextField.d.ts +130 -0
- package/dist/components/text-field/MPTextField.js +1 -0
- package/dist/components/text-field/index.d.ts +2 -0
- package/dist/components/text-field/index.js +1 -0
- package/dist/constants/icons.d.ts +55 -0
- package/dist/constants/icons.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -0
- package/dist/styles.css +2 -0
- package/dist/tailwind.css +63 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.js +0 -0
- package/package.json +154 -0
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
|
+
[](https://github.com/jooy2/material-plus/blob/main/LICENSE)  [](https://www.npmjs.com/package/material-plus-ui) [](https://www.npmjs.com/package/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 @@
|
|
|
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 @@
|
|
|
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};
|
package/dist/index.d.ts
ADDED
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";
|
package/dist/styles.css
ADDED
|
@@ -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
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
}
|