@reservi/theme 1.3.3 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reservi/theme",
3
- "version": "1.3.3",
3
+ "version": "1.4.0",
4
4
  "description": "Tema de Reservi Calendar: design tokens (TailwindCSS 4 @theme) y estilos base.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,12 +25,15 @@
25
25
  "*.css"
26
26
  ],
27
27
  "files": [
28
- "src"
28
+ "src",
29
+ "scripts"
29
30
  ],
30
31
  "main": "./src/index.css",
31
32
  "style": "./src/index.css",
32
33
  "exports": {
33
34
  ".": "./src/index.css",
35
+ "./plain": "./src/plain.css",
36
+ "./tokens": "./src/tokens.generated.css",
34
37
  "./theme.css": "./src/theme.css",
35
38
  "./components.css": "./src/components.css",
36
39
  "./template": "./src/theme-template.css",
@@ -49,10 +52,11 @@
49
52
  "./themes/graphite": "./src/themes/graphite.css",
50
53
  "./themes/solarized-light": "./src/themes/solarized-light.css",
51
54
  "./themes/solarized-dark": "./src/themes/solarized-dark.css",
52
- "./themes/contrast": "./src/themes/contrast.css"
55
+ "./themes/contrast": "./src/themes/contrast.css",
56
+ "./themes/reservi": "./src/themes/reservi.css"
53
57
  },
54
58
  "scripts": {
55
- "build": "node -e \"process.exit(0)\"",
59
+ "build": "node scripts/build-plain.mjs",
56
60
  "lint": "node -e \"process.exit(0)\"",
57
61
  "typecheck": "node -e \"process.exit(0)\""
58
62
  }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Genera la variante "plain CSS" del tema a partir de la fuente de TailwindCSS 4.
3
+ *
4
+ * `theme.css` declara los design tokens en un bloque `@theme`, que solo
5
+ * entiende Tailwind 4. Los estilos de `components.css` no usan utilidades de
6
+ * Tailwind: leen las variables con `var(--color-rsv-*, fallback)`. Por tanto,
7
+ * para un consumidor con Tailwind 3 (o sin Tailwind) basta con declarar los
8
+ * mismos tokens en `:root`.
9
+ *
10
+ * Este script hace exactamente esa transformación, de modo que NO haya dos
11
+ * listas de tokens que mantener sincronizadas a mano.
12
+ *
13
+ * Salida: `src/tokens.generated.css`, que importa `plain.css`.
14
+ */
15
+ import { readFile, writeFile } from "node:fs/promises";
16
+ import { fileURLToPath } from "node:url";
17
+ import { dirname, join } from "node:path";
18
+
19
+ const here = dirname(fileURLToPath(import.meta.url));
20
+ const src = join(here, "..", "src");
21
+
22
+ const HEADER = `/**
23
+ * ARCHIVO GENERADO — no editar a mano.
24
+ * Fuente: src/theme.css · Generador: scripts/build-plain.mjs
25
+ *
26
+ * Misma lista de design tokens que el bloque \`@theme\` de Tailwind 4, pero
27
+ * declarados en \`:root\` para consumidores con Tailwind 3 o sin Tailwind.
28
+ */
29
+ `;
30
+
31
+ const theme = await readFile(join(src, "theme.css"), "utf8");
32
+
33
+ // Se descarta el comentario de cabecera original (habla de Tailwind 4) antes de
34
+ // comprobar que no queda ningun `@theme`: el comentario lo menciona.
35
+ let out = theme.replace(/^\/\*\*[\s\S]*?\*\/\s*/, "");
36
+
37
+ // `@theme { … }` → `:root { … }`. El bloque puede venir anidado dentro de un
38
+ // `@media (prefers-color-scheme: dark)`, que se conserva tal cual.
39
+ out = out.replace(/@theme(\s+(?:static|inline|reference|default))?\s*\{/g, ":root {");
40
+
41
+ if (out.includes("@theme")) {
42
+ throw new Error("build-plain: queda algun @theme sin transformar en theme.css");
43
+ }
44
+ if (!out.includes(":root {")) {
45
+ throw new Error("build-plain: no se encontro ningun bloque @theme en theme.css");
46
+ }
47
+
48
+ await writeFile(join(src, "tokens.generated.css"), HEADER + "\n" + out, "utf8");
49
+ console.log("@reservi/theme: src/tokens.generated.css generado desde src/theme.css");
@@ -16,6 +16,25 @@
16
16
  flex-direction: column;
17
17
  }
18
18
 
19
+ /* El area de vista reparte el alto disponible del root (flex column), de modo
20
+ que el cuerpo de timeGrid pueda scrollear cuando el root tiene alto fijo.
21
+ Con `height: "auto"` el calendario crece con su contenido. */
22
+ [data-rsv="view"] {
23
+ display: flex;
24
+ flex-direction: column;
25
+ flex: 1 1 auto;
26
+ min-height: 0;
27
+ }
28
+ [data-rsv="root"][data-rsv-autoheight="true"] [data-rsv="view"] {
29
+ flex: 0 0 auto;
30
+ }
31
+
32
+ /* Hueco donde el framework anfitrion (React/Vue) pinta un render prop: no debe
33
+ introducir una caja propia que rompa el layout del evento o de la celda. */
34
+ [data-rsv="host"] {
35
+ display: contents;
36
+ }
37
+
19
38
  /* Toolbar */
20
39
  [data-rsv="toolbar-wrap"] {
21
40
  display: flex;
@@ -348,6 +367,12 @@
348
367
  outline: 2px solid var(--color-rsv-primary, #4f46e5);
349
368
  outline-offset: 1px;
350
369
  }
370
+ /* Selección múltiple (bulk ops): ctrl/cmd-click sobre un evento. */
371
+ [data-rsv="event"][data-rsv-selected="true"] {
372
+ outline: 2px solid var(--color-rsv-primary, #4f46e5);
373
+ outline-offset: 1px;
374
+ box-shadow: 0 0 0 3px var(--color-rsv-selection, #e8e8ff);
375
+ }
351
376
  [data-rsv="day-number"] {
352
377
  align-self: flex-end;
353
378
  font-size: var(--text-rsv-sm, 0.8125rem);
@@ -373,6 +398,7 @@
373
398
  border-radius: var(--radius-rsv-event, 0.375rem);
374
399
  background: var(--rsv-event-bg, var(--color-rsv-event-1));
375
400
  color: var(--rsv-event-fg, var(--color-rsv-event-fg, #fff));
401
+ border: 1px solid var(--rsv-event-border, transparent);
376
402
  cursor: pointer;
377
403
  overflow: hidden;
378
404
  white-space: nowrap;
@@ -475,6 +501,8 @@
475
501
  [data-rsv="timegrid"] {
476
502
  display: flex;
477
503
  flex-direction: column;
504
+ flex: 1 1 auto;
505
+ min-height: 0;
478
506
  --rsv-tg-axis-width: 3.5rem;
479
507
  }
480
508
  [data-rsv="tg-header"],
@@ -511,6 +539,11 @@
511
539
  display: grid;
512
540
  grid-template-columns: var(--rsv-tg-axis-width) repeat(var(--rsv-tg-days, 7), minmax(0, 1fr));
513
541
  overflow: auto;
542
+ /* Imprescindible para que un grid dentro de un flex column pueda encogerse
543
+ y por tanto scrollear en vez de desbordar al padre. */
544
+ flex: 1 1 auto;
545
+ min-height: 0;
546
+ align-content: start;
514
547
  }
515
548
  [data-rsv="tg-axis"] {
516
549
  display: flex;
@@ -524,13 +557,25 @@
524
557
  [data-rsv="tg-slot"][data-major="true"] {
525
558
  border-top: 1px solid var(--color-rsv-border, #eee);
526
559
  }
560
+ /**
561
+ * La etiqueta va centrada en su franja, no montada sobre la línea superior.
562
+ *
563
+ * Antes se subía con `top: -0.6em` y se le pintaba detrás un rectángulo opaco
564
+ * de `--color-rsv-bg` para tapar la línea que cruzaba el texto. Ese rectángulo
565
+ * asumía que el fondo real del eje era exactamente `--color-rsv-bg`, y se veía
566
+ * como una mancha en cuanto algo lo teñía (día actual, `surface`, franjas no
567
+ * laborables, o un token sobreescrito por la app).
568
+ *
569
+ * Centrada no cruza ningún borde, así que el rectángulo sobra y el problema
570
+ * desaparece en vez de taparse.
571
+ */
527
572
  [data-rsv="tg-slot-label"] {
528
573
  position: absolute;
529
- top: -0.6em;
574
+ top: 50%;
575
+ transform: translateY(-50%);
530
576
  right: 0.35rem;
531
577
  font-size: var(--text-rsv-xs, 0.75rem);
532
578
  color: var(--color-rsv-muted, #777);
533
- background: var(--color-rsv-bg, #fff);
534
579
  }
535
580
  [data-rsv="tg-col"] {
536
581
  position: relative;
@@ -554,7 +599,24 @@
554
599
  font-size: var(--text-rsv-xs, 0.75rem);
555
600
  overflow: hidden;
556
601
  box-sizing: border-box;
557
- border: 1px solid color-mix(in oklch, var(--rsv-event-bg) 80%, black);
602
+ /* `borderColor` del evento si lo hay; si no, un tono del propio fondo. */
603
+ border: 1px solid
604
+ var(--rsv-event-border, color-mix(in oklch, var(--rsv-event-bg) 80%, black));
605
+ align-items: stretch;
606
+ white-space: normal;
607
+ }
608
+
609
+ /* Fantasma del rango que se esta seleccionando (`selectMirror`). */
610
+ [data-rsv="select-mirror"] {
611
+ position: absolute;
612
+ left: 0;
613
+ right: 0;
614
+ background: var(--color-rsv-selection, #e8e8ff);
615
+ border: 1px dashed var(--color-rsv-primary, #4f46e5);
616
+ border-radius: var(--radius-rsv-event, 0.375rem);
617
+ opacity: 0.7;
618
+ pointer-events: none;
619
+ z-index: 4;
558
620
  }
559
621
  [data-rsv="tg-nonbusiness"] {
560
622
  position: absolute;
@@ -602,6 +664,10 @@
602
664
  flex-direction: column;
603
665
  }
604
666
  [data-rsv="list-day-header"] {
667
+ display: flex;
668
+ align-items: baseline;
669
+ justify-content: space-between;
670
+ gap: 0.5rem;
605
671
  position: sticky;
606
672
  top: 0;
607
673
  padding: 0.4rem 0.75rem;
@@ -615,6 +681,11 @@
615
681
  [data-rsv="list-day"][data-today="true"] [data-rsv="list-day-header"] {
616
682
  color: var(--color-rsv-primary, #4f46e5);
617
683
  }
684
+ [data-rsv="list-day-side-text"] {
685
+ font-weight: 400;
686
+ font-size: var(--text-rsv-xs, 0.75rem);
687
+ color: var(--color-rsv-muted, #777);
688
+ }
618
689
  [data-rsv="list-item"] {
619
690
  display: flex;
620
691
  align-items: center;
@@ -812,6 +883,55 @@
812
883
  cursor: pointer;
813
884
  }
814
885
 
886
+ /* Vista mensual con recursos como columnas anidadas por día. */
887
+ [data-rsv="resource-daygrid-month"] {
888
+ display: flex;
889
+ flex-direction: column;
890
+ }
891
+ [data-rsv="rdgm-week"] {
892
+ display: grid;
893
+ grid-template-columns: repeat(var(--rsv-rdgm-cols, 7), minmax(0, 1fr));
894
+ }
895
+ [data-rsv="rdgm-week"] [data-rsv="day"]:last-child {
896
+ border-right: none;
897
+ }
898
+ [data-rsv="rdgm-columns"] {
899
+ display: flex;
900
+ flex-direction: column;
901
+ gap: 0.2rem;
902
+ overflow: hidden;
903
+ }
904
+ [data-rsv="rdgm-col"] {
905
+ display: flex;
906
+ flex-direction: column;
907
+ gap: 0.1rem;
908
+ border-inline-start: 2px solid var(--color-rsv-border, #eee);
909
+ padding-inline-start: 0.2rem;
910
+ }
911
+ [data-rsv="rdgm-col-label"] {
912
+ font-size: 0.65rem;
913
+ font-weight: 600;
914
+ color: var(--color-rsv-muted, #777);
915
+ text-transform: uppercase;
916
+ letter-spacing: 0.02em;
917
+ }
918
+ [data-rsv="rdgm-col"] [data-rsv="event"] {
919
+ background: var(--rsv-event-bg, var(--color-rsv-event-1));
920
+ color: var(--rsv-event-fg, #fff);
921
+ border-radius: var(--radius-rsv-event, 0.375rem);
922
+ padding: 0.05rem 0.3rem;
923
+ font-size: var(--text-rsv-xs, 0.75rem);
924
+ cursor: pointer;
925
+ overflow: hidden;
926
+ white-space: nowrap;
927
+ text-overflow: ellipsis;
928
+ }
929
+ [data-rsv="rdgm-more"] {
930
+ font-size: 0.65rem;
931
+ color: var(--color-rsv-muted, #777);
932
+ cursor: pointer;
933
+ }
934
+
815
935
  /* Eventos de fondo (display: background) */
816
936
  [data-rsv="event"][data-display="background"] {
817
937
  opacity: 0.25;
package/src/plain.css ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Punto de entrada "plain CSS" de Reservi Calendar: sin Tailwind.
3
+ *
4
+ * Uso (Tailwind 3, CSS Modules, PostCSS o un <link> suelto):
5
+ * @import "@reservi/theme/plain";
6
+ *
7
+ * Equivale a `@reservi/theme` pero declara los design tokens en `:root` en
8
+ * lugar de en un bloque `@theme` de TailwindCSS 4. Los estilos de componentes
9
+ * son los mismos: leen los tokens con `var(--color-rsv-*, fallback)`.
10
+ */
11
+ @import "./tokens.generated.css";
12
+ @import "./components.css";
@@ -18,3 +18,4 @@
18
18
  @import "./solarized-light.css";
19
19
  @import "./solarized-dark.css";
20
20
  @import "./contrast.css";
21
+ @import "./reservi.css";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Theme "reservi" — claro, paleta de marca de Reservi.
3
+ *
4
+ * Los valores son los mismos hex de `tailwind.config.js` de reservi-frontend-v2.
5
+ * Se usa hex y no `oklch()` como el resto de themes a propósito: son colores de
6
+ * marca y la conversión introduce desviación; aquí pesa más la fidelidad.
7
+ *
8
+ * `--font-rsv-sans` NO se declara aquí: la fuente de la app (Urbanist) llega
9
+ * como variable de Next, así que la aporta el frontend vía `themeTokens`.
10
+ */
11
+ [data-rsv-theme="reservi"] {
12
+ /* Superficies */
13
+ --color-rsv-bg: #fefefe;
14
+ --color-rsv-surface: #faf5fc;
15
+ --color-rsv-border: #efefef;
16
+ --color-rsv-muted: #5e636b;
17
+ --color-rsv-text: #1a1a1a;
18
+
19
+ /* Marca / acento */
20
+ --color-rsv-primary: #865e9b;
21
+ --color-rsv-primary-fg: #fefefe;
22
+ /* Translúcido a propósito: tiñe la columna del día actual sin tapar las
23
+ líneas de franja que se pintan debajo. */
24
+ --color-rsv-today: rgba(155, 135, 245, 0.1);
25
+
26
+ /* Eventos — mapeados a los estados de cita (`border-status-*`). */
27
+ --color-rsv-event-1: #865e9b; /* pendiente */
28
+ --color-rsv-event-2: #78b157; /* confirmada */
29
+ --color-rsv-event-3: #e1d25e; /* no-show */
30
+ --color-rsv-event-4: #d85353; /* cancelada */
31
+ --color-rsv-event-5: #bbbec2; /* completada */
32
+ /* Texto oscuro: la paleta de estados va de claro (#e1d25e, #bbbec2) a medio,
33
+ y un fg blanco sería ilegible sobre los dos claros. */
34
+ --color-rsv-event-fg: #141517;
35
+
36
+ /* Estados */
37
+ --color-rsv-now: #d85353;
38
+ --color-rsv-selection: #f6f0fa;
39
+ --color-rsv-nonbusiness: rgba(250, 250, 255, 0.5);
40
+
41
+ /* Geometría */
42
+ --radius-rsv: 8px;
43
+ --radius-rsv-event: 4px;
44
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * ARCHIVO GENERADO — no editar a mano.
3
+ * Fuente: src/theme.css · Generador: scripts/build-plain.mjs
4
+ *
5
+ * Misma lista de design tokens que el bloque `@theme` de Tailwind 4, pero
6
+ * declarados en `:root` para consumidores con Tailwind 3 o sin Tailwind.
7
+ */
8
+
9
+ :root {
10
+ /* Superficies */
11
+ --color-rsv-bg: oklch(1 0 0);
12
+ --color-rsv-surface: oklch(0.98 0 0);
13
+ --color-rsv-border: oklch(0.9 0 0);
14
+ --color-rsv-muted: oklch(0.55 0 0);
15
+ --color-rsv-text: oklch(0.2 0 0);
16
+
17
+ /* Marca / acento */
18
+ --color-rsv-primary: oklch(0.55 0.2 264);
19
+ --color-rsv-primary-fg: oklch(0.99 0 0);
20
+ --color-rsv-today: oklch(0.97 0.04 264);
21
+
22
+ /* Paleta categórica de eventos */
23
+ --color-rsv-event-1: oklch(0.6 0.18 264);
24
+ --color-rsv-event-2: oklch(0.65 0.17 150);
25
+ --color-rsv-event-3: oklch(0.7 0.17 60);
26
+ --color-rsv-event-4: oklch(0.62 0.2 20);
27
+ --color-rsv-event-5: oklch(0.6 0.16 320);
28
+ --color-rsv-event-fg: oklch(0.99 0 0);
29
+
30
+ /* Estados */
31
+ --color-rsv-now: oklch(0.6 0.22 20);
32
+ --color-rsv-selection: oklch(0.9 0.05 264);
33
+ --color-rsv-nonbusiness: oklch(0.96 0 0);
34
+
35
+ /* Tipografía */
36
+ --font-rsv-sans: ui-sans-serif, system-ui, sans-serif;
37
+ --text-rsv-xs: 0.75rem;
38
+ --text-rsv-sm: 0.8125rem;
39
+ --text-rsv-base: 0.875rem;
40
+
41
+ /* Geometría */
42
+ --radius-rsv: 0.5rem;
43
+ --radius-rsv-event: 0.375rem;
44
+ --spacing-rsv-slot: 2.5rem;
45
+ --spacing-rsv-gutter: 0.25rem;
46
+
47
+ /* Sombras y transición */
48
+ --shadow-rsv-pop: 0 4px 20px -4px oklch(0 0 0 / 0.2);
49
+ --ease-rsv: cubic-bezier(0.2, 0, 0, 1);
50
+ }
51
+
52
+ /* Modo oscuro automático */
53
+ @media (prefers-color-scheme: dark) {
54
+ :root {
55
+ --color-rsv-bg: oklch(0.18 0.01 264);
56
+ --color-rsv-surface: oklch(0.22 0.01 264);
57
+ --color-rsv-border: oklch(0.3 0.01 264);
58
+ --color-rsv-text: oklch(0.95 0 0);
59
+ --color-rsv-muted: oklch(0.6 0 0);
60
+ --color-rsv-today: oklch(0.3 0.05 264);
61
+ --color-rsv-nonbusiness: oklch(0.2 0.01 264);
62
+ }
63
+ }
64
+
65
+ /* Modo oscuro por clase (.dark) para toggle manual */
66
+ .dark {
67
+ --color-rsv-bg: oklch(0.18 0.01 264);
68
+ --color-rsv-surface: oklch(0.22 0.01 264);
69
+ --color-rsv-border: oklch(0.3 0.01 264);
70
+ --color-rsv-text: oklch(0.95 0 0);
71
+ --color-rsv-muted: oklch(0.6 0 0);
72
+ --color-rsv-today: oklch(0.3 0.05 264);
73
+ --color-rsv-nonbusiness: oklch(0.2 0.01 264);
74
+ }