@satellatickets/core 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +36 -3
- package/dist/index.d.ts +242 -0
- package/dist/index.js +249 -0
- package/package.json +49 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Roberto Rodriguez Carbonell
|
|
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
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @satellatickets/core
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
La parte de satella-ui que no depende de la plataforma: los contratos de props de cada componente, las constantes de variantes, el contexto de tema y los hooks headless. Solo depende de `react` y de `@satellatickets/tokens`; nunca importa `react-dom` ni `react-native`.
|
|
4
|
+
|
|
5
|
+
`@satellatickets/ui` lo usa para que `<Button variant="primary">` signifique lo mismo en web y en móvil. Puede instalarse solo si necesitas la lógica o los tipos sin las vistas.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { buttonVariants, useButton, useTheme, type ButtonProps } from '@satellatickets/core';
|
|
9
|
+
|
|
10
|
+
buttonVariants.variant; // ['primary', 'secondary', 'ghost', 'danger']
|
|
11
|
+
|
|
12
|
+
// Comportamiento común de un botón: deshabilitado o cargando no dispara onPress.
|
|
13
|
+
const { interactive, press } = useButton({ loading, onPress });
|
|
14
|
+
|
|
15
|
+
// Tokens del tema activo, con la marca aplicada.
|
|
16
|
+
const theme = useTheme();
|
|
17
|
+
theme.color.action.primary;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Qué contiene
|
|
21
|
+
|
|
22
|
+
| Qué | Ejemplos |
|
|
23
|
+
| ----------------------- | ------------------------------------------------------------------------------- |
|
|
24
|
+
| Contratos de props | `ButtonProps`, `BadgeProps`, `BoxProps`, `StackProps`, `TextProps`, `IconProps` |
|
|
25
|
+
| Constantes de variantes | `buttonVariants`, `badgeVariants`, `textVariants`, `iconSizes` |
|
|
26
|
+
| Tema | `UIContext`, `useTheme`, `useColorScheme`, `useBrand`, `resolveTheme` |
|
|
27
|
+
| Hooks headless | `useButton` |
|
|
28
|
+
|
|
29
|
+
Las variantes se exportan como arrays `as const`, además de como tipos, para poder recorrerlas: en historias, en selectores o en tests.
|
|
30
|
+
|
|
31
|
+
## Instalación
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @satellatickets/core
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Requiere `react >=19.0`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { BrandName, Theme, ThemeName, ThemeOverrides } from "@satellatickets/tokens";
|
|
3
|
+
//#region src/hooks/use-button.d.ts
|
|
4
|
+
export interface UseButtonOptions {
|
|
5
|
+
disabled?: boolean | undefined;
|
|
6
|
+
loading?: boolean | undefined;
|
|
7
|
+
onPress?: (() => void) | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface UseButtonResult {
|
|
10
|
+
disabled: boolean;
|
|
11
|
+
loading: boolean;
|
|
12
|
+
/** `false` si está deshabilitado o cargando: el botón no responde a la pulsación. */
|
|
13
|
+
interactive: boolean;
|
|
14
|
+
/** Llama a `onPress` solo si el botón es interactivo. */
|
|
15
|
+
press: () => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Comportamiento de `Button` común a web y nativo: un botón deshabilitado o que
|
|
19
|
+
* está cargando no dispara `onPress`. Cada vista añade su accesibilidad
|
|
20
|
+
* (`disabled`/`aria-busy` en web, `accessibilityState` en nativo).
|
|
21
|
+
*/
|
|
22
|
+
export declare function useButton({ disabled, loading, onPress }: UseButtonOptions): UseButtonResult;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/types/ui-provider.d.ts
|
|
25
|
+
/** Modo de tema que acepta `UIProvider` (ADR-010). */
|
|
26
|
+
export declare const themeModes: readonly ["light", "dark", "system"];
|
|
27
|
+
export type ThemeMode = (typeof themeModes)[number];
|
|
28
|
+
/** Esquema resuelto: siempre uno de los temas definidos en tokens. */
|
|
29
|
+
export type ColorScheme = ThemeName;
|
|
30
|
+
export interface UIProviderProps {
|
|
31
|
+
/** Tema claro, oscuro o el del sistema (por defecto `system`). */
|
|
32
|
+
theme?: ThemeMode | undefined;
|
|
33
|
+
/** Marca registrada en tokens. Sin marca se usa la identidad por defecto. */
|
|
34
|
+
brand?: BrandName | undefined;
|
|
35
|
+
children: ReactNode;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/theme/context.d.ts
|
|
39
|
+
interface UIContextValue {
|
|
40
|
+
theme: Theme;
|
|
41
|
+
colorScheme: ColorScheme;
|
|
42
|
+
mode: ThemeMode;
|
|
43
|
+
brand: BrandName | undefined;
|
|
44
|
+
}
|
|
45
|
+
export declare function createUIContextValue(mode: ThemeMode, systemScheme: ColorScheme, brand?: BrandName): UIContextValue;
|
|
46
|
+
/** Sin `UIProvider` se usa el tema claro por defecto, para que los componentes funcionen aislados. */
|
|
47
|
+
export declare const UIContext: import("react").Context<UIContextValue>;
|
|
48
|
+
export declare function useUIContext(): UIContextValue;
|
|
49
|
+
/** Tema resuelto (tokens con la marca aplicada). Imprescindible en las vistas nativas (ADR-008). */
|
|
50
|
+
export declare function useTheme(): Theme;
|
|
51
|
+
/** `'light' | 'dark'` ya resuelto, también con `theme="system"`. */
|
|
52
|
+
export declare function useColorScheme(): ColorScheme;
|
|
53
|
+
export declare function useBrand(): BrandName | undefined;
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/theme/resolve-theme.d.ts
|
|
56
|
+
/** Combina los overrides de una marca sobre un tema (ADR-010). Los arrays (sombras) se sustituyen enteros. */
|
|
57
|
+
export declare function mergeTheme(base: Theme, overrides: ThemeOverrides): Theme;
|
|
58
|
+
/** Tema resuelto para un esquema y una marca; memoizado para que la identidad sea estable. */
|
|
59
|
+
export declare function resolveTheme(scheme: ThemeName, brand?: BrandName): Theme;
|
|
60
|
+
export declare function resolveColorScheme(mode: ThemeMode, systemScheme: ColorScheme): ColorScheme;
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/types/badge.d.ts
|
|
63
|
+
/**
|
|
64
|
+
* Variantes de `Badge` (ADR-009): una por cada color de `color.feedback`. Las historias
|
|
65
|
+
* las usan para `argTypes` y la matriz `AllVariants`.
|
|
66
|
+
*/
|
|
67
|
+
export declare const badgeVariants: readonly ["success", "warning", "danger", "info"];
|
|
68
|
+
export type BadgeVariant = (typeof badgeVariants)[number];
|
|
69
|
+
export interface BadgeProps {
|
|
70
|
+
/** Significado del estado que etiqueta (por defecto `info`). */
|
|
71
|
+
variant?: BadgeVariant | undefined;
|
|
72
|
+
/** `data-testid` en web, `testID` en nativo. */
|
|
73
|
+
testID?: string | undefined;
|
|
74
|
+
children: ReactNode;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/types/box.d.ts
|
|
78
|
+
export type SpaceToken = keyof Theme['space'];
|
|
79
|
+
export type RadiusToken = keyof Theme['radius'];
|
|
80
|
+
export type ShadowToken = keyof Theme['shadow'];
|
|
81
|
+
export type BackgroundToken = keyof Theme['color']['bg'];
|
|
82
|
+
export type BorderColorToken = keyof Theme['color']['border'];
|
|
83
|
+
/** Constantes para `argTypes` y matrices de historias (ADR-009). */
|
|
84
|
+
export declare const spaceTokens: SpaceToken[];
|
|
85
|
+
export declare const radiusTokens: RadiusToken[];
|
|
86
|
+
export declare const shadowTokens: ShadowToken[];
|
|
87
|
+
export declare const backgroundTokens: BackgroundToken[];
|
|
88
|
+
export declare const borderColorTokens: BorderColorToken[];
|
|
89
|
+
/** Contenedor base: relleno, fondo, radio, borde y sombra, siempre con tokens. */
|
|
90
|
+
export interface BoxProps {
|
|
91
|
+
padding?: SpaceToken | undefined;
|
|
92
|
+
paddingX?: SpaceToken | undefined;
|
|
93
|
+
paddingY?: SpaceToken | undefined;
|
|
94
|
+
background?: BackgroundToken | undefined;
|
|
95
|
+
radius?: RadiusToken | undefined;
|
|
96
|
+
/** Borde fino (`borderWidth.thin`) con el color indicado. */
|
|
97
|
+
borderColor?: BorderColorToken | undefined;
|
|
98
|
+
shadow?: ShadowToken | undefined;
|
|
99
|
+
flex?: number | undefined;
|
|
100
|
+
/** `data-testid` en web, `testID` en nativo. */
|
|
101
|
+
testID?: string | undefined;
|
|
102
|
+
children?: ReactNode;
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/types/button.d.ts
|
|
106
|
+
/** Variantes de `Button` (ADR-009). Las historias las usan para `argTypes` y la matriz `AllVariants`. */
|
|
107
|
+
export declare const buttonVariants: {
|
|
108
|
+
readonly variant: readonly ["primary", "secondary", "ghost", "danger"];
|
|
109
|
+
readonly size: readonly ["sm", "md", "lg"];
|
|
110
|
+
};
|
|
111
|
+
export type ButtonVariant = (typeof buttonVariants.variant)[number];
|
|
112
|
+
export type ButtonSize = (typeof buttonVariants.size)[number];
|
|
113
|
+
/** Tamaño del icono (y del spinner de carga) para cada tamaño de botón. */
|
|
114
|
+
export declare const buttonIconSize: {
|
|
115
|
+
readonly sm: "sm";
|
|
116
|
+
readonly md: "sm";
|
|
117
|
+
readonly lg: "md";
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Contrato de `Button`. El conjunto de iconos lo aporta `@satellatickets/icons`,
|
|
121
|
+
* que está fuera de `core` (ADR-002), por eso el nombre es un parámetro de tipo.
|
|
122
|
+
*/
|
|
123
|
+
export interface ButtonProps<IconName extends string = string> {
|
|
124
|
+
/** Jerarquía visual de la acción (por defecto `primary`). */
|
|
125
|
+
variant?: ButtonVariant | undefined;
|
|
126
|
+
size?: ButtonSize | undefined;
|
|
127
|
+
/** No se puede pulsar ni enfocar. */
|
|
128
|
+
disabled?: boolean | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* Acción en curso: muestra un spinner y no dispara `onPress`, pero conserva el foco
|
|
131
|
+
* y su anchura para que la interfaz no salte.
|
|
132
|
+
*/
|
|
133
|
+
loading?: boolean | undefined;
|
|
134
|
+
/** Icono delante del texto. */
|
|
135
|
+
iconStart?: IconName | undefined;
|
|
136
|
+
/** Icono detrás del texto. */
|
|
137
|
+
iconEnd?: IconName | undefined;
|
|
138
|
+
/** Ocupa todo el ancho de su contenedor. */
|
|
139
|
+
fullWidth?: boolean | undefined;
|
|
140
|
+
/** Se pulsa con ratón, teclado o toque. Nombre neutral: la vista web lo mapea a `click`. */
|
|
141
|
+
onPress?: (() => void) | undefined;
|
|
142
|
+
/** Nombre accesible cuando el texto visible no basta para describir la acción. */
|
|
143
|
+
accessibilityLabel?: string | undefined;
|
|
144
|
+
/** `data-testid` en web, `testID` en nativo. */
|
|
145
|
+
testID?: string | undefined;
|
|
146
|
+
children: ReactNode;
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/types/text.d.ts
|
|
150
|
+
export declare const textVariants: readonly ["hero", "display", "title", "heading", "subheading", "body", "bodySmall", "label", "caption", "code"];
|
|
151
|
+
export type TextVariant = (typeof textVariants)[number];
|
|
152
|
+
export type TextColorToken = keyof Theme['color']['text'];
|
|
153
|
+
export declare const textColors: TextColorToken[];
|
|
154
|
+
export declare const textAligns: readonly ["left", "center", "right"];
|
|
155
|
+
export type TextAlign = (typeof textAligns)[number];
|
|
156
|
+
/** Tokens que componen una variante tipográfica; ambas vistas los leen del tema. */
|
|
157
|
+
export interface TextVariantStyle {
|
|
158
|
+
family: keyof Theme['font']['family'];
|
|
159
|
+
/** Tamaño; `font.lineHeight` comparte las mismas claves (lo comprueba el typecheck de las vistas). */
|
|
160
|
+
size: keyof Theme['font']['size'];
|
|
161
|
+
weight: keyof Theme['font']['weight'];
|
|
162
|
+
uppercase?: boolean;
|
|
163
|
+
/** Espaciado entre letras en px. */
|
|
164
|
+
letterSpacing?: number;
|
|
165
|
+
/** Nivel de encabezado semántico (h1–h4) si la variante es un título. */
|
|
166
|
+
headingLevel?: 1 | 2 | 3 | 4;
|
|
167
|
+
}
|
|
168
|
+
/** Escala tipográfica de Satella: Unbounded para titulares, Hanken Grotesk para texto, IBM Plex Mono para etiquetas. */
|
|
169
|
+
export declare const textVariantStyles: Readonly<Record<TextVariant, TextVariantStyle>>;
|
|
170
|
+
export interface TextProps {
|
|
171
|
+
variant?: TextVariant | undefined;
|
|
172
|
+
color?: TextColorToken | undefined;
|
|
173
|
+
align?: TextAlign | undefined;
|
|
174
|
+
/** Una sola línea con puntos suspensivos. */
|
|
175
|
+
truncate?: boolean | undefined;
|
|
176
|
+
testID?: string | undefined;
|
|
177
|
+
children?: ReactNode;
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/types/icon.d.ts
|
|
181
|
+
export declare const iconSizes: readonly ["sm", "md", "lg"];
|
|
182
|
+
export type IconSize = (typeof iconSizes)[number];
|
|
183
|
+
/** Tamaño en px/puntos de cada icono. */
|
|
184
|
+
export declare const iconSizePx: {
|
|
185
|
+
sm: number;
|
|
186
|
+
md: number;
|
|
187
|
+
lg: number;
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Contrato de `Icon`. El conjunto de nombres lo aporta `@satellatickets/icons`,
|
|
191
|
+
* que está fuera de `core` (ADR-002), por eso el nombre es un parámetro de tipo.
|
|
192
|
+
*/
|
|
193
|
+
export interface IconProps<Name extends string = string> {
|
|
194
|
+
name: Name;
|
|
195
|
+
size?: IconSize | undefined;
|
|
196
|
+
color?: TextColorToken | undefined;
|
|
197
|
+
/** Texto alternativo. Sin él, el icono es decorativo y se oculta a los lectores de pantalla. */
|
|
198
|
+
label?: string | undefined;
|
|
199
|
+
testID?: string | undefined;
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/types/spinner.d.ts
|
|
203
|
+
/**
|
|
204
|
+
* `Spinner` comparte la escala de `Icon`: así puede ocupar el sitio de un icono
|
|
205
|
+
* (por ejemplo, dentro de un botón que carga) sin mover el resto del contenido.
|
|
206
|
+
*/
|
|
207
|
+
export declare const spinnerSizes: readonly ["sm", "md", "lg"];
|
|
208
|
+
export type SpinnerSize = IconSize;
|
|
209
|
+
/** Tamaño en px/puntos de cada spinner. */
|
|
210
|
+
export declare const spinnerSizePx: {
|
|
211
|
+
sm: number;
|
|
212
|
+
md: number;
|
|
213
|
+
lg: number;
|
|
214
|
+
};
|
|
215
|
+
export interface SpinnerProps {
|
|
216
|
+
size?: SpinnerSize | undefined;
|
|
217
|
+
color?: TextColorToken | undefined;
|
|
218
|
+
/**
|
|
219
|
+
* Texto que anuncian los lectores de pantalla ("Cargando eventos"). Sin él, el
|
|
220
|
+
* spinner es decorativo: úsalo así solo si el estado de carga ya se comunica de otra forma.
|
|
221
|
+
*/
|
|
222
|
+
label?: string | undefined;
|
|
223
|
+
testID?: string | undefined;
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/types/stack.d.ts
|
|
227
|
+
export declare const stackDirections: readonly ["column", "row"];
|
|
228
|
+
export type StackDirection = (typeof stackDirections)[number];
|
|
229
|
+
export declare const stackAligns: readonly ["stretch", "start", "center", "end"];
|
|
230
|
+
export type StackAlign = (typeof stackAligns)[number];
|
|
231
|
+
export declare const stackJustifies: readonly ["start", "center", "end", "between", "around"];
|
|
232
|
+
export type StackJustify = (typeof stackJustifies)[number];
|
|
233
|
+
/** `Box` con layout flex: dirección, separación y alineación. */
|
|
234
|
+
export interface StackProps extends BoxProps {
|
|
235
|
+
direction?: StackDirection | undefined;
|
|
236
|
+
gap?: SpaceToken | undefined;
|
|
237
|
+
align?: StackAlign | undefined;
|
|
238
|
+
justify?: StackJustify | undefined;
|
|
239
|
+
wrap?: boolean | undefined;
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
242
|
+
export type { UIContextValue };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { createContext, useCallback, useContext } from "react";
|
|
2
|
+
import { brands, themes } from "@satellatickets/tokens";
|
|
3
|
+
//#region src/hooks/use-button.ts
|
|
4
|
+
/**
|
|
5
|
+
* Comportamiento de `Button` común a web y nativo: un botón deshabilitado o que
|
|
6
|
+
* está cargando no dispara `onPress`. Cada vista añade su accesibilidad
|
|
7
|
+
* (`disabled`/`aria-busy` en web, `accessibilityState` en nativo).
|
|
8
|
+
*/
|
|
9
|
+
function useButton({ disabled = false, loading = false, onPress }) {
|
|
10
|
+
const interactive = !disabled && !loading;
|
|
11
|
+
return {
|
|
12
|
+
disabled,
|
|
13
|
+
loading,
|
|
14
|
+
interactive,
|
|
15
|
+
press: useCallback(() => {
|
|
16
|
+
if (interactive) onPress?.();
|
|
17
|
+
}, [interactive, onPress])
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/theme/resolve-theme.ts
|
|
22
|
+
function isPlainObject(value) {
|
|
23
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function merge(base, overrides) {
|
|
26
|
+
const result = { ...base };
|
|
27
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
28
|
+
if (value === void 0) continue;
|
|
29
|
+
const current = base[key];
|
|
30
|
+
result[key] = isPlainObject(current) && isPlainObject(value) ? merge(current, value) : value;
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
/** Combina los overrides de una marca sobre un tema (ADR-010). Los arrays (sombras) se sustituyen enteros. */
|
|
35
|
+
function mergeTheme(base, overrides) {
|
|
36
|
+
return merge(base, overrides);
|
|
37
|
+
}
|
|
38
|
+
const cache = /* @__PURE__ */ new Map();
|
|
39
|
+
/** Tema resuelto para un esquema y una marca; memoizado para que la identidad sea estable. */
|
|
40
|
+
function resolveTheme(scheme, brand) {
|
|
41
|
+
const key = `${scheme}/${brand ?? ""}`;
|
|
42
|
+
const cached = cache.get(key);
|
|
43
|
+
if (cached) return cached;
|
|
44
|
+
const base = themes[scheme];
|
|
45
|
+
const overrides = brand === void 0 ? void 0 : brands[brand]?.[scheme];
|
|
46
|
+
const resolved = overrides === void 0 ? base : mergeTheme(base, overrides);
|
|
47
|
+
cache.set(key, resolved);
|
|
48
|
+
return resolved;
|
|
49
|
+
}
|
|
50
|
+
function resolveColorScheme(mode, systemScheme) {
|
|
51
|
+
return mode === "system" ? systemScheme : mode;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/theme/context.ts
|
|
55
|
+
function createUIContextValue(mode, systemScheme, brand) {
|
|
56
|
+
const colorScheme = resolveColorScheme(mode, systemScheme);
|
|
57
|
+
return {
|
|
58
|
+
theme: resolveTheme(colorScheme, brand),
|
|
59
|
+
colorScheme,
|
|
60
|
+
mode,
|
|
61
|
+
brand
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Sin `UIProvider` se usa el tema claro por defecto, para que los componentes funcionen aislados. */
|
|
65
|
+
const UIContext = createContext(createUIContextValue("light", "light"));
|
|
66
|
+
function useUIContext() {
|
|
67
|
+
return useContext(UIContext);
|
|
68
|
+
}
|
|
69
|
+
/** Tema resuelto (tokens con la marca aplicada). Imprescindible en las vistas nativas (ADR-008). */
|
|
70
|
+
function useTheme() {
|
|
71
|
+
return useContext(UIContext).theme;
|
|
72
|
+
}
|
|
73
|
+
/** `'light' | 'dark'` ya resuelto, también con `theme="system"`. */
|
|
74
|
+
function useColorScheme() {
|
|
75
|
+
return useContext(UIContext).colorScheme;
|
|
76
|
+
}
|
|
77
|
+
function useBrand() {
|
|
78
|
+
return useContext(UIContext).brand;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/types/badge.ts
|
|
82
|
+
/**
|
|
83
|
+
* Variantes de `Badge` (ADR-009): una por cada color de `color.feedback`. Las historias
|
|
84
|
+
* las usan para `argTypes` y la matriz `AllVariants`.
|
|
85
|
+
*/
|
|
86
|
+
const badgeVariants = [
|
|
87
|
+
"success",
|
|
88
|
+
"warning",
|
|
89
|
+
"danger",
|
|
90
|
+
"info"
|
|
91
|
+
];
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/types/box.ts
|
|
94
|
+
/** Constantes para `argTypes` y matrices de historias (ADR-009). */
|
|
95
|
+
const spaceTokens = Object.keys(themes.light.space).map(Number);
|
|
96
|
+
const radiusTokens = Object.keys(themes.light.radius);
|
|
97
|
+
const shadowTokens = Object.keys(themes.light.shadow);
|
|
98
|
+
const backgroundTokens = Object.keys(themes.light.color.bg);
|
|
99
|
+
const borderColorTokens = Object.keys(themes.light.color.border);
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/types/button.ts
|
|
102
|
+
/** Variantes de `Button` (ADR-009). Las historias las usan para `argTypes` y la matriz `AllVariants`. */
|
|
103
|
+
const buttonVariants = {
|
|
104
|
+
variant: [
|
|
105
|
+
"primary",
|
|
106
|
+
"secondary",
|
|
107
|
+
"ghost",
|
|
108
|
+
"danger"
|
|
109
|
+
],
|
|
110
|
+
size: [
|
|
111
|
+
"sm",
|
|
112
|
+
"md",
|
|
113
|
+
"lg"
|
|
114
|
+
]
|
|
115
|
+
};
|
|
116
|
+
/** Tamaño del icono (y del spinner de carga) para cada tamaño de botón. */
|
|
117
|
+
const buttonIconSize = {
|
|
118
|
+
sm: "sm",
|
|
119
|
+
md: "sm",
|
|
120
|
+
lg: "md"
|
|
121
|
+
};
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/types/icon.ts
|
|
124
|
+
const iconSizes = [
|
|
125
|
+
"sm",
|
|
126
|
+
"md",
|
|
127
|
+
"lg"
|
|
128
|
+
];
|
|
129
|
+
/** Tamaño en px/puntos de cada icono. */
|
|
130
|
+
const iconSizePx = {
|
|
131
|
+
sm: 16,
|
|
132
|
+
md: 20,
|
|
133
|
+
lg: 24
|
|
134
|
+
};
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/types/spinner.ts
|
|
137
|
+
/**
|
|
138
|
+
* `Spinner` comparte la escala de `Icon`: así puede ocupar el sitio de un icono
|
|
139
|
+
* (por ejemplo, dentro de un botón que carga) sin mover el resto del contenido.
|
|
140
|
+
*/
|
|
141
|
+
const spinnerSizes = iconSizes;
|
|
142
|
+
/** Tamaño en px/puntos de cada spinner. */
|
|
143
|
+
const spinnerSizePx = iconSizePx;
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/types/stack.ts
|
|
146
|
+
const stackDirections = ["column", "row"];
|
|
147
|
+
const stackAligns = [
|
|
148
|
+
"stretch",
|
|
149
|
+
"start",
|
|
150
|
+
"center",
|
|
151
|
+
"end"
|
|
152
|
+
];
|
|
153
|
+
const stackJustifies = [
|
|
154
|
+
"start",
|
|
155
|
+
"center",
|
|
156
|
+
"end",
|
|
157
|
+
"between",
|
|
158
|
+
"around"
|
|
159
|
+
];
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/types/text.ts
|
|
162
|
+
const textVariants = [
|
|
163
|
+
"hero",
|
|
164
|
+
"display",
|
|
165
|
+
"title",
|
|
166
|
+
"heading",
|
|
167
|
+
"subheading",
|
|
168
|
+
"body",
|
|
169
|
+
"bodySmall",
|
|
170
|
+
"label",
|
|
171
|
+
"caption",
|
|
172
|
+
"code"
|
|
173
|
+
];
|
|
174
|
+
const textColors = Object.keys(themes.light.color.text);
|
|
175
|
+
const textAligns = [
|
|
176
|
+
"left",
|
|
177
|
+
"center",
|
|
178
|
+
"right"
|
|
179
|
+
];
|
|
180
|
+
/** Escala tipográfica de Satella: Unbounded para titulares, Hanken Grotesk para texto, IBM Plex Mono para etiquetas. */
|
|
181
|
+
const textVariantStyles = {
|
|
182
|
+
hero: {
|
|
183
|
+
family: "display",
|
|
184
|
+
size: "hero",
|
|
185
|
+
weight: "bold",
|
|
186
|
+
headingLevel: 1
|
|
187
|
+
},
|
|
188
|
+
display: {
|
|
189
|
+
family: "display",
|
|
190
|
+
size: "display",
|
|
191
|
+
weight: "bold",
|
|
192
|
+
headingLevel: 1
|
|
193
|
+
},
|
|
194
|
+
title: {
|
|
195
|
+
family: "display",
|
|
196
|
+
size: "xxl",
|
|
197
|
+
weight: "bold",
|
|
198
|
+
headingLevel: 2
|
|
199
|
+
},
|
|
200
|
+
heading: {
|
|
201
|
+
family: "sans",
|
|
202
|
+
size: "xl",
|
|
203
|
+
weight: "semibold",
|
|
204
|
+
headingLevel: 3
|
|
205
|
+
},
|
|
206
|
+
subheading: {
|
|
207
|
+
family: "sans",
|
|
208
|
+
size: "lg",
|
|
209
|
+
weight: "semibold",
|
|
210
|
+
headingLevel: 4
|
|
211
|
+
},
|
|
212
|
+
body: {
|
|
213
|
+
family: "sans",
|
|
214
|
+
size: "md",
|
|
215
|
+
weight: "regular"
|
|
216
|
+
},
|
|
217
|
+
bodySmall: {
|
|
218
|
+
family: "sans",
|
|
219
|
+
size: "sm",
|
|
220
|
+
weight: "regular"
|
|
221
|
+
},
|
|
222
|
+
label: {
|
|
223
|
+
family: "mono",
|
|
224
|
+
size: "xs",
|
|
225
|
+
weight: "medium",
|
|
226
|
+
uppercase: true,
|
|
227
|
+
letterSpacing: 1.2
|
|
228
|
+
},
|
|
229
|
+
caption: {
|
|
230
|
+
family: "sans",
|
|
231
|
+
size: "xs",
|
|
232
|
+
weight: "regular"
|
|
233
|
+
},
|
|
234
|
+
code: {
|
|
235
|
+
family: "mono",
|
|
236
|
+
size: "sm",
|
|
237
|
+
weight: "regular"
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region src/types/ui-provider.ts
|
|
242
|
+
/** Modo de tema que acepta `UIProvider` (ADR-010). */
|
|
243
|
+
const themeModes = [
|
|
244
|
+
"light",
|
|
245
|
+
"dark",
|
|
246
|
+
"system"
|
|
247
|
+
];
|
|
248
|
+
//#endregion
|
|
249
|
+
export { UIContext, backgroundTokens, badgeVariants, borderColorTokens, buttonIconSize, buttonVariants, createUIContextValue, iconSizePx, iconSizes, mergeTheme, radiusTokens, resolveColorScheme, resolveTheme, shadowTokens, spaceTokens, spinnerSizePx, spinnerSizes, stackAligns, stackDirections, stackJustifies, textAligns, textColors, textVariantStyles, textVariants, themeModes, useBrand, useButton, useColorScheme, useTheme, useUIContext };
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@satellatickets/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lógica headless de satella-ui: contratos de props, constantes de variantes, contexto de tema y hooks compartidos por React y React Native",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/RobertoRodriguezCarbonell/satella-ui.git",
|
|
9
|
+
"directory": "packages/core"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": ">=19.0"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@satellatickets/tokens": "0.1.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@testing-library/react": "^16.3.3",
|
|
33
|
+
"@types/node": "^22.0.0",
|
|
34
|
+
"@types/react": "19.2.18",
|
|
35
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
36
|
+
"eslint": "^10.12.0",
|
|
37
|
+
"jsdom": "^30.1.2",
|
|
38
|
+
"react": "19.2.3",
|
|
39
|
+
"react-dom": "19.2.3",
|
|
40
|
+
"tsdown": "^0.23.0",
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"vitest": "^5.0.3",
|
|
43
|
+
"@satellatickets/eslint-config": "0.0.0",
|
|
44
|
+
"@satellatickets/tsconfig": "0.0.0"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsdown",
|
|
48
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
49
|
+
"lint": "eslint . --max-warnings 0",
|
|
50
|
+
"test": "vitest run --coverage"
|
|
51
|
+
}
|
|
6
52
|
}
|