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.
Files changed (44) hide show
  1. package/README.md +189 -0
  2. package/dist/cli.js +103 -0
  3. package/package.json +40 -0
  4. package/templates/masked-buttons/MaskedButton/Base/BaseButton.styles.ts +155 -0
  5. package/templates/masked-buttons/MaskedButton/Base/BaseButton.tsx +75 -0
  6. package/templates/masked-buttons/MaskedButton/MaskedButton.tsx +38 -0
  7. package/templates/masked-buttons/MaskedButton/MaskedButton.types.ts +67 -0
  8. package/templates/masked-buttons/MaskedButton/variants/Default/DefaultButton.styles.ts +53 -0
  9. package/templates/masked-buttons/MaskedButton/variants/Default/DefaultButton.tsx +10 -0
  10. package/templates/masked-buttons/MaskedButton/variants/Ghost/GhostButton.styles.ts +52 -0
  11. package/templates/masked-buttons/MaskedButton/variants/Ghost/GhostButton.tsx +10 -0
  12. package/templates/masked-buttons/MaskedButton/variants/Gradient/GradientButton.styles.ts +52 -0
  13. package/templates/masked-buttons/MaskedButton/variants/Gradient/GradientButton.tsx +10 -0
  14. package/templates/masked-buttons/MaskedButton/variants/Link/LinkButton.styles.ts +55 -0
  15. package/templates/masked-buttons/MaskedButton/variants/Link/LinkButton.tsx +10 -0
  16. package/templates/masked-buttons/MaskedButton/variants/Neon/NeonButton.styles.ts +57 -0
  17. package/templates/masked-buttons/MaskedButton/variants/Neon/NeonButton.tsx +10 -0
  18. package/templates/masked-buttons/MaskedButton/variants/Outline/OutlineButton.styles.ts +53 -0
  19. package/templates/masked-buttons/MaskedButton/variants/Outline/OutlineButton.tsx +10 -0
  20. package/templates/masked-buttons/MaskedButton/variants/Toggle/ToggleButton.styles.ts +70 -0
  21. package/templates/masked-buttons/MaskedButton/variants/Toggle/ToggleButton.tsx +15 -0
  22. package/templates/masked-cards/MaskedCards/MaskedCards.styles.ts +0 -0
  23. package/templates/masked-cards/MaskedCards/MaskedCards.tsx +0 -0
  24. package/templates/masked-cards/MaskedCards/MaskedCardsStyles.ts +0 -0
  25. package/templates/masked-input/MaskedInput/FormikMaskedInput.tsx +44 -0
  26. package/templates/masked-input/MaskedInput/MaskedInput.styles.ts +298 -0
  27. package/templates/masked-input/MaskedInput/MaskedInput.tsx +42 -0
  28. package/templates/masked-input/MaskedInput/MaskedInput.types.ts +79 -0
  29. package/templates/masked-input/MaskedInput/formikVariants/FormikCurrencyInput.tsx +13 -0
  30. package/templates/masked-input/MaskedInput/formikVariants/FormikFileInput.tsx +13 -0
  31. package/templates/masked-input/MaskedInput/formikVariants/FormikMaskedTextInput.tsx +13 -0
  32. package/templates/masked-input/MaskedInput/formikVariants/FormikPasswordInput.tsx +13 -0
  33. package/templates/masked-input/MaskedInput/formikVariants/FormikSearchInput.tsx +13 -0
  34. package/templates/masked-input/MaskedInput/formikVariants/FormikSelectInput.tsx +13 -0
  35. package/templates/masked-input/MaskedInput/formikVariants/FormikTextInput.tsx +13 -0
  36. package/templates/masked-input/MaskedInput/formikVariants/FormikTextareaInput.tsx +13 -0
  37. package/templates/masked-input/MaskedInput/variants/CurrencyInput.tsx +100 -0
  38. package/templates/masked-input/MaskedInput/variants/FileInput.tsx +94 -0
  39. package/templates/masked-input/MaskedInput/variants/MaskedInput.tsx +35 -0
  40. package/templates/masked-input/MaskedInput/variants/PasswordInput.tsx +39 -0
  41. package/templates/masked-input/MaskedInput/variants/SearchInput.tsx +47 -0
  42. package/templates/masked-input/MaskedInput/variants/SelectInput.tsx +67 -0
  43. package/templates/masked-input/MaskedInput/variants/TextInput.tsx +34 -0
  44. 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]