masked-components-cli 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -0
- package/dist/cli.js +103 -0
- package/package.json +40 -0
- package/templates/masked-buttons/MaskedButton/Base/BaseButton.styles.ts +155 -0
- package/templates/masked-buttons/MaskedButton/Base/BaseButton.tsx +75 -0
- package/templates/masked-buttons/MaskedButton/MaskedButton.tsx +38 -0
- package/templates/masked-buttons/MaskedButton/MaskedButton.types.ts +67 -0
- package/templates/masked-buttons/MaskedButton/variants/Default/DefaultButton.styles.ts +53 -0
- package/templates/masked-buttons/MaskedButton/variants/Default/DefaultButton.tsx +10 -0
- package/templates/masked-buttons/MaskedButton/variants/Ghost/GhostButton.styles.ts +52 -0
- package/templates/masked-buttons/MaskedButton/variants/Ghost/GhostButton.tsx +10 -0
- package/templates/masked-buttons/MaskedButton/variants/Gradient/GradientButton.styles.ts +52 -0
- package/templates/masked-buttons/MaskedButton/variants/Gradient/GradientButton.tsx +10 -0
- package/templates/masked-buttons/MaskedButton/variants/Link/LinkButton.styles.ts +55 -0
- package/templates/masked-buttons/MaskedButton/variants/Link/LinkButton.tsx +10 -0
- package/templates/masked-buttons/MaskedButton/variants/Neon/NeonButton.styles.ts +57 -0
- package/templates/masked-buttons/MaskedButton/variants/Neon/NeonButton.tsx +10 -0
- package/templates/masked-buttons/MaskedButton/variants/Outline/OutlineButton.styles.ts +53 -0
- package/templates/masked-buttons/MaskedButton/variants/Outline/OutlineButton.tsx +10 -0
- package/templates/masked-buttons/MaskedButton/variants/Toggle/ToggleButton.styles.ts +70 -0
- package/templates/masked-buttons/MaskedButton/variants/Toggle/ToggleButton.tsx +15 -0
- package/templates/masked-cards/MaskedCards/MaskedCards.styles.ts +0 -0
- package/templates/masked-cards/MaskedCards/MaskedCards.tsx +0 -0
- package/templates/masked-cards/MaskedCards/MaskedCardsStyles.ts +0 -0
- package/templates/masked-input/MaskedInput/FormikMaskedInput.tsx +44 -0
- package/templates/masked-input/MaskedInput/MaskedInput.styles.ts +298 -0
- package/templates/masked-input/MaskedInput/MaskedInput.tsx +42 -0
- package/templates/masked-input/MaskedInput/MaskedInput.types.ts +79 -0
- package/templates/masked-input/MaskedInput/formikVariants/FormikCurrencyInput.tsx +13 -0
- package/templates/masked-input/MaskedInput/formikVariants/FormikFileInput.tsx +13 -0
- package/templates/masked-input/MaskedInput/formikVariants/FormikMaskedTextInput.tsx +13 -0
- package/templates/masked-input/MaskedInput/formikVariants/FormikPasswordInput.tsx +13 -0
- package/templates/masked-input/MaskedInput/formikVariants/FormikSearchInput.tsx +13 -0
- package/templates/masked-input/MaskedInput/formikVariants/FormikSelectInput.tsx +13 -0
- package/templates/masked-input/MaskedInput/formikVariants/FormikTextInput.tsx +13 -0
- package/templates/masked-input/MaskedInput/formikVariants/FormikTextareaInput.tsx +13 -0
- package/templates/masked-input/MaskedInput/variants/CurrencyInput.tsx +100 -0
- package/templates/masked-input/MaskedInput/variants/FileInput.tsx +94 -0
- package/templates/masked-input/MaskedInput/variants/MaskedInput.tsx +35 -0
- package/templates/masked-input/MaskedInput/variants/PasswordInput.tsx +39 -0
- package/templates/masked-input/MaskedInput/variants/SearchInput.tsx +47 -0
- package/templates/masked-input/MaskedInput/variants/SelectInput.tsx +67 -0
- package/templates/masked-input/MaskedInput/variants/TextInput.tsx +34 -0
- package/templates/masked-input/MaskedInput/variants/TextareaInput.tsx +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# β¨ Masked Components CLI
|
|
2
|
+
|
|
3
|
+
A modern, type-safe and flexible component system for **Next.js + React**, designed to boost developer experience and speed up UI creation using a simple **CLI installer**.
|
|
4
|
+
|
|
5
|
+
Masked Components provides **polymorphic**, **theme-ready**, and **highly customizable** UI components with first-class **TypeScript support**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## π Live Demo
|
|
10
|
+
|
|
11
|
+
π **Website:** https://masked-components.vercel.app/
|
|
12
|
+
π **CLI Install Command:**
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx masked-components
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## π¦ What is Masked Components?
|
|
21
|
+
|
|
22
|
+
Masked Components is a UI component library combined with a CLI that allows you to **install and scaffold pre-configured components** directly into your project.
|
|
23
|
+
|
|
24
|
+
It focuses on:
|
|
25
|
+
|
|
26
|
+
- Developer Experience (DX)
|
|
27
|
+
- Clean and scalable architecture
|
|
28
|
+
- Modern UI patterns
|
|
29
|
+
- Performance and tree-shaking
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## π§± Tech Stack
|
|
34
|
+
|
|
35
|
+
- **Next.js** β Optimized routing, SSR and App Router ready
|
|
36
|
+
- **React** β Component-based architecture
|
|
37
|
+
- **TypeScript** β Strong typing and intelligent autocomplete
|
|
38
|
+
- **Styled Components** β Theme-driven and dynamic styling
|
|
39
|
+
- **React Icons** β Flexible icon support
|
|
40
|
+
- **IMask** β Advanced input masking
|
|
41
|
+
- **CLI (Node.js)** β Simple and fast installation
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## β‘ Installation
|
|
46
|
+
|
|
47
|
+
Install Masked Components in seconds using the CLI:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx masked-components
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This command will:
|
|
54
|
+
|
|
55
|
+
- Create a new Next.js project
|
|
56
|
+
- Configure TypeScript
|
|
57
|
+
- Install dependencies
|
|
58
|
+
- Add Masked Components structure
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## π§© Components Overview
|
|
63
|
+
|
|
64
|
+
### π MaskedButton
|
|
65
|
+
|
|
66
|
+
Highly customizable buttons with multiple variants, states, sizes, shapes, icons, and behaviors.
|
|
67
|
+
|
|
68
|
+
#### Variants
|
|
69
|
+
|
|
70
|
+
- `default`
|
|
71
|
+
- `outline`
|
|
72
|
+
- `ghost`
|
|
73
|
+
- `gradient`
|
|
74
|
+
- `neon`
|
|
75
|
+
- `link`
|
|
76
|
+
- `toggle`
|
|
77
|
+
|
|
78
|
+
#### States
|
|
79
|
+
|
|
80
|
+
- `default`
|
|
81
|
+
- `loading`
|
|
82
|
+
- `disabled`
|
|
83
|
+
- `error`
|
|
84
|
+
- `active`
|
|
85
|
+
|
|
86
|
+
#### Features
|
|
87
|
+
|
|
88
|
+
- Left & right icons
|
|
89
|
+
- Loading spinner
|
|
90
|
+
- Tooltip label
|
|
91
|
+
- Full width support
|
|
92
|
+
- Polymorphic (`button` or `a`)
|
|
93
|
+
- Toggle mode
|
|
94
|
+
|
|
95
|
+
#### Basic Example
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<MaskedButton variant="default" size="sm" leftIcon={<FaCode />}>
|
|
99
|
+
Default Button
|
|
100
|
+
</MaskedButton>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### β¨οΈ MaskedInput
|
|
106
|
+
|
|
107
|
+
Powerful input system with intelligent masks and real-time formatting.
|
|
108
|
+
|
|
109
|
+
#### Input Variants
|
|
110
|
+
|
|
111
|
+
- `default`
|
|
112
|
+
- `textarea`
|
|
113
|
+
- `masked`
|
|
114
|
+
- `password`
|
|
115
|
+
- `select`
|
|
116
|
+
- `file`
|
|
117
|
+
- `search`
|
|
118
|
+
- `currency`
|
|
119
|
+
|
|
120
|
+
#### Features
|
|
121
|
+
|
|
122
|
+
- Automatic masking
|
|
123
|
+
- Controlled & uncontrolled support
|
|
124
|
+
- Icons
|
|
125
|
+
- Validation-ready
|
|
126
|
+
- Cloudinary file upload support
|
|
127
|
+
- Currency formatting
|
|
128
|
+
|
|
129
|
+
#### Example
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
<MInput
|
|
133
|
+
variant="masked"
|
|
134
|
+
label="Phone"
|
|
135
|
+
mask="(00) 0000-0000"
|
|
136
|
+
placeholder="Phone number"
|
|
137
|
+
value={value}
|
|
138
|
+
onChange={setValue}
|
|
139
|
+
/>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## β¨ Key Features
|
|
145
|
+
|
|
146
|
+
- β
TypeScript Native
|
|
147
|
+
- β
Polymorphic Components
|
|
148
|
+
- β
Responsive by default
|
|
149
|
+
- β
Tree-shaking support
|
|
150
|
+
- β
Zero unnecessary dependencies
|
|
151
|
+
- β
Theme-ready styles
|
|
152
|
+
- β
Modern animations
|
|
153
|
+
- β
Accessibility-friendly
|
|
154
|
+
- β
Optimized performance
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## π§ͺ Playground
|
|
159
|
+
|
|
160
|
+
The website provides a full interactive playground where you can:
|
|
161
|
+
|
|
162
|
+
- Test all button variants
|
|
163
|
+
- Change states dynamically
|
|
164
|
+
- Try input masks in real time
|
|
165
|
+
- Preview sizes, shapes and icons
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## π£οΈ Roadmap
|
|
170
|
+
|
|
171
|
+
- π Masked Cards (Coming Soon)
|
|
172
|
+
- ποΈ Masked Animations (Coming Soon)
|
|
173
|
+
- π Extended documentation
|
|
174
|
+
- π§© More components
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## π€ Author
|
|
179
|
+
|
|
180
|
+
Created with π by **Renato Minoita**
|
|
181
|
+
|
|
182
|
+
- πΌ LinkedIn: https://www.linkedin.com/in/renato-minoita/
|
|
183
|
+
- π§βπ» GitHub: https://github.com/RNT13
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## π License
|
|
188
|
+
|
|
189
|
+
MIT License Β© 2026
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/installDeps.ts
|
|
4
|
+
import { execa } from "execa";
|
|
5
|
+
async function installDeps(deps) {
|
|
6
|
+
await execa("npm", ["install", ...deps], {
|
|
7
|
+
stdio: "inherit"
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/utils/copyTemplate.ts
|
|
12
|
+
import fs from "fs-extra";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
async function copyTemplate({ templateDir, targetDir }) {
|
|
16
|
+
const packageFile = fileURLToPath(import.meta.url);
|
|
17
|
+
const packageDir = path.dirname(packageFile);
|
|
18
|
+
let currentDir = packageDir;
|
|
19
|
+
let source = null;
|
|
20
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
21
|
+
const possible = path.join(currentDir, "templates", templateDir);
|
|
22
|
+
if (fs.existsSync(possible)) {
|
|
23
|
+
source = possible;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
currentDir = path.dirname(currentDir);
|
|
27
|
+
}
|
|
28
|
+
if (!source) {
|
|
29
|
+
throw new Error(`Template n\xE3o encontrado: ${templateDir}`);
|
|
30
|
+
}
|
|
31
|
+
const target = path.resolve(process.cwd(), targetDir);
|
|
32
|
+
await fs.ensureDir(target);
|
|
33
|
+
await fs.copy(source, target, {
|
|
34
|
+
overwrite: false,
|
|
35
|
+
errorOnExist: false
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/installers/maskedButtons.ts
|
|
40
|
+
async function installMaskedButtons() {
|
|
41
|
+
console.log("\u{1F4E6} Instalando Masked Buttons...");
|
|
42
|
+
await installDeps(["react-input-mask"]);
|
|
43
|
+
await copyTemplate({
|
|
44
|
+
templateDir: "masked-buttons",
|
|
45
|
+
targetDir: "src/components/ui"
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/installers/maskedCards.ts
|
|
50
|
+
async function installMaskedCards() {
|
|
51
|
+
console.log("\u{1F4E6} Instalando Masked Cards...");
|
|
52
|
+
await installDeps(["react-input-mask"]);
|
|
53
|
+
await copyTemplate({
|
|
54
|
+
templateDir: "masked-cards",
|
|
55
|
+
targetDir: "src/components/ui"
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/installers/maskedInput.ts
|
|
60
|
+
async function installMaskedInput() {
|
|
61
|
+
console.log("\u{1F4E6} Instalando Masked Input...");
|
|
62
|
+
await installDeps(["react-input-mask"]);
|
|
63
|
+
await copyTemplate({
|
|
64
|
+
templateDir: "masked-input",
|
|
65
|
+
targetDir: "src/components/ui"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/prompts/selectComponents.ts
|
|
70
|
+
import prompts from "prompts";
|
|
71
|
+
async function selectComponents() {
|
|
72
|
+
const response = await prompts({
|
|
73
|
+
type: "multiselect",
|
|
74
|
+
name: "components",
|
|
75
|
+
message: "Quais componentes deseja instalar?",
|
|
76
|
+
choices: [
|
|
77
|
+
{ title: "Masked Input", value: "masked-input" },
|
|
78
|
+
{ title: "Masked Cards", value: "masked-cards" },
|
|
79
|
+
{ title: "Masked Buttons", value: "masked-buttons" }
|
|
80
|
+
]
|
|
81
|
+
});
|
|
82
|
+
return response.components ?? [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/cli.ts
|
|
86
|
+
async function main() {
|
|
87
|
+
const components = await selectComponents();
|
|
88
|
+
if (!components.length) {
|
|
89
|
+
console.log("Nenhum componente selecionado.");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (components.includes("masked-input")) {
|
|
93
|
+
await installMaskedInput();
|
|
94
|
+
}
|
|
95
|
+
if (components.includes("masked-cards")) {
|
|
96
|
+
await installMaskedCards();
|
|
97
|
+
}
|
|
98
|
+
if (components.includes("masked-buttons")) {
|
|
99
|
+
await installMaskedButtons();
|
|
100
|
+
}
|
|
101
|
+
console.log("\u2705 Componentes instalados com sucesso!");
|
|
102
|
+
}
|
|
103
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "masked-components-cli",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"templates"
|
|
8
|
+
],
|
|
9
|
+
"bin": {
|
|
10
|
+
"masked-components-cli": "./dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"description": "Masked Components",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
15
|
+
"build": "tsup src/cli.ts --format esm --platform node --target es2020",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"masked",
|
|
20
|
+
"components"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "RNT",
|
|
24
|
+
"main": "index.js",
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/fs-extra": "^11.0.4",
|
|
27
|
+
"@types/node": "^25.0.3",
|
|
28
|
+
"@types/prompts": "^2.4.9",
|
|
29
|
+
"@types/react": "^19.2.7",
|
|
30
|
+
"tsup": "^8.5.1",
|
|
31
|
+
"typescript": "^5.9.3"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"execa": "^9.6.1",
|
|
35
|
+
"fs-extra": "^11.3.3",
|
|
36
|
+
"prompts": "^2.4.2",
|
|
37
|
+
"react-icons": "^5.5.0",
|
|
38
|
+
"react-input-mask": "^2.0.4"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { animations, transitions } from '@/styles/animations'
|
|
2
|
+
import styled, { css } from 'styled-components'
|
|
3
|
+
import { ButtonShape, ButtonSize, ButtonState } from '../MaskedButton.types'
|
|
4
|
+
|
|
5
|
+
export const LabelDiv = styled.div`
|
|
6
|
+
position: absolute;
|
|
7
|
+
bottom: calc(150% + 12px);
|
|
8
|
+
left: 50%;
|
|
9
|
+
transform: translateX(-50%) translateY(4px);
|
|
10
|
+
|
|
11
|
+
padding: 8px 12px;
|
|
12
|
+
background-color: ${({ theme }) => theme.colors.baseBlue.light};
|
|
13
|
+
color: white;
|
|
14
|
+
border-radius: 6px;
|
|
15
|
+
font-size: 12px;
|
|
16
|
+
white-space: nowrap;
|
|
17
|
+
|
|
18
|
+
opacity: 0;
|
|
19
|
+
pointer-events: none;
|
|
20
|
+
|
|
21
|
+
z-index: 1;
|
|
22
|
+
|
|
23
|
+
${transitions.delay}
|
|
24
|
+
|
|
25
|
+
&::before {
|
|
26
|
+
content: '';
|
|
27
|
+
position: absolute;
|
|
28
|
+
top: 87%;
|
|
29
|
+
left: 50%;
|
|
30
|
+
transform: translateX(-50%) rotate(45deg);
|
|
31
|
+
width: 8px;
|
|
32
|
+
height: 8px;
|
|
33
|
+
background-color: ${({ theme }) => theme.colors.baseBlue.light};
|
|
34
|
+
}
|
|
35
|
+
`
|
|
36
|
+
|
|
37
|
+
export const ButtonContent = styled.span<{ $state: ButtonState }>`
|
|
38
|
+
position: relative;
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
gap: 8px;
|
|
42
|
+
`
|
|
43
|
+
|
|
44
|
+
export const IconWrapper = styled.span`
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
|
|
48
|
+
svg {
|
|
49
|
+
width: 20px;
|
|
50
|
+
height: 20px;
|
|
51
|
+
}
|
|
52
|
+
`
|
|
53
|
+
|
|
54
|
+
const sizeStyles = {
|
|
55
|
+
sm: css`
|
|
56
|
+
height: 100%;
|
|
57
|
+
padding: 6px 10px;
|
|
58
|
+
font-size: 14px;
|
|
59
|
+
`,
|
|
60
|
+
md: css`
|
|
61
|
+
height: 100%;
|
|
62
|
+
padding: 12px 14px;
|
|
63
|
+
font-size: 16px;
|
|
64
|
+
`,
|
|
65
|
+
lg: css`
|
|
66
|
+
height: 100%;
|
|
67
|
+
padding: 16px 20px;
|
|
68
|
+
font-size: 18px;
|
|
69
|
+
`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const ButtonShapes = {
|
|
73
|
+
rounded: css`
|
|
74
|
+
border-radius: 18px;
|
|
75
|
+
`,
|
|
76
|
+
|
|
77
|
+
circle: css`
|
|
78
|
+
width: 55px;
|
|
79
|
+
height: 55px;
|
|
80
|
+
border-radius: 50%;
|
|
81
|
+
display: flex;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
align-items: center;
|
|
84
|
+
|
|
85
|
+
svg,
|
|
86
|
+
span {
|
|
87
|
+
width: 25px;
|
|
88
|
+
height: 25px;
|
|
89
|
+
margin: 0;
|
|
90
|
+
}
|
|
91
|
+
`,
|
|
92
|
+
|
|
93
|
+
square: css`
|
|
94
|
+
border-radius: 5px;
|
|
95
|
+
`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const stateStyles = {
|
|
99
|
+
loading: css`
|
|
100
|
+
cursor: not-allowed;
|
|
101
|
+
|
|
102
|
+
svg {
|
|
103
|
+
${animations.spin}
|
|
104
|
+
stroke-width: 2;
|
|
105
|
+
}
|
|
106
|
+
`,
|
|
107
|
+
|
|
108
|
+
default: css``,
|
|
109
|
+
|
|
110
|
+
disabled: css`
|
|
111
|
+
cursor: not-allowed;
|
|
112
|
+
opacity: 0.5;
|
|
113
|
+
`,
|
|
114
|
+
|
|
115
|
+
error: css`
|
|
116
|
+
&:hover {
|
|
117
|
+
${animations.shakeX}
|
|
118
|
+
}
|
|
119
|
+
`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const BaseButtonContainer = styled.button<{
|
|
123
|
+
$size?: ButtonSize
|
|
124
|
+
$state?: ButtonState
|
|
125
|
+
$fullWidth?: boolean
|
|
126
|
+
$shape?: ButtonShape
|
|
127
|
+
}>`
|
|
128
|
+
display: inline-flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: 8px;
|
|
131
|
+
font-weight: 700;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
position: relative;
|
|
134
|
+
|
|
135
|
+
${({ $size = 'md' }) => sizeStyles[$size]}
|
|
136
|
+
${({ $fullWidth }) => $fullWidth && 'width: 100%;'}
|
|
137
|
+
${({ $fullWidth }) => $fullWidth && 'justify-content: center;'}
|
|
138
|
+
${({ $state }) => $state && stateStyles[$state]}
|
|
139
|
+
${({ $shape }) => $shape && ButtonShapes[$shape]}
|
|
140
|
+
|
|
141
|
+
${transitions.fast}
|
|
142
|
+
|
|
143
|
+
&:hover ${LabelDiv} {
|
|
144
|
+
opacity: 1;
|
|
145
|
+
transform: translateX(-50%) translateY(0);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
&:not(:hover) ${LabelDiv} {
|
|
149
|
+
transition-delay: 0s;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
&:active {
|
|
153
|
+
transform: scale(0.97);
|
|
154
|
+
}
|
|
155
|
+
`
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { forwardRef } from 'react'
|
|
4
|
+
import { VscLoading } from 'react-icons/vsc'
|
|
5
|
+
import { BaseButtonProps } from '../MaskedButton.types'
|
|
6
|
+
import { BaseButtonContainer, ButtonContent, IconWrapper, LabelDiv } from './BaseButton.styles'
|
|
7
|
+
|
|
8
|
+
type BaseButtonInternalProps = BaseButtonProps & {
|
|
9
|
+
href?: string
|
|
10
|
+
target?: string
|
|
11
|
+
rel?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export const BaseButton = forwardRef<HTMLButtonElement | HTMLAnchorElement, BaseButtonInternalProps>(
|
|
16
|
+
function BaseButton(
|
|
17
|
+
{
|
|
18
|
+
size = 'md',
|
|
19
|
+
state = 'default',
|
|
20
|
+
label,
|
|
21
|
+
children,
|
|
22
|
+
leftIcon,
|
|
23
|
+
rightIcon,
|
|
24
|
+
fullWidth,
|
|
25
|
+
href,
|
|
26
|
+
target,
|
|
27
|
+
rel,
|
|
28
|
+
type = 'button',
|
|
29
|
+
shapes = 'rounded',
|
|
30
|
+
...props
|
|
31
|
+
},
|
|
32
|
+
ref
|
|
33
|
+
) {
|
|
34
|
+
const isDisabled = state === 'disabled' || state === 'loading'
|
|
35
|
+
|
|
36
|
+
const Component = href ? 'a' : 'button'
|
|
37
|
+
|
|
38
|
+
const content = (
|
|
39
|
+
<ButtonContent $state={state}>
|
|
40
|
+
{state === 'loading' ? (
|
|
41
|
+
<>
|
|
42
|
+
{<IconWrapper>{<VscLoading />}</IconWrapper>}
|
|
43
|
+
<span>Loading...</span>
|
|
44
|
+
</>
|
|
45
|
+
) : (
|
|
46
|
+
<>
|
|
47
|
+
{leftIcon && <IconWrapper>{leftIcon}</IconWrapper>}
|
|
48
|
+
{<span>{children}</span>}
|
|
49
|
+
{rightIcon && <IconWrapper>{rightIcon}</IconWrapper>}
|
|
50
|
+
</>
|
|
51
|
+
)}
|
|
52
|
+
{label && <LabelDiv>{label}</LabelDiv>}
|
|
53
|
+
</ButtonContent>
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<BaseButtonContainer
|
|
58
|
+
as={Component}
|
|
59
|
+
href={href}
|
|
60
|
+
target={target}
|
|
61
|
+
rel={rel}
|
|
62
|
+
ref={ref as unknown as React.Ref<HTMLButtonElement>}
|
|
63
|
+
disabled={!href && isDisabled}
|
|
64
|
+
aria-disabled={href ? isDisabled : undefined}
|
|
65
|
+
type={!href ? type : undefined}
|
|
66
|
+
$size={size}
|
|
67
|
+
$state={state}
|
|
68
|
+
$fullWidth={fullWidth}
|
|
69
|
+
$shape={shapes}
|
|
70
|
+
{...props}
|
|
71
|
+
>
|
|
72
|
+
{content}
|
|
73
|
+
</BaseButtonContainer>
|
|
74
|
+
)
|
|
75
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ButtonProps } from './MaskedButton.types'
|
|
4
|
+
import DefaultButton from './variants/Default/DefaultButton'
|
|
5
|
+
import GhostButton from './variants/Ghost/GhostButton'
|
|
6
|
+
import GradientButton from './variants/Gradient/GradientButton'
|
|
7
|
+
import LinkButton from './variants/Link/LinkButton'
|
|
8
|
+
import NeonButton from './variants/Neon/NeonButton'
|
|
9
|
+
import OutlineButton from './variants/Outline/OutlineButton'
|
|
10
|
+
import TobbleButton from './variants/Toggle/ToggleButton'
|
|
11
|
+
|
|
12
|
+
export function MButton(props: ButtonProps) {
|
|
13
|
+
switch (props.variant) {
|
|
14
|
+
case 'default':
|
|
15
|
+
return <DefaultButton {...props} />
|
|
16
|
+
|
|
17
|
+
case 'outline':
|
|
18
|
+
return <OutlineButton {...props} />
|
|
19
|
+
|
|
20
|
+
case 'ghost':
|
|
21
|
+
return <GhostButton {...props} />
|
|
22
|
+
|
|
23
|
+
case 'link':
|
|
24
|
+
return <LinkButton {...props} />
|
|
25
|
+
|
|
26
|
+
case 'gradient':
|
|
27
|
+
return <GradientButton {...props} />
|
|
28
|
+
|
|
29
|
+
case 'neon':
|
|
30
|
+
return <NeonButton {...props} />
|
|
31
|
+
|
|
32
|
+
case 'toggle':
|
|
33
|
+
return <TobbleButton {...props} />
|
|
34
|
+
|
|
35
|
+
default:
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* ================= BASE ================= */
|
|
2
|
+
|
|
3
|
+
export type ButtonSize = 'sm' | 'md' | 'lg'
|
|
4
|
+
|
|
5
|
+
export type ButtonState = 'default' | 'disabled' | 'loading' | 'error'
|
|
6
|
+
|
|
7
|
+
export type ButtonShape = 'rounded' | 'circle' | 'square'
|
|
8
|
+
|
|
9
|
+
export type BaseButtonProps = {
|
|
10
|
+
size?: ButtonSize
|
|
11
|
+
shapes?: ButtonShape
|
|
12
|
+
state?: ButtonState
|
|
13
|
+
|
|
14
|
+
fullWidth?: boolean
|
|
15
|
+
label?: string
|
|
16
|
+
|
|
17
|
+
leftIcon?: React.ReactNode
|
|
18
|
+
rightIcon?: React.ReactNode
|
|
19
|
+
children?: React.ReactNode
|
|
20
|
+
|
|
21
|
+
className?: string
|
|
22
|
+
type?: 'button' | 'submit' | 'reset'
|
|
23
|
+
onClick?: () => void
|
|
24
|
+
|
|
25
|
+
href?: string
|
|
26
|
+
target?: React.HTMLAttributeAnchorTarget
|
|
27
|
+
rel?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* ================= VARIANT MAP ================= */
|
|
31
|
+
|
|
32
|
+
export type ButtonVariantMap = {
|
|
33
|
+
default: BaseButtonProps & {
|
|
34
|
+
$isActive?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
outline: BaseButtonProps & {
|
|
38
|
+
$isActive?: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
ghost: BaseButtonProps & {
|
|
42
|
+
$isActive?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
link: BaseButtonProps & {
|
|
46
|
+
$isActive?: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
gradient: BaseButtonProps & {
|
|
50
|
+
$isActive?: boolean
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
neon: BaseButtonProps & {
|
|
54
|
+
$isActive?: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
toggle: BaseButtonProps & {
|
|
58
|
+
$isActive?: boolean
|
|
59
|
+
$toggleLabel?: string
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ================= UNION AUTOMΓTICA ================= */
|
|
64
|
+
|
|
65
|
+
export type ButtonProps = {
|
|
66
|
+
[K in keyof ButtonVariantMap]: { variant: K } & ButtonVariantMap[K]
|
|
67
|
+
}[keyof ButtonVariantMap]
|