@axzydev/axzy_ui_system 1.4.7 → 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 (226) hide show
  1. package/README.md +2 -11
  2. package/dist/index.cjs +2174 -1570
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.css +1 -1
  5. package/dist/index.css.map +1 -1
  6. package/dist/index.d.cts +232 -19
  7. package/dist/index.d.ts +232 -19
  8. package/dist/index.js +1838 -1238
  9. package/dist/index.js.map +1 -1
  10. package/dist/layered.css +1 -1
  11. package/package.json +3 -16
  12. package/src/App.tsx +90 -73
  13. package/src/components/atoms/badget/badget.tsx +23 -5
  14. package/src/components/atoms/chip/chip.tsx +3 -1
  15. package/src/components/atoms/image/image.tsx +3 -1
  16. package/src/components/atoms/input/input.tsx +8 -5
  17. package/src/components/atoms/loader/loader.tsx +3 -1
  18. package/src/components/atoms/slide/slide.props.ts +12 -0
  19. package/src/components/atoms/slide/slide.tsx +3 -0
  20. package/src/components/molecules/alert/alert.tsx +3 -1
  21. package/src/components/molecules/breadcrumbs/breadcrumbs.tsx +3 -1
  22. package/src/components/molecules/calendar/calendar.tsx +9 -6
  23. package/src/components/molecules/card/card.tsx +5 -2
  24. package/src/components/molecules/chip-input/chip-input.tsx +9 -7
  25. package/src/components/molecules/date-picker/datePicker.tsx +9 -9
  26. package/src/components/molecules/dropdown-menu/dropdown-menu.props.ts +1 -1
  27. package/src/components/molecules/dropdown-menu/dropdown-menu.tsx +6 -3
  28. package/src/components/molecules/field/field.tsx +3 -1
  29. package/src/components/molecules/form-header/form-header.tsx +3 -1
  30. package/src/components/molecules/line-bar-chart/line-bar-chart.props.ts +2 -2
  31. package/src/components/molecules/line-bar-chart/line-bar-chart.tsx +20 -15
  32. package/src/components/molecules/masked-input/masked-input.tsx +3 -1
  33. package/src/components/molecules/multi-select/multi-select.tsx +12 -9
  34. package/src/components/molecules/pagination/pagination.tsx +11 -5
  35. package/src/components/molecules/search-select/search-select.tsx +11 -9
  36. package/src/components/molecules/select/select.tsx +6 -4
  37. package/src/components/molecules/stepper/stepper.tsx +6 -4
  38. package/src/components/molecules/table/table.props.ts +10 -2
  39. package/src/components/molecules/table/table.tsx +163 -114
  40. package/src/components/molecules/time-picker/timePicker.tsx +8 -6
  41. package/src/components/molecules/wysiwyg/wysiwyg.tsx +23 -12
  42. package/src/components/organisms/confirm-dialog/confirm-dialog.props.ts +4 -4
  43. package/src/components/organisms/confirm-dialog/confirm-dialog.tsx +16 -8
  44. package/src/components/organisms/data-table/dataTable.props.ts +3 -2
  45. package/src/components/organisms/data-table/dataTable.tsx +149 -81
  46. package/src/components/organisms/dialog/dialog.tsx +3 -0
  47. package/src/components/organisms/drawer/drawer.tsx +3 -0
  48. package/src/components/organisms/dropfile/dropfile.tsx +19 -17
  49. package/src/components/organisms/form-builder/fieldRenderer.tsx +4 -2
  50. package/src/components/organisms/navbar/navbar.tsx +4 -2
  51. package/src/components/organisms/page-header/page-header.tsx +3 -1
  52. package/src/components/organisms/searchTable/components/PaginationControls.tsx +8 -6
  53. package/src/components/organisms/searchTable/components/PaginationInfo.tsx +3 -1
  54. package/src/components/organisms/searchTable/components/SearchAndSortBar.tsx +7 -5
  55. package/src/components/organisms/searchTable/components/SearchInput.tsx +4 -2
  56. package/src/components/organisms/searchTable/components/SortButton.tsx +13 -11
  57. package/src/components/organisms/searchTable/components/TableEmptyState.tsx +4 -2
  58. package/src/components/organisms/searchTable/components/TableRow.tsx +6 -4
  59. package/src/components/organisms/searchTable/searchTable.tsx +4 -2
  60. package/src/components/organisms/sidebar/sidebar.props.ts +1 -1
  61. package/src/components/organisms/sidebar/sidebar.tsx +9 -4
  62. package/src/components/organisms/toast/toast.tsx +3 -1
  63. package/src/components/organisms/topbar/topbar.tsx +4 -2
  64. package/src/components/templates/page/page.props.ts +4 -4
  65. package/src/components/templates/page/page.tsx +6 -4
  66. package/src/components/theme-provider/themeProvider.tsx +28 -17
  67. package/src/dev.css +77 -2
  68. package/src/global.d.ts +10 -0
  69. package/src/i18n/context.ts +9 -0
  70. package/src/i18n/index.ts +22 -0
  71. package/src/i18n/locales/en.ts +163 -0
  72. package/src/i18n/locales/es.ts +165 -0
  73. package/src/i18n/provider.tsx +44 -0
  74. package/src/i18n/translator.ts +82 -0
  75. package/src/i18n/types.ts +54 -0
  76. package/src/i18n/useITTranslation.ts +20 -0
  77. package/src/index.css +0 -1
  78. package/src/index.ts +9 -3
  79. package/src/sandbox/LandingShowcase.tsx +224 -580
  80. package/src/sandbox/NotFoundShowcase.tsx +51 -36
  81. package/src/sandbox/TopbarSearch.tsx +6 -4
  82. package/src/sandbox/controls.tsx +180 -0
  83. package/src/sandbox/i18n.ts +1929 -0
  84. package/src/sandbox/icons.tsx +25 -0
  85. package/src/sandbox/locale.tsx +72 -0
  86. package/src/sandbox/ui.tsx +377 -0
  87. package/src/sandbox/useHashRoute.ts +46 -10
  88. package/src/showcases/DataShowcases.tsx +201 -80
  89. package/src/showcases/FeedbackShowcases.tsx +37 -30
  90. package/src/showcases/FormShowcases.tsx +302 -214
  91. package/src/showcases/GettingStartedShowcase.tsx +151 -145
  92. package/src/showcases/HelpShowcase.tsx +126 -128
  93. package/src/showcases/HomeShowcase.tsx +678 -482
  94. package/src/showcases/LayoutPrimitivesShowcases.tsx +129 -114
  95. package/src/showcases/NavigationShowcases.tsx +45 -37
  96. package/src/showcases/PageShowcases.tsx +171 -152
  97. package/src/showcases/ProductoDetalle.tsx +272 -129
  98. package/src/showcases/ShowcaseLayout.tsx +270 -216
  99. package/src/showcases/SizesShowcase.tsx +123 -64
  100. package/src/showcases/StructureShowcases.tsx +157 -131
  101. package/src/showcases/portfolioUi.tsx +153 -0
  102. package/src/showcases/techIcons.ts +3 -0
  103. package/src/types/table.types.ts +110 -0
  104. package/src/utils/table.utils.ts +10 -2
  105. package/src/components/atoms/avatar/avatar.doc.mdx +0 -48
  106. package/src/components/atoms/avatar/avatar.stories.tsx +0 -64
  107. package/src/components/atoms/badget/badget.doc.mdx +0 -46
  108. package/src/components/atoms/badget/badget.stories.tsx +0 -75
  109. package/src/components/atoms/button/button.doc.mdx +0 -59
  110. package/src/components/atoms/button/button.stories.tsx +0 -173
  111. package/src/components/atoms/checkbox/checkbox.doc.mdx +0 -54
  112. package/src/components/atoms/checkbox/checkbox.stories.tsx +0 -53
  113. package/src/components/atoms/chip/chip.doc.mdx +0 -47
  114. package/src/components/atoms/chip/chip.stories.tsx +0 -64
  115. package/src/components/atoms/divider/divider.doc.mdx +0 -42
  116. package/src/components/atoms/divider/divider.stories.tsx +0 -33
  117. package/src/components/atoms/flex/flex.doc.mdx +0 -59
  118. package/src/components/atoms/flex/flex.stories.tsx +0 -70
  119. package/src/components/atoms/grid/grid.doc.mdx +0 -54
  120. package/src/components/atoms/grid/grid.stories.tsx +0 -71
  121. package/src/components/atoms/image/image.doc.mdx +0 -44
  122. package/src/components/atoms/image/image.stories.tsx +0 -48
  123. package/src/components/atoms/input/input.doc.mdx +0 -70
  124. package/src/components/atoms/input/input.stories.tsx +0 -113
  125. package/src/components/atoms/input-number/input-number.doc.mdx +0 -74
  126. package/src/components/atoms/input-number/input-number.stories.tsx +0 -111
  127. package/src/components/atoms/loader/loader.doc.mdx +0 -43
  128. package/src/components/atoms/loader/loader.stories.tsx +0 -104
  129. package/src/components/atoms/popover/popover.doc.mdx +0 -53
  130. package/src/components/atoms/popover/popover.stories.tsx +0 -24
  131. package/src/components/atoms/progress/progress.doc.mdx +0 -48
  132. package/src/components/atoms/progress/progress.stories.tsx +0 -39
  133. package/src/components/atoms/radio/radio.doc.mdx +0 -62
  134. package/src/components/atoms/radio/radio.stories.tsx +0 -48
  135. package/src/components/atoms/segmented-control/segmented-control.doc.mdx +0 -60
  136. package/src/components/atoms/segmented-control/segmented-control.stories.tsx +0 -62
  137. package/src/components/atoms/skeleton/skeleton.doc.mdx +0 -43
  138. package/src/components/atoms/skeleton/skeleton.stories.tsx +0 -29
  139. package/src/components/atoms/slide/slide.doc.mdx +0 -46
  140. package/src/components/atoms/slide/slide.stories.tsx +0 -120
  141. package/src/components/atoms/slider/slider.doc.mdx +0 -49
  142. package/src/components/atoms/slider/slider.stories.tsx +0 -29
  143. package/src/components/atoms/stack/stack.doc.mdx +0 -126
  144. package/src/components/atoms/stack/stack.stories.tsx +0 -283
  145. package/src/components/atoms/text/text.doc.mdx +0 -41
  146. package/src/components/atoms/text/text.stories.tsx +0 -66
  147. package/src/components/atoms/textarea/textarea.doc.mdx +0 -64
  148. package/src/components/atoms/textarea/textarea.stories.tsx +0 -26
  149. package/src/components/atoms/tooltip/tooltip.doc.mdx +0 -47
  150. package/src/components/atoms/tooltip/tooltip.stories.tsx +0 -19
  151. package/src/components/isolation/isolation.stories.tsx +0 -202
  152. package/src/components/molecules/accordion/accordion.doc.mdx +0 -47
  153. package/src/components/molecules/accordion/accordion.stories.tsx +0 -68
  154. package/src/components/molecules/alert/alert.doc.mdx +0 -48
  155. package/src/components/molecules/alert/alert.stories.tsx +0 -40
  156. package/src/components/molecules/breadcrumbs/breadcrumbs.doc.mdx +0 -57
  157. package/src/components/molecules/breadcrumbs/breadcrumbs.stories.tsx +0 -20
  158. package/src/components/molecules/calendar/calendar.doc.mdx +0 -76
  159. package/src/components/molecules/calendar/calendar.stories.tsx +0 -90
  160. package/src/components/molecules/card/card.doc.mdx +0 -54
  161. package/src/components/molecules/card/card.stories.tsx +0 -57
  162. package/src/components/molecules/chip-input/chip-input.doc.mdx +0 -44
  163. package/src/components/molecules/chip-input/chip-input.stories.tsx +0 -52
  164. package/src/components/molecules/date-picker/date-picker.doc.mdx +0 -65
  165. package/src/components/molecules/date-picker/date-picker.stories.tsx +0 -88
  166. package/src/components/molecules/dropdown-menu/dropdown-menu.doc.mdx +0 -46
  167. package/src/components/molecules/dropdown-menu/dropdown-menu.stories.tsx +0 -41
  168. package/src/components/molecules/empty-state/empty-state.doc.mdx +0 -50
  169. package/src/components/molecules/empty-state/empty-state.stories.tsx +0 -19
  170. package/src/components/molecules/field/field.doc.mdx +0 -43
  171. package/src/components/molecules/field/field.stories.tsx +0 -42
  172. package/src/components/molecules/form-header/form-header.doc.mdx +0 -38
  173. package/src/components/molecules/form-header/form-header.stories.tsx +0 -52
  174. package/src/components/molecules/masked-input/masked-input.doc.mdx +0 -78
  175. package/src/components/molecules/masked-input/masked-input.stories.tsx +0 -126
  176. package/src/components/molecules/multi-select/multi-select.doc.mdx +0 -55
  177. package/src/components/molecules/multi-select/multi-select.stories.tsx +0 -59
  178. package/src/components/molecules/pagination/pagination.doc.mdx +0 -62
  179. package/src/components/molecules/pagination/pagination.stories.tsx +0 -110
  180. package/src/components/molecules/search-select/search-select.doc.mdx +0 -77
  181. package/src/components/molecules/search-select/search-select.stories.tsx +0 -146
  182. package/src/components/molecules/select/select.doc.mdx +0 -75
  183. package/src/components/molecules/select/select.stories.tsx +0 -85
  184. package/src/components/molecules/stat-card/stat-card.doc.mdx +0 -48
  185. package/src/components/molecules/stat-card/stat-card.stories.tsx +0 -40
  186. package/src/components/molecules/stepper/stepper.doc.mdx +0 -54
  187. package/src/components/molecules/stepper/stepper.stories.tsx +0 -154
  188. package/src/components/molecules/table/table.doc.mdx +0 -123
  189. package/src/components/molecules/table/table.stories.tsx +0 -402
  190. package/src/components/molecules/tabs/tabs.doc.mdx +0 -52
  191. package/src/components/molecules/tabs/tabs.stories.tsx +0 -31
  192. package/src/components/molecules/time-picker/timePicker.doc.mdx +0 -68
  193. package/src/components/molecules/time-picker/timePicker.stories.tsx +0 -130
  194. package/src/components/molecules/wysiwyg/wysiwyg.stories.tsx +0 -95
  195. package/src/components/organisms/confirm-dialog/confirm-dialog.doc.mdx +0 -60
  196. package/src/components/organisms/confirm-dialog/confirm-dialog.stories.tsx +0 -32
  197. package/src/components/organisms/data-table/ITDataTable.doc.mdx +0 -115
  198. package/src/components/organisms/data-table/ITDataTable.stories.tsx +0 -353
  199. package/src/components/organisms/dialog/dialog.doc.mdx +0 -54
  200. package/src/components/organisms/dialog/dialog.stories.tsx +0 -126
  201. package/src/components/organisms/drawer/drawer.doc.mdx +0 -59
  202. package/src/components/organisms/drawer/drawer.stories.tsx +0 -40
  203. package/src/components/organisms/dropfile/dropfile.doc.mdx +0 -74
  204. package/src/components/organisms/dropfile/dropfile.stories.tsx +0 -93
  205. package/src/components/organisms/form-builder/formBuilder.doc.mdx +0 -251
  206. package/src/components/organisms/form-builder/formBuilder.stories.tsx +0 -914
  207. package/src/components/organisms/navbar/navbar.doc.mdx +0 -77
  208. package/src/components/organisms/navbar/navbar.stories.tsx +0 -83
  209. package/src/components/organisms/page-header/page-header.doc.mdx +0 -54
  210. package/src/components/organisms/page-header/page-header.stories.tsx +0 -74
  211. package/src/components/organisms/searchTable/searchTable.doc.mdx +0 -79
  212. package/src/components/organisms/searchTable/searchTable.stories.tsx +0 -73
  213. package/src/components/organisms/sidebar/sidebar.doc.mdx +0 -176
  214. package/src/components/organisms/sidebar/sidebar.stories.tsx +0 -234
  215. package/src/components/organisms/toast/toast.doc.mdx +0 -50
  216. package/src/components/organisms/toast/toast.stories.tsx +0 -137
  217. package/src/components/organisms/topbar/topbar.doc.mdx +0 -72
  218. package/src/components/organisms/topbar/topbar.stories.tsx +0 -79
  219. package/src/components/organisms/triple-filter/tripleFilter.doc.mdx +0 -62
  220. package/src/components/organisms/triple-filter/tripleFilter.stories.tsx +0 -31
  221. package/src/components/templates/layout/layout.doc.mdx +0 -53
  222. package/src/components/templates/layout/layout.stories.tsx +0 -113
  223. package/src/components/templates/page/page.doc.mdx +0 -86
  224. package/src/components/templates/page/page.stories.tsx +0 -97
  225. package/src/components/theme-provider/themeProvider.doc.mdx +0 -85
  226. package/src/components/theme-provider/themeProvider.stories.tsx +0 -58
@@ -3,6 +3,7 @@ import ITButton from "@/components/atoms/button/button";
3
3
  import ITFlex from "@/components/atoms/flex/flex";
4
4
  import ITStack from "@/components/atoms/stack/stack";
5
5
  import ITText from "@/components/atoms/text/text";
6
+ import { useITTranslation } from "@/i18n";
6
7
 
7
8
  /** Props for the sandbox 404 page. */
8
9
  export interface NotFoundShowcaseProps {
@@ -18,53 +19,67 @@ export interface NotFoundShowcaseProps {
18
19
  * Sandbox 404 page rendered when a hash route does not resolve to a page.
19
20
  *
20
21
  * Shows the attempted path, a friendly message, and buttons to recover.
22
+ * Follows the blueprint spec: flat surface, 1px border, mono metadata.
21
23
  */
22
24
  export default function NotFoundShowcase({
23
25
  attempted,
24
26
  onGoLanding,
25
27
  onGoPortfolio,
26
28
  }: NotFoundShowcaseProps) {
29
+ const { t } = useITTranslation();
27
30
  return (
28
31
  <div className="animate-fadeIn flex min-h-[60vh] items-center justify-center px-4">
29
- <ITStack spacing={5} className="w-full max-w-xl text-center items-center">
30
- <ITText
31
- as="div"
32
- className="!text-7xl !font-extrabold !tracking-tight bg-gradient-to-br from-primary-500 via-primary-600 to-purple-600 bg-clip-text text-transparent"
33
- >
34
- 404
35
- </ITText>
36
-
37
- <ITStack spacing={2} className="items-center">
38
- <ITText as="h1" className="!text-2xl !font-bold text-slate-900 dark:text-white">
39
- Ruta no encontrada
40
- </ITText>
41
- <ITText as="p" className="!text-sm text-slate-500 dark:text-slate-400">
42
- No existe una página para este hash. Revisa el enlace o vuelve al inicio.
32
+ <div className="it-reveal w-full max-w-xl rounded-2xl border border-slate-200 bg-white p-8 text-center dark:border-slate-800 dark:bg-slate-900 sm:p-10">
33
+ <ITStack spacing={5} className="items-center">
34
+ <ITText
35
+ as="p"
36
+ className="font-mono !text-[11px] !font-medium uppercase !tracking-[0.18em] text-primary-600 dark:text-primary-400"
37
+ >
38
+ {t("showcase.notFound.eyebrow")}
43
39
  </ITText>
44
- <code className="mt-1 max-w-full truncate rounded-lg border border-slate-200 bg-slate-50 px-3 py-1.5 font-mono text-xs text-slate-600 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300">
45
- {attempted || "#"}
46
- </code>
47
- </ITStack>
48
40
 
49
- <ITFlex gap={3} wrap="wrap" justify="center" align="center">
50
- <ITButton
51
- variant="filled"
52
- color="primary"
53
- icon={<FaHome size={13} />}
54
- onClick={onGoLanding}
55
- >
56
- Volver al inicio
57
- </ITButton>
58
- <ITButton
59
- variant="outlined"
60
- color="secondary"
61
- icon={<FaLayerGroup size={13} />}
62
- onClick={onGoPortfolio}
41
+ <ITText
42
+ as="div"
43
+ className="font-display !text-6xl !font-bold -tracking-[0.02em] tabular-nums text-slate-900 dark:text-white"
63
44
  >
64
- Ver portafolio
65
- </ITButton>
66
- </ITFlex>
67
- </ITStack>
45
+ 404
46
+ </ITText>
47
+
48
+ <ITStack spacing={2} className="items-center">
49
+ <ITText
50
+ as="h1"
51
+ className="font-display !text-2xl !font-bold -tracking-[0.02em] text-slate-900 dark:text-white"
52
+ >
53
+ {t("showcase.notFound.title")}
54
+ </ITText>
55
+ <ITText as="p" className="!text-sm !leading-relaxed text-slate-500 dark:text-slate-400">
56
+ {t("showcase.notFound.description")}
57
+ </ITText>
58
+ <code className="mt-1 max-w-full truncate rounded-[10px] border border-slate-200 bg-slate-50 px-3 py-1.5 font-mono text-xs text-slate-600 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-300">
59
+ {attempted || "#"}
60
+ </code>
61
+ </ITStack>
62
+
63
+ <ITFlex gap={3} wrap="wrap" justify="center" align="center">
64
+ <ITButton
65
+ variant="filled"
66
+ color="primary"
67
+ icon={<FaHome size={13} />}
68
+ onClick={onGoLanding}
69
+ >
70
+ {t("showcase.notFound.backHome")}
71
+ </ITButton>
72
+ <ITButton
73
+ variant="outlined"
74
+ color="secondary"
75
+ icon={<FaLayerGroup size={13} />}
76
+ onClick={onGoPortfolio}
77
+ >
78
+ {t("showcase.landing.viewPortfolio")}
79
+ </ITButton>
80
+ </ITFlex>
81
+ </ITStack>
82
+ </div>
68
83
  </div>
69
84
  );
70
85
  }
@@ -2,6 +2,7 @@ import type { ChangeEvent } from "react";
2
2
  import { FaSearch, FaTimes } from "react-icons/fa";
3
3
  import ITButton from "@/components/atoms/button/button";
4
4
  import ITInput from "@/components/atoms/input/input";
5
+ import { useITTranslation } from "@/i18n";
5
6
 
6
7
  /** Props for the sandbox topbar search filter. */
7
8
  export interface TopbarSearchProps {
@@ -37,6 +38,7 @@ export default function TopbarSearch({
37
38
  pending = false,
38
39
  className = "w-full",
39
40
  }: TopbarSearchProps) {
41
+ const { t } = useITTranslation();
40
42
  const isActive = value.trim().length > 0;
41
43
  const showCount = isActive && !pending;
42
44
 
@@ -50,9 +52,9 @@ export default function TopbarSearch({
50
52
  <div className="flex-1 min-w-0">
51
53
  <ITInput
52
54
  name="sandbox-topbar-search"
53
- label="Buscar componentes"
55
+ label={t("sandbox.search.label")}
54
56
  labelClassName="sr-only"
55
- placeholder="Buscar componente…"
57
+ placeholder={t("sandbox.search.placeholder")}
56
58
  value={value}
57
59
  onChange={(event: ChangeEvent<HTMLInputElement>) =>
58
60
  onChange(event.target.value)
@@ -68,7 +70,7 @@ export default function TopbarSearch({
68
70
  variant="text"
69
71
  color="danger"
70
72
  size="sm"
71
- ariaLabel="Limpiar búsqueda"
73
+ ariaLabel={t("sandbox.search.clear")}
72
74
  onClick={onClear}
73
75
  className="flex-shrink-0"
74
76
  >
@@ -78,7 +80,7 @@ export default function TopbarSearch({
78
80
  </div>
79
81
 
80
82
  <span role="status" aria-live="polite" className="sr-only">
81
- {showCount ? `${resultCount} resultados` : ""}
83
+ {showCount ? t("sandbox.search.results", { count: resultCount }) : ""}
82
84
  </span>
83
85
  </form>
84
86
  );
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Controles de chrome del sandbox: idioma y modo de color.
3
+ *
4
+ * Viven aquí (y no en `showcases/`) porque los usan las dos superficies: la
5
+ * barra del sandbox (`ITTopBar`, en `App.tsx`) y la cabecera del portafolio
6
+ * (`HomeShowcase` / `ProductoDetalle`).
7
+ *
8
+ * No se usa `ITSegmentedControl` de la librería: su opción inactiva queda en
9
+ * 4.34:1 sobre `slate-100` y falla AA con la paleta por defecto. Estas opciones
10
+ * van en `slate-600`/`slate-300` (≥ 7:1) y el activo en `primary-700` con texto
11
+ * blanco (5:1).
12
+ */
13
+
14
+ import type { ReactNode } from "react";
15
+ import { FaDesktop, FaMoon, FaSun } from "react-icons/fa";
16
+ import { useITTranslation } from "@/i18n";
17
+ import { useITThemeSafe } from "@/theme/theme-context";
18
+ import { useSandboxLocale } from "./locale";
19
+
20
+ /** Una opción del control segmentado. */
21
+ export interface OpcionSegmento {
22
+ /** Valor que emite al pulsarla. */
23
+ value: string;
24
+ /** Texto visible. También es el nombre accesible cuando la opción es solo ícono. */
25
+ label: string;
26
+ /** Ícono opcional; cuando existe, la opción se pinta cuadrada y solo con ícono. */
27
+ icono?: ReactNode;
28
+ }
29
+
30
+ /** Props de `ControlSegmentado`. */
31
+ export interface ControlSegmentadoProps {
32
+ /** Opciones mutuamente excluyentes. */
33
+ opciones: OpcionSegmento[];
34
+ /** Valor activo. */
35
+ valor: string;
36
+ /** Se dispara con el valor elegido. */
37
+ onChange: (value: string) => void;
38
+ /** Nombre accesible del grupo. */
39
+ etiqueta: string;
40
+ /** Clases extra del contenedor. */
41
+ className?: string;
42
+ /**
43
+ * Pinta el texto junto al ícono (en vez de solo el ícono) y reparte el ancho
44
+ * entre las opciones. Para barras de pestañas; en controles compactos
45
+ * (cabecera, tema) se deja en `false`.
46
+ * @default false
47
+ */
48
+ conEtiqueta?: boolean;
49
+ }
50
+
51
+ /**
52
+ * Grupo de opciones excluyentes en la lengua visual del proyecto: borde de 1px,
53
+ * mono en mayúsculas y activo relleno del acento.
54
+ *
55
+ * @example
56
+ * ```tsx
57
+ * <ControlSegmentado
58
+ * etiqueta={t("sandbox.language")}
59
+ * valor={locale}
60
+ * onChange={setLocale}
61
+ * opciones={[{ value: "es", label: "ES" }, { value: "en", label: "EN" }]}
62
+ * />
63
+ * ```
64
+ */
65
+ export const ControlSegmentado = ({
66
+ opciones,
67
+ valor,
68
+ onChange,
69
+ etiqueta,
70
+ className = "",
71
+ conEtiqueta = false,
72
+ }: ControlSegmentadoProps) => (
73
+ <div
74
+ role="group"
75
+ aria-label={etiqueta}
76
+ className={`inline-flex shrink-0 items-center gap-0.5 rounded-[10px] border border-slate-200 bg-white p-0.5 dark:border-slate-800 dark:bg-slate-900 ${className}`}
77
+ >
78
+ {opciones.map((opcion) => {
79
+ const activo = opcion.value === valor;
80
+ return (
81
+ <button
82
+ key={opcion.value}
83
+ type="button"
84
+ onClick={() => onChange(opcion.value)}
85
+ aria-pressed={activo}
86
+ title={opcion.label}
87
+ className={`inline-flex h-7 items-center justify-center gap-1.5 rounded-[7px] font-mono text-[10px] font-medium uppercase tracking-[0.1em] transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/60 ${
88
+ conEtiqueta ? "flex-1 px-3" : opcion.icono ? "w-7 px-0" : "px-2.5"
89
+ } ${
90
+ activo
91
+ ? "bg-primary-700 text-white dark:bg-primary-500 dark:text-slate-950"
92
+ : "text-slate-600 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white"
93
+ }`}
94
+ >
95
+ {opcion.icono}
96
+ {conEtiqueta || !opcion.icono ? (
97
+ <span>{opcion.label}</span>
98
+ ) : (
99
+ <span className="sr-only">{opcion.label}</span>
100
+ )}
101
+ </button>
102
+ );
103
+ })}
104
+ </div>
105
+ );
106
+
107
+ /**
108
+ * Selector de idioma (ES/EN). Comparte estado con cualquier otro selector de la
109
+ * página a través de `useSandboxLocale`.
110
+ *
111
+ * @example
112
+ * ```tsx
113
+ * <SelectorIdioma />
114
+ * ```
115
+ */
116
+ export const SelectorIdioma = ({ className = "" }: { className?: string }) => {
117
+ const { t } = useITTranslation();
118
+ const { locale, setLocale } = useSandboxLocale();
119
+
120
+ return (
121
+ <ControlSegmentado
122
+ className={className}
123
+ etiqueta={t("sandbox.language")}
124
+ valor={locale}
125
+ onChange={(value) => setLocale(value === "en" ? "en" : "es")}
126
+ opciones={[
127
+ { value: "es", label: "ES" },
128
+ { value: "en", label: "EN" },
129
+ ]}
130
+ />
131
+ );
132
+ };
133
+
134
+ /**
135
+ * Selector de modo de color: claro, oscuro o el del sistema. Si no hay
136
+ * `ITThemeProvider` encima no se pinta (no rompe el render aislado).
137
+ *
138
+ * @example
139
+ * ```tsx
140
+ * <SelectorTema />
141
+ * ```
142
+ */
143
+ export const SelectorTema = ({ className = "" }: { className?: string }) => {
144
+ const { t } = useITTranslation();
145
+ const theme = useITThemeSafe();
146
+
147
+ if (!theme) return null;
148
+
149
+ return (
150
+ <ControlSegmentado
151
+ className={className}
152
+ etiqueta={t("sandbox.theme")}
153
+ valor={theme.darkModeMode}
154
+ onChange={(value) =>
155
+ theme.setDarkModeMode(value === "dark" ? "dark" : value === "light" ? "light" : "system")
156
+ }
157
+ opciones={[
158
+ { value: "light", label: t("sandbox.themeLight"), icono: <FaSun size={11} /> },
159
+ { value: "dark", label: t("sandbox.themeDark"), icono: <FaMoon size={11} /> },
160
+ { value: "system", label: t("sandbox.themeSystem"), icono: <FaDesktop size={11} /> },
161
+ ]}
162
+ />
163
+ );
164
+ };
165
+
166
+ /**
167
+ * Par de controles (idioma + tema) tal como se pintan en la cabecera del
168
+ * portafolio.
169
+ *
170
+ * @example
171
+ * ```tsx
172
+ * <header><nav>…<ControlesPortafolio /></nav></header>
173
+ * ```
174
+ */
175
+ export const ControlesPortafolio = ({ className = "" }: { className?: string }) => (
176
+ <div className={`flex shrink-0 items-center gap-2 ${className}`}>
177
+ <SelectorIdioma />
178
+ <SelectorTema />
179
+ </div>
180
+ );