@mimi-ng/cli 0.1.0 → 0.2.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 +19 -1
- package/dist/collection.json +1 -1
- package/dist/init/css.js +17 -0
- package/dist/init/index.js +7 -5
- package/dist/init/items.js +9 -0
- package/dist/ng-add/index.js +17 -1
- package/dist/ng-add/schema.json +27 -0
- package/dist/registry.json +222 -1
- package/dist/templates/components/avatar/avatar.ts +1 -1
- package/dist/templates/components/avatar/avatar.variants.ts +4 -4
- package/dist/templates/components/badge/badge.ts +41 -6
- package/dist/templates/components/badge/badge.variants.ts +105 -11
- package/dist/templates/components/button/button.ts +74 -7
- package/dist/templates/components/button/button.variants.ts +145 -21
- package/dist/templates/components/card/card.ts +8 -5
- package/dist/templates/components/checkbox/checkbox.ts +3 -3
- package/dist/templates/components/confirm/confirm-texts.ts +46 -0
- package/dist/templates/components/confirm/confirm.ts +190 -0
- package/dist/templates/components/confirm/index.ts +2 -0
- package/dist/templates/components/context-menu/context-menu.ts +378 -0
- package/dist/templates/components/context-menu/index.ts +14 -0
- package/dist/templates/components/dialog/dialog-texts.ts +41 -0
- package/dist/templates/components/dialog/dialog.ts +701 -0
- package/dist/templates/components/dialog/index.ts +18 -0
- package/dist/templates/components/dropdown-menu/dropdown-menu.ts +252 -0
- package/dist/templates/components/dropdown-menu/index.ts +14 -0
- package/dist/templates/components/form-field/form-error.ts +1 -1
- package/dist/templates/components/form-field/form-field.ts +3 -2
- package/dist/templates/components/input/input.variants.ts +6 -4
- package/dist/templates/components/menu/index.ts +15 -0
- package/dist/templates/components/menu/menu.ts +656 -0
- package/dist/templates/components/pagination/index.ts +2 -0
- package/dist/templates/components/pagination/pagination-page-size.ts +74 -0
- package/dist/templates/components/pagination/pagination-texts.ts +69 -0
- package/dist/templates/components/pagination/pagination.ts +443 -0
- package/dist/templates/components/popover/index.ts +12 -0
- package/dist/templates/components/popover/popover.ts +399 -0
- package/dist/templates/components/select/index.ts +12 -0
- package/dist/templates/components/select/select-texts.ts +62 -0
- package/dist/templates/components/select/select.ts +1085 -0
- package/dist/templates/components/separator/separator.variants.ts +10 -7
- package/dist/templates/components/skeleton/skeleton.ts +4 -1
- package/dist/templates/components/switch/switch.ts +6 -6
- package/dist/templates/components/tabs/index.ts +2 -0
- package/dist/templates/components/tabs/tabs.ts +426 -0
- package/dist/templates/components/textarea/textarea.variants.ts +14 -11
- package/dist/templates/components/toast/index.ts +3 -0
- package/dist/templates/components/toast/toast-config.ts +45 -0
- package/dist/templates/components/toast/toast-texts.ts +46 -0
- package/dist/templates/components/toast/toast.ts +1000 -0
- package/dist/templates/components/toolbar/index.ts +1 -0
- package/dist/templates/components/toolbar/toolbar.ts +377 -0
- package/dist/templates/components/tooltip/index.ts +7 -0
- package/dist/templates/components/tooltip/tooltip.ts +407 -0
- package/dist/templates/theme/provider.ts +62 -6
- package/dist/templates/theme/theme-base.css +307 -9
- package/dist/templates/theme/types.ts +278 -4
- package/dist/templates/utils/cn.ts +15 -1
- package/dist/templates/utils/control-styles.ts +2 -2
- package/dist/templates/utils/overlay.ts +140 -0
- package/dist/ui/index.js +80 -8
- package/dist/ui/schema.json +44 -0
- package/dist/utils/write-files.js +156 -59
- package/package.json +6 -5
|
@@ -1,4 +1,68 @@
|
|
|
1
|
-
import { cva
|
|
1
|
+
import { cva } from 'class-variance-authority';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cómo se ve el badge (spec 4, «Variante y tono»), sin los atajos de la 0.1.x. Sin ghost ni link:
|
|
5
|
+
* el diseño no los tiene.
|
|
6
|
+
*/
|
|
7
|
+
export type BadgeStyleVariant = 'solid' | 'soft' | 'outline';
|
|
8
|
+
|
|
9
|
+
/** De qué color es. `danger` usa los tokens `destructive`. */
|
|
10
|
+
export type BadgeTone = 'primary' | 'secondary' | 'success' | 'warning' | 'info' | 'danger';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Atajos de la 0.1.0, cuando el color era una variante. Siguen funcionando, sin error ni aviso:
|
|
14
|
+
* `default` = solid + primary, `secondary` = solid + secondary, `destructive` = solid + danger.
|
|
15
|
+
* @deprecated Se quitan en la 1.0 (spec 12). Usa `variant` y `tone`.
|
|
16
|
+
*/
|
|
17
|
+
export type BadgeVariantShortcut = 'default' | 'secondary' | 'destructive';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Lo que acepta `variant`: las variantes y, hasta la 1.0, los atajos de la 0.1.x (deprecados), para
|
|
21
|
+
* que el código que tipaba `BadgeVariant` con `'default'` o `'destructive'` siga compilando.
|
|
22
|
+
*/
|
|
23
|
+
export type BadgeVariant = BadgeStyleVariant | BadgeVariantShortcut;
|
|
24
|
+
|
|
25
|
+
/** Punto decorativo de outline (un ::before vacío: no lo leen los lectores de pantalla). */
|
|
26
|
+
const DOT = "gap-1.5 before:size-1.5 before:shrink-0 before:rounded-full before:content-['']";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Colores de cada tono en cada variante. Clases completas y literales para Tailwind. Diseño:
|
|
30
|
+
* docs/design/«Mimi F2 Tokens y Tonos» (Tonos en Badge). Outline lleva un punto del color del
|
|
31
|
+
* tono; sin tono (secondary) se ve como en la 0.1.0, sin punto. El tono secondary es
|
|
32
|
+
* provisional, sin diseño (spec 13).
|
|
33
|
+
*/
|
|
34
|
+
const TONE_CLASSES = {
|
|
35
|
+
primary: {
|
|
36
|
+
solid: 'border-transparent bg-primary text-primary-foreground',
|
|
37
|
+
soft: 'border-transparent bg-secondary text-secondary-foreground',
|
|
38
|
+
outline: `border-border bg-transparent text-foreground ${DOT} before:bg-primary`,
|
|
39
|
+
},
|
|
40
|
+
secondary: {
|
|
41
|
+
solid: 'border-transparent bg-secondary text-secondary-foreground',
|
|
42
|
+
soft: 'border-transparent bg-muted text-foreground',
|
|
43
|
+
outline: 'border-border bg-transparent text-foreground',
|
|
44
|
+
},
|
|
45
|
+
success: {
|
|
46
|
+
solid: 'border-transparent bg-success text-success-foreground',
|
|
47
|
+
soft: 'border-transparent bg-success-soft text-success-soft-foreground',
|
|
48
|
+
outline: `border-border bg-transparent text-foreground ${DOT} before:bg-success`,
|
|
49
|
+
},
|
|
50
|
+
warning: {
|
|
51
|
+
solid: 'border-transparent bg-warning text-warning-foreground',
|
|
52
|
+
soft: 'border-transparent bg-warning-soft text-warning-soft-foreground',
|
|
53
|
+
outline: `border-border bg-transparent text-foreground ${DOT} before:bg-warning`,
|
|
54
|
+
},
|
|
55
|
+
info: {
|
|
56
|
+
solid: 'border-transparent bg-info text-info-foreground',
|
|
57
|
+
soft: 'border-transparent bg-info-soft text-info-soft-foreground',
|
|
58
|
+
outline: `border-border bg-transparent text-foreground ${DOT} before:bg-info`,
|
|
59
|
+
},
|
|
60
|
+
danger: {
|
|
61
|
+
solid: 'border-transparent bg-destructive text-destructive-foreground',
|
|
62
|
+
soft: 'border-transparent bg-destructive-soft-bg text-destructive-soft-foreground',
|
|
63
|
+
outline: `border-border bg-transparent text-foreground ${DOT} before:bg-destructive`,
|
|
64
|
+
},
|
|
65
|
+
} as const satisfies Record<BadgeTone, Record<BadgeStyleVariant, string>>;
|
|
2
66
|
|
|
3
67
|
/**
|
|
4
68
|
* Clases de mimiBadge (docs/design/Mimi Componentes.dc.html, sección 5). Literales para que
|
|
@@ -7,21 +71,51 @@ import { cva, type VariantProps } from 'class-variance-authority';
|
|
|
7
71
|
*/
|
|
8
72
|
export const badgeVariants = cva(
|
|
9
73
|
[
|
|
10
|
-
'inline-flex h-[22px] shrink-0 items-center gap-1 rounded-badge border px-
|
|
11
|
-
'font-sans text-
|
|
74
|
+
'inline-flex h-[var(--mimi-badge-height,22px)] shrink-0 items-center gap-1 rounded-badge border px-[var(--mimi-badge-px,0.625rem)]',
|
|
75
|
+
'font-sans text-[length:var(--mimi-badge-font-size,0.75rem)] leading-none font-[weight:var(--mimi-badge-font-weight,600)] whitespace-nowrap',
|
|
12
76
|
'[&_svg]:pointer-events-none [&_svg]:size-3 [&_svg]:shrink-0',
|
|
13
77
|
],
|
|
14
78
|
{
|
|
15
79
|
variants: {
|
|
16
|
-
variant: {
|
|
17
|
-
|
|
18
|
-
secondary: 'border-transparent bg-secondary text-secondary-foreground',
|
|
19
|
-
outline: 'border-border bg-transparent text-foreground',
|
|
20
|
-
destructive: 'border-transparent bg-destructive text-destructive-foreground',
|
|
21
|
-
},
|
|
80
|
+
variant: { solid: '', soft: '', outline: '' },
|
|
81
|
+
tone: { primary: '', secondary: '', success: '', warning: '', info: '', danger: '' },
|
|
22
82
|
},
|
|
23
|
-
|
|
83
|
+
compoundVariants: Object.entries(TONE_CLASSES).flatMap(([tone, byVariant]) =>
|
|
84
|
+
Object.entries(byVariant).map(([variant, cls]) => ({
|
|
85
|
+
tone: tone as BadgeTone,
|
|
86
|
+
variant: variant as BadgeStyleVariant,
|
|
87
|
+
class: cls,
|
|
88
|
+
})),
|
|
89
|
+
),
|
|
90
|
+
defaultVariants: { variant: 'solid', tone: 'primary' },
|
|
24
91
|
},
|
|
25
92
|
);
|
|
26
93
|
|
|
27
|
-
|
|
94
|
+
/** Sin `tone`, cada variante usa su tono natural: outline es neutro, como en la 0.1.0. */
|
|
95
|
+
const NATURAL_TONE: Record<BadgeStyleVariant, BadgeTone> = {
|
|
96
|
+
solid: 'primary',
|
|
97
|
+
soft: 'primary',
|
|
98
|
+
outline: 'secondary',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const SHORTCUTS: Record<BadgeVariantShortcut, { variant: BadgeStyleVariant; tone: BadgeTone }> = {
|
|
102
|
+
default: { variant: 'solid', tone: 'primary' },
|
|
103
|
+
secondary: { variant: 'solid', tone: 'secondary' },
|
|
104
|
+
destructive: { variant: 'solid', tone: 'danger' },
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Resuelve variante y tono. Un atajo de la 0.1.0 gana siempre; `ignoredTone` indica que además
|
|
109
|
+
* se pasó un `tone` que no se aplicó.
|
|
110
|
+
*/
|
|
111
|
+
export function resolveBadgeStyle(
|
|
112
|
+
variant: BadgeVariant,
|
|
113
|
+
tone: BadgeTone | undefined,
|
|
114
|
+
): { variant: BadgeStyleVariant; tone: BadgeTone; ignoredTone: boolean } {
|
|
115
|
+
if (variant in SHORTCUTS) {
|
|
116
|
+
const shortcut = SHORTCUTS[variant as BadgeVariantShortcut];
|
|
117
|
+
return { ...shortcut, ignoredTone: tone !== undefined && tone !== shortcut.tone };
|
|
118
|
+
}
|
|
119
|
+
const v = variant as BadgeStyleVariant;
|
|
120
|
+
return { variant: v, tone: tone ?? NATURAL_TONE[v], ignoredTone: false };
|
|
121
|
+
}
|
|
@@ -3,17 +3,35 @@ import {
|
|
|
3
3
|
Component,
|
|
4
4
|
DestroyRef,
|
|
5
5
|
ElementRef,
|
|
6
|
+
Renderer2,
|
|
6
7
|
booleanAttribute,
|
|
7
8
|
computed,
|
|
9
|
+
effect,
|
|
8
10
|
inject,
|
|
9
11
|
input,
|
|
12
|
+
isDevMode,
|
|
10
13
|
} from '@angular/core';
|
|
11
14
|
import { cn } from '@/components/ui/utils/cn';
|
|
12
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
type ButtonSize,
|
|
17
|
+
type ButtonTone,
|
|
18
|
+
type ButtonVariant,
|
|
19
|
+
buttonVariants,
|
|
20
|
+
resolveButtonStyle,
|
|
21
|
+
} from './button.variants';
|
|
22
|
+
|
|
23
|
+
/** El aviso de atajo + tone sale una sola vez por página. */
|
|
24
|
+
let warnedShortcutWithTone = false;
|
|
13
25
|
|
|
14
26
|
/**
|
|
15
27
|
* Botón de Mimi sobre el elemento nativo: `<button mimiBtn>` o `<a mimiBtn>`.
|
|
16
28
|
*
|
|
29
|
+
* - `variant` (cómo se ve: solid, soft, outline, ghost, link) y `tone` (de qué color: primary,
|
|
30
|
+
* secondary, success, warning, info, danger). Sin `tone`, cada variante usa su tono natural:
|
|
31
|
+
* outline y ghost son neutros (secondary); las demás, primary. `data-variant` y `data-tone`
|
|
32
|
+
* muestran los valores resueltos.
|
|
33
|
+
* - Los atajos de la 0.1.0 (`default`, `secondary`, `destructive`) siguen funcionando y ganan
|
|
34
|
+
* sobre `tone` (en modo desarrollo, avisa una vez).
|
|
17
35
|
* - `disabled` o `loading` deshabilitan: `disabled` nativo en <button>; en <a>,
|
|
18
36
|
* `aria-disabled="true"`, `tabindex="-1"` y el clic se cancela.
|
|
19
37
|
* - `loading` muestra un spinner centrado encima del contenido, que queda transparente en su
|
|
@@ -48,19 +66,21 @@ import { type ButtonSize, type ButtonVariant, buttonVariants } from './button.va
|
|
|
48
66
|
`,
|
|
49
67
|
host: {
|
|
50
68
|
'[class]': 'classes()',
|
|
51
|
-
'[attr.data-variant]': '
|
|
69
|
+
'[attr.data-variant]': 'resolved().variant',
|
|
70
|
+
'[attr.data-tone]': 'resolved().tone',
|
|
52
71
|
'[attr.data-size]': 'size()',
|
|
53
72
|
'[attr.data-loading]': 'loading() ? "" : null',
|
|
54
73
|
'[attr.data-disabled]': 'isDisabled() ? "" : null',
|
|
55
74
|
'[attr.disabled]': 'isNativeButton && isDisabled() ? "" : null',
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
'[attr.aria-busy]': 'loading() ? "true" : null',
|
|
75
|
+
// aria-disabled, tabindex y aria-busy no van aquí: un binding de host que vale null borra el
|
|
76
|
+
// atributo que haya puesto el usuario. Los pone `ownAttributes` solo cuando hacen falta.
|
|
59
77
|
},
|
|
60
78
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
61
79
|
})
|
|
62
80
|
export class MimiButton {
|
|
63
|
-
|
|
81
|
+
/** Incluye los atajos de la 0.1.x (`ButtonVariantShortcut`, deprecados). */
|
|
82
|
+
readonly variant = input<ButtonVariant>('solid');
|
|
83
|
+
readonly tone = input<ButtonTone>();
|
|
64
84
|
readonly size = input<ButtonSize>('default');
|
|
65
85
|
readonly disabled = input(false, { transform: booleanAttribute });
|
|
66
86
|
readonly loading = input(false, { transform: booleanAttribute });
|
|
@@ -71,15 +91,62 @@ export class MimiButton {
|
|
|
71
91
|
|
|
72
92
|
protected readonly isDisabled = computed(() => this.disabled() || this.loading());
|
|
73
93
|
|
|
94
|
+
protected readonly resolved = computed(() => {
|
|
95
|
+
const style = resolveButtonStyle(this.variant(), this.tone());
|
|
96
|
+
if (style.ignoredTone && isDevMode() && !warnedShortcutWithTone) {
|
|
97
|
+
warnedShortcutWithTone = true;
|
|
98
|
+
console.warn(
|
|
99
|
+
`[Mimi] Button: variant="${this.variant()}" ya define el color; se ignora ` +
|
|
100
|
+
`tone="${this.tone()}". Usa variant="${style.variant}" con tone (los atajos se ` +
|
|
101
|
+
`quitan en la 1.0).`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return style;
|
|
105
|
+
});
|
|
106
|
+
|
|
74
107
|
protected readonly classes = computed(() =>
|
|
75
108
|
cn(
|
|
76
|
-
buttonVariants({
|
|
109
|
+
buttonVariants({
|
|
110
|
+
variant: this.resolved().variant,
|
|
111
|
+
tone: this.resolved().tone,
|
|
112
|
+
size: this.size(),
|
|
113
|
+
}),
|
|
77
114
|
// El diseño deshabilitado no tiene sombra; cargando la conserva.
|
|
78
115
|
this.disabled() && !this.loading() && 'shadow-none',
|
|
79
116
|
this.userClass(),
|
|
80
117
|
),
|
|
81
118
|
);
|
|
82
119
|
|
|
120
|
+
/**
|
|
121
|
+
* `aria-disabled="true"` y `tabindex="-1"` en un <a> deshabilitado, y `aria-busy="true"` mientras
|
|
122
|
+
* carga. Solo se escriben cuando hacen falta; al dejar de hacer falta vuelve el valor que tenía el
|
|
123
|
+
* elemento (el del usuario, fijo o enlazado) o se quitan. Así `<button mimiBtn aria-disabled="true">`
|
|
124
|
+
* (un botón deshabilitado que sigue enfocable) conserva su atributo.
|
|
125
|
+
*/
|
|
126
|
+
private readonly ownAttributes = (() => {
|
|
127
|
+
const renderer = inject(Renderer2);
|
|
128
|
+
/** Lo que había antes de que Mimi escribiera cada atributo (null: no había). */
|
|
129
|
+
const previous = new Map<string, string | null>();
|
|
130
|
+
const apply = (name: string, value: string | null) => {
|
|
131
|
+
if (value !== null) {
|
|
132
|
+
if (!previous.has(name)) previous.set(name, this.element.getAttribute(name));
|
|
133
|
+
renderer.setAttribute(this.element, name, value);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (!previous.has(name)) return;
|
|
137
|
+
const before = previous.get(name) ?? null;
|
|
138
|
+
previous.delete(name);
|
|
139
|
+
if (before === null) renderer.removeAttribute(this.element, name);
|
|
140
|
+
else renderer.setAttribute(this.element, name, before);
|
|
141
|
+
};
|
|
142
|
+
return effect(() => {
|
|
143
|
+
const disabledLink = !this.isNativeButton && this.isDisabled();
|
|
144
|
+
apply('aria-disabled', disabledLink ? 'true' : null);
|
|
145
|
+
apply('tabindex', disabledLink ? '-1' : null);
|
|
146
|
+
apply('aria-busy', this.loading() ? 'true' : null);
|
|
147
|
+
});
|
|
148
|
+
})();
|
|
149
|
+
|
|
83
150
|
/**
|
|
84
151
|
* En <a> deshabilitado cancela el clic (en <button> el navegador ya lo bloquea). Se escucha
|
|
85
152
|
* en la fase de captura: en el propio elemento corre antes que los (click) del usuario, que
|
|
@@ -1,14 +1,96 @@
|
|
|
1
1
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { buttonFocusStyles, controlDisabledStyles } from '@/components/ui/utils/control-styles';
|
|
3
|
+
|
|
4
|
+
/** Cómo se ve el botón (spec 4, «Variante y tono»), sin los atajos de la 0.1.x. */
|
|
5
|
+
export type ButtonStyleVariant = 'solid' | 'soft' | 'outline' | 'ghost' | 'link';
|
|
6
|
+
|
|
7
|
+
/** De qué color es. `danger` usa los tokens `destructive`. */
|
|
8
|
+
export type ButtonTone = 'primary' | 'secondary' | 'success' | 'warning' | 'info' | 'danger';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Atajos de la 0.1.0, cuando el color era una variante. Siguen funcionando, sin error ni aviso:
|
|
12
|
+
* `default` = solid + primary, `secondary` = solid + secondary, `destructive` = solid + danger.
|
|
13
|
+
* @deprecated Se quitan en la 1.0 (spec 12). Usa `variant` y `tone`.
|
|
14
|
+
*/
|
|
15
|
+
export type ButtonVariantShortcut = 'default' | 'secondary' | 'destructive';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Lo que acepta `variant`: las variantes y, hasta la 1.0, los atajos de la 0.1.x (deprecados), para
|
|
19
|
+
* que el código que tipaba `ButtonVariant` con `'default'` o `'destructive'` siga compilando.
|
|
20
|
+
*/
|
|
21
|
+
export type ButtonVariant = ButtonStyleVariant | ButtonVariantShortcut;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Colores de cada tono en cada variante: una fila por tono, una entrada por variante. Todas las
|
|
25
|
+
* clases van completas y literales para que Tailwind las detecte. Diseño: docs/design/
|
|
26
|
+
* «Mimi F2 Tokens y Tonos» (solid, soft y outline con tono). Las celdas de secondary, ghost y
|
|
27
|
+
* link con tono son provisionales, sin diseño (spec 13).
|
|
28
|
+
*/
|
|
29
|
+
const TONE_CLASSES = {
|
|
30
|
+
primary: {
|
|
31
|
+
solid:
|
|
32
|
+
'bg-primary text-primary-foreground shadow-primary hover:not-disabled:bg-primary-hover hover:not-disabled:shadow-primary-hover active:ring-3 active:ring-ring-soft',
|
|
33
|
+
soft: 'bg-secondary text-secondary-foreground hover:not-disabled:bg-primary-soft-hover',
|
|
34
|
+
outline:
|
|
35
|
+
'border-primary-border bg-transparent text-secondary-foreground hover:not-disabled:bg-primary-subtle',
|
|
36
|
+
ghost:
|
|
37
|
+
'bg-transparent text-foreground hover:not-disabled:bg-accent hover:not-disabled:text-accent-foreground',
|
|
38
|
+
link: 'bg-transparent text-primary',
|
|
39
|
+
},
|
|
40
|
+
secondary: {
|
|
41
|
+
solid:
|
|
42
|
+
'bg-secondary text-secondary-foreground shadow-neutral hover:not-disabled:bg-secondary-hover hover:not-disabled:shadow-neutral-hover active:ring-3 active:ring-ring-soft',
|
|
43
|
+
soft: 'bg-muted text-foreground hover:not-disabled:bg-accent hover:not-disabled:text-accent-foreground',
|
|
44
|
+
outline:
|
|
45
|
+
'border-border bg-card text-foreground shadow-neutral hover:not-disabled:bg-accent hover:not-disabled:text-accent-foreground hover:not-disabled:shadow-neutral-hover active:ring-3 active:ring-ring-soft',
|
|
46
|
+
ghost:
|
|
47
|
+
'bg-transparent text-foreground hover:not-disabled:bg-accent hover:not-disabled:text-accent-foreground',
|
|
48
|
+
link: 'bg-transparent text-foreground',
|
|
49
|
+
},
|
|
50
|
+
success: {
|
|
51
|
+
solid:
|
|
52
|
+
'bg-success text-success-foreground shadow-success hover:not-disabled:bg-success-hover hover:not-disabled:shadow-success-hover active:ring-3 active:ring-success-ring',
|
|
53
|
+
soft: 'bg-success-soft text-success-soft-foreground hover:not-disabled:bg-success-soft-hover',
|
|
54
|
+
outline:
|
|
55
|
+
'border-success-border bg-transparent text-success-soft-foreground hover:not-disabled:bg-success-subtle',
|
|
56
|
+
ghost: 'bg-transparent text-success-soft-foreground hover:not-disabled:bg-success-soft',
|
|
57
|
+
link: 'bg-transparent text-success-soft-foreground',
|
|
58
|
+
},
|
|
59
|
+
warning: {
|
|
60
|
+
solid:
|
|
61
|
+
'bg-warning text-warning-foreground shadow-warning hover:not-disabled:bg-warning-hover hover:not-disabled:shadow-warning-hover active:ring-3 active:ring-warning-ring',
|
|
62
|
+
soft: 'bg-warning-soft text-warning-soft-foreground hover:not-disabled:bg-warning-soft-hover',
|
|
63
|
+
outline:
|
|
64
|
+
'border-warning-border bg-transparent text-warning-soft-foreground hover:not-disabled:bg-warning-subtle',
|
|
65
|
+
ghost: 'bg-transparent text-warning-soft-foreground hover:not-disabled:bg-warning-soft',
|
|
66
|
+
link: 'bg-transparent text-warning-soft-foreground',
|
|
67
|
+
},
|
|
68
|
+
info: {
|
|
69
|
+
solid:
|
|
70
|
+
'bg-info text-info-foreground shadow-info hover:not-disabled:bg-info-hover hover:not-disabled:shadow-info-hover active:ring-3 active:ring-info-ring',
|
|
71
|
+
soft: 'bg-info-soft text-info-soft-foreground hover:not-disabled:bg-info-soft-hover',
|
|
72
|
+
outline:
|
|
73
|
+
'border-info-border bg-transparent text-info-soft-foreground hover:not-disabled:bg-info-subtle',
|
|
74
|
+
ghost: 'bg-transparent text-info-soft-foreground hover:not-disabled:bg-info-soft',
|
|
75
|
+
link: 'bg-transparent text-info-soft-foreground',
|
|
76
|
+
},
|
|
77
|
+
danger: {
|
|
78
|
+
solid:
|
|
79
|
+
'bg-destructive text-destructive-foreground shadow-destructive hover:not-disabled:bg-destructive-hover hover:not-disabled:shadow-destructive-hover active:ring-3 active:ring-destructive-ring',
|
|
80
|
+
soft: 'bg-destructive-soft-bg text-destructive-soft-foreground hover:not-disabled:bg-destructive-soft-hover',
|
|
81
|
+
outline:
|
|
82
|
+
'border-destructive-border bg-transparent text-destructive-soft-foreground hover:not-disabled:bg-destructive-subtle',
|
|
83
|
+
ghost:
|
|
84
|
+
'bg-transparent text-destructive-soft-foreground hover:not-disabled:bg-destructive-soft-bg',
|
|
85
|
+
link: 'bg-transparent text-destructive-soft-foreground',
|
|
86
|
+
},
|
|
87
|
+
} as const satisfies Record<ButtonTone, Record<ButtonStyleVariant, string>>;
|
|
7
88
|
|
|
8
89
|
/**
|
|
9
90
|
* Clases de mimiBtn (docs/design/Mimi Componentes.dc.html, sección 1). Todas literales para
|
|
10
91
|
* que Tailwind las detecte. Las medidas siguen la cascada de la spec 6.2:
|
|
11
|
-
* --mimi-btn-* → --mimi-control-* / --mimi-radius → valor del diseño.
|
|
92
|
+
* --mimi-btn-* → --mimi-control-* / --mimi-radius → valor del diseño. La variante da la forma
|
|
93
|
+
* y el tono, los colores (TONE_CLASSES).
|
|
12
94
|
*/
|
|
13
95
|
export const buttonVariants = cva(
|
|
14
96
|
[
|
|
@@ -20,41 +102,83 @@ export const buttonVariants = cva(
|
|
|
20
102
|
buttonFocusStyles,
|
|
21
103
|
'focus-visible:outline-[length:var(--mimi-btn-focus-ring-width,2px)]',
|
|
22
104
|
controlDisabledStyles,
|
|
23
|
-
|
|
105
|
+
// Escala al presionar: --mimi-btn-press-scale → --mimi-press-scale (spec 6.6).
|
|
106
|
+
'mimi-transition active:scale-[var(--mimi-btn-press-scale,var(--mimi-press-scale))]',
|
|
24
107
|
// Cargando: deshabilitado, pero con opacidad 85% y cursor de progreso (gana a disabled:).
|
|
25
108
|
'aria-busy:disabled:cursor-progress aria-busy:disabled:opacity-85 aria-busy:aria-disabled:opacity-85',
|
|
26
109
|
],
|
|
27
110
|
{
|
|
28
111
|
variants: {
|
|
29
112
|
variant: {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
113
|
+
solid: '',
|
|
114
|
+
soft: '',
|
|
115
|
+
outline: '',
|
|
116
|
+
ghost: '',
|
|
117
|
+
link: 'underline-offset-4 hover:not-disabled:underline',
|
|
118
|
+
},
|
|
119
|
+
tone: {
|
|
120
|
+
primary: '',
|
|
121
|
+
secondary: '',
|
|
122
|
+
success: '',
|
|
123
|
+
warning: '',
|
|
124
|
+
info: '',
|
|
125
|
+
danger: '',
|
|
41
126
|
},
|
|
42
127
|
size: {
|
|
43
128
|
default:
|
|
44
|
-
'h-[var(--mimi-btn-height,var(--mimi-control-height,2.5rem))] gap-
|
|
129
|
+
'h-[var(--mimi-btn-height,var(--mimi-control-height,2.5rem))] gap-[var(--mimi-btn-gap,0.5rem)] px-[var(--mimi-btn-px,var(--mimi-btn-padding-x,1rem))] text-[length:var(--mimi-btn-font-size,0.875rem)]',
|
|
45
130
|
sm: 'h-[var(--mimi-btn-height-sm,var(--mimi-control-height-sm,2rem))] gap-1.5 px-3 text-[13px]',
|
|
46
131
|
lg: 'h-[var(--mimi-btn-height-lg,var(--mimi-control-height-lg,3rem))] gap-2 px-6 text-[15px]',
|
|
47
132
|
icon: 'size-[var(--mimi-btn-height,var(--mimi-control-height,2.5rem))] p-0',
|
|
48
133
|
},
|
|
49
134
|
},
|
|
50
135
|
compoundVariants: [
|
|
136
|
+
// Los colores: una combinación por cada tono y variante de TONE_CLASSES.
|
|
137
|
+
...Object.entries(TONE_CLASSES).flatMap(([tone, byVariant]) =>
|
|
138
|
+
Object.entries(byVariant).map(([variant, cls]) => ({
|
|
139
|
+
tone: tone as ButtonTone,
|
|
140
|
+
variant: variant as ButtonStyleVariant,
|
|
141
|
+
class: cls,
|
|
142
|
+
})),
|
|
143
|
+
),
|
|
51
144
|
// El enlace del diseño solo tiene 4px de padding horizontal.
|
|
52
145
|
{ variant: 'link', size: ['default', 'sm', 'lg'], class: 'px-1' },
|
|
53
146
|
],
|
|
54
|
-
defaultVariants: { variant: '
|
|
147
|
+
defaultVariants: { variant: 'solid', tone: 'primary', size: 'default' },
|
|
55
148
|
},
|
|
56
149
|
);
|
|
57
150
|
|
|
58
151
|
type ButtonVariantProps = VariantProps<typeof buttonVariants>;
|
|
59
|
-
export type ButtonVariant = NonNullable<ButtonVariantProps['variant']>;
|
|
60
152
|
export type ButtonSize = NonNullable<ButtonVariantProps['size']>;
|
|
153
|
+
|
|
154
|
+
/** Sin `tone`, cada variante usa su tono natural: outline y ghost son neutros, como en la 0.1.0. */
|
|
155
|
+
const NATURAL_TONE: Record<ButtonStyleVariant, ButtonTone> = {
|
|
156
|
+
solid: 'primary',
|
|
157
|
+
soft: 'primary',
|
|
158
|
+
outline: 'secondary',
|
|
159
|
+
ghost: 'secondary',
|
|
160
|
+
link: 'primary',
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const SHORTCUTS: Record<ButtonVariantShortcut, { variant: ButtonStyleVariant; tone: ButtonTone }> =
|
|
164
|
+
{
|
|
165
|
+
default: { variant: 'solid', tone: 'primary' },
|
|
166
|
+
secondary: { variant: 'solid', tone: 'secondary' },
|
|
167
|
+
destructive: { variant: 'solid', tone: 'danger' },
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Resuelve variante y tono. Un atajo de la 0.1.0 gana siempre; `ignoredTone` indica que además
|
|
172
|
+
* se pasó un `tone` que no se aplicó.
|
|
173
|
+
*/
|
|
174
|
+
export function resolveButtonStyle(
|
|
175
|
+
variant: ButtonVariant,
|
|
176
|
+
tone: ButtonTone | undefined,
|
|
177
|
+
): { variant: ButtonStyleVariant; tone: ButtonTone; ignoredTone: boolean } {
|
|
178
|
+
if (variant in SHORTCUTS) {
|
|
179
|
+
const shortcut = SHORTCUTS[variant as ButtonVariantShortcut];
|
|
180
|
+
return { ...shortcut, ignoredTone: tone !== undefined && tone !== shortcut.tone };
|
|
181
|
+
}
|
|
182
|
+
const v = variant as ButtonStyleVariant;
|
|
183
|
+
return { variant: v, tone: tone ?? NATURAL_TONE[v], ignoredTone: false };
|
|
184
|
+
}
|
|
@@ -24,9 +24,9 @@ export class MimiCard {
|
|
|
24
24
|
readonly userClass = input('', { alias: 'class' });
|
|
25
25
|
protected readonly classes = computed(() =>
|
|
26
26
|
cn(
|
|
27
|
-
'flex flex-col bg-card text-card-foreground',
|
|
27
|
+
'flex flex-col bg-[color:var(--mimi-card-bg,var(--mimi-card))] text-[color:var(--mimi-card-fg,var(--mimi-card-foreground))]',
|
|
28
28
|
'rounded-[var(--mimi-card-radius,var(--mimi-radius-card))]',
|
|
29
|
-
'border-[length:var(--mimi-card-border-width,1px)] border-border',
|
|
29
|
+
'border-[length:var(--mimi-card-border-width,1px)] border-[color:var(--mimi-card-border,var(--mimi-border))]',
|
|
30
30
|
'shadow-[shadow:var(--mimi-card-shadow,var(--mimi-shadow-card))]',
|
|
31
31
|
this.userClass(),
|
|
32
32
|
),
|
|
@@ -44,7 +44,7 @@ export class MimiCardHeader {
|
|
|
44
44
|
readonly userClass = input('', { alias: 'class' });
|
|
45
45
|
protected readonly classes = computed(() =>
|
|
46
46
|
cn(
|
|
47
|
-
'flex flex-col gap-1.5 p-[var(--mimi-card-padding-header,1.
|
|
47
|
+
'flex flex-col gap-1.5 p-[var(--mimi-card-padding-header,var(--mimi-card-padding,1.5rem)_var(--mimi-card-padding,1.5rem)_1rem)]',
|
|
48
48
|
this.userClass(),
|
|
49
49
|
),
|
|
50
50
|
);
|
|
@@ -101,7 +101,10 @@ export class MimiCardDescription {
|
|
|
101
101
|
export class MimiCardContent {
|
|
102
102
|
readonly userClass = input('', { alias: 'class' });
|
|
103
103
|
protected readonly classes = computed(() =>
|
|
104
|
-
cn(
|
|
104
|
+
cn(
|
|
105
|
+
'block p-[var(--mimi-card-padding-content,0_var(--mimi-card-padding,1.5rem)_1.25rem)] first:pt-[var(--mimi-card-padding,1.5rem)]',
|
|
106
|
+
this.userClass(),
|
|
107
|
+
),
|
|
105
108
|
);
|
|
106
109
|
}
|
|
107
110
|
|
|
@@ -116,7 +119,7 @@ export class MimiCardFooter {
|
|
|
116
119
|
readonly userClass = input('', { alias: 'class' });
|
|
117
120
|
protected readonly classes = computed(() =>
|
|
118
121
|
cn(
|
|
119
|
-
'flex items-center justify-end gap-2 p-[var(--mimi-card-padding-footer,
|
|
122
|
+
'flex items-center justify-end gap-2 p-[var(--mimi-card-padding-footer,0_var(--mimi-card-padding,1.5rem)_var(--mimi-card-padding,1.5rem))] first:pt-[var(--mimi-card-padding,1.5rem)]',
|
|
120
123
|
this.userClass(),
|
|
121
124
|
),
|
|
122
125
|
);
|
|
@@ -150,10 +150,10 @@ export class MimiCheckbox implements FormCheckboxControl, MimiFieldControl {
|
|
|
150
150
|
|
|
151
151
|
// Caja de 16px. Marcada o mixta: primary con su sombra. Foco: borde ring (sin marcar) y contorno.
|
|
152
152
|
protected readonly boxClasses = cn(
|
|
153
|
-
'peer inline-flex size-
|
|
153
|
+
'peer inline-flex size-[var(--mimi-checkbox-size,1rem)] shrink-0 items-center justify-center rounded-[var(--mimi-checkbox-radius,var(--mimi-radius-sm))] border border-[color:var(--mimi-checkbox-border,var(--mimi-input))] bg-input-background text-[color:var(--mimi-checkbox-check,var(--mimi-primary-foreground))]',
|
|
154
154
|
'mimi-transition active:scale-(--mimi-press-scale-sm)',
|
|
155
|
-
'data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:shadow-primary',
|
|
156
|
-
'data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:shadow-primary',
|
|
155
|
+
'data-[state=checked]:border-[color:var(--mimi-checkbox-checked-bg,var(--mimi-primary))] data-[state=checked]:bg-[color:var(--mimi-checkbox-checked-bg,var(--mimi-primary))] data-[state=checked]:shadow-primary',
|
|
156
|
+
'data-[state=indeterminate]:border-[color:var(--mimi-checkbox-checked-bg,var(--mimi-primary))] data-[state=indeterminate]:bg-[color:var(--mimi-checkbox-checked-bg,var(--mimi-primary))] data-[state=indeterminate]:shadow-primary',
|
|
157
157
|
'data-[state=unchecked]:focus-visible:border-ring data-[state=unchecked]:aria-invalid:border-destructive',
|
|
158
158
|
'disabled:shadow-none',
|
|
159
159
|
buttonFocusStyles,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { InjectionToken, type Provider } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Textos de Confirm, en archivo propio y sin @angular/cdk: así se pueden proveer en
|
|
5
|
+
* `app.config.ts` sin llevar el Dialog al chunk inicial.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Textos de los botones de Confirm. */
|
|
9
|
+
export interface MimiConfirmText {
|
|
10
|
+
/** Botón que confirma. */
|
|
11
|
+
confirm: string;
|
|
12
|
+
/** Botón que cancela. */
|
|
13
|
+
cancel: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Textos predeterminados (inglés). */
|
|
17
|
+
export const MIMI_CONFIRM_TEXT_EN: MimiConfirmText = {
|
|
18
|
+
confirm: 'Confirm',
|
|
19
|
+
cancel: 'Cancel',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Textos en español: `provideMimiConfirmText(MIMI_CONFIRM_TEXT_ES)`. */
|
|
23
|
+
export const MIMI_CONFIRM_TEXT_ES: MimiConfirmText = {
|
|
24
|
+
confirm: 'Confirmar',
|
|
25
|
+
cancel: 'Cancelar',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Textos activos. Sin `provideMimiConfirmText`, los predeterminados en inglés. */
|
|
29
|
+
export const MIMI_CONFIRM_TEXT = new InjectionToken<MimiConfirmText>('MIMI_CONFIRM_TEXT', {
|
|
30
|
+
providedIn: 'root',
|
|
31
|
+
factory: () => MIMI_CONFIRM_TEXT_EN,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Cambia los textos de Confirm. Lo que no definas sigue en inglés:
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* provideMimiConfirmText(MIMI_CONFIRM_TEXT_ES)
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* Va en `app.config.ts` o en los `providers` de un componente o ruta. `confirmText` y
|
|
42
|
+
* `cancelText` de cada llamada ganan sobre estos.
|
|
43
|
+
*/
|
|
44
|
+
export function provideMimiConfirmText(text: Partial<MimiConfirmText>): Provider {
|
|
45
|
+
return { provide: MIMI_CONFIRM_TEXT, useValue: { ...MIMI_CONFIRM_TEXT_EN, ...text } };
|
|
46
|
+
}
|