@satellatickets/ui 0.2.0 → 0.3.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 CHANGED
@@ -35,7 +35,7 @@ Requiere `react-dom >=19.0`. Importa los estilos **una vez**, en la entrada de l
35
35
  import '@satellatickets/ui/styles.css';
36
36
  ```
37
37
 
38
- Es un CSS estático, sin runtime de estilos: funciona con SSR y Server Components, y cambiar de tema no re-renderiza nada.
38
+ Es un CSS estático, sin runtime de estilos: funciona con SSR, y cambiar de tema no re-renderiza nada. En Next.js, los componentes se importan directamente desde una página o un layout, sin envolverlos: el paquete ya lleva `"use client"`.
39
39
 
40
40
  ### React Native
41
41
 
@@ -51,7 +51,7 @@ No hay nada más que configurar: Metro resuelve la versión nativa del paquete.
51
51
 
52
52
  ### Fuentes
53
53
 
54
- La librería nombra las familias de Satella (Unbounded, Hanken Grotesk e IBM Plex Mono) pero no las empaqueta: las carga cada app, con `next/font` o un `<link>` en web y con `expo-font` en nativo. Si una fuente no está cargada, se usa la del sistema.
54
+ La librería nombra las familias de Satella (Unbounded, Hanken Grotesk e IBM Plex Mono) pero no las empaqueta: las carga cada app, con `@fontsource` o un `<link>` en web y con `expo-font` en nativo. Si una fuente no está cargada, se usa la del sistema.
55
55
 
56
56
  ## Tema y marca
57
57
 
@@ -189,7 +189,7 @@ interface SelectNativeProps extends SelectProps$1 {
189
189
  //#endregion
190
190
  //#region src/select/Select.native.d.ts
191
191
  /**
192
- * Un disparador con aspecto de `Input` que abre una lista modal (ADR-038). React
192
+ * Un disparador con aspecto de `Input` que abre una lista modal (ADR-042). React
193
193
  * Native no trae ningún selector, y así `Select` no añade dependencias.
194
194
  */
195
195
  export declare function Select({ options, value, defaultValue, onValueChange, placeholder, size, disabled, invalid, required, accessibilityLabel, style, testID, ref }: SelectNativeProps): import("react").JSX.Element;
@@ -1536,7 +1536,7 @@ const styles$6 = StyleSheet.create({
1536
1536
  }
1537
1537
  });
1538
1538
  /**
1539
- * Un disparador con aspecto de `Input` que abre una lista modal (ADR-038). React
1539
+ * Un disparador con aspecto de `Input` que abre una lista modal (ADR-042). React
1540
1540
  * Native no trae ningún selector, y así `Select` no añade dependencias.
1541
1541
  */
1542
1542
  function Select({ options, value, defaultValue, onValueChange, placeholder, size = "md", disabled, invalid, required, accessibilityLabel, style, testID, ref }) {
@@ -1,3 +1,4 @@
1
+
1
2
  import { AlertProps, AlertProps as AlertProps$1, BadgeProps, BadgeProps as BadgeProps$1, BoxProps, BoxProps as BoxProps$1, ButtonProps as ButtonProps$1, CardProps, CardProps as CardProps$1, CheckboxProps, CheckboxProps as CheckboxProps$1, ColorScheme, DividerProps, DividerProps as DividerProps$1, FormFieldProps, FormFieldProps as FormFieldProps$1, IconButtonProps as IconButtonProps$1, IconProps as IconProps$1, InputProps as InputProps$1, LinkPressEvent, LinkProps, LinkProps as LinkProps$1, ModalProps, ModalProps as ModalProps$1, SelectOption, SelectProps, SelectProps as SelectProps$1, SheetProps, SkeletonProps, SkeletonProps as SkeletonProps$1, SpinnerProps, SpinnerProps as SpinnerProps$1, StackProps, StackProps as StackProps$1, SwitchProps, SwitchProps as SwitchProps$1, TabItem as TabItem$1, TabsProps as TabsProps$1, TextAreaProps, TextAreaProps as TextAreaProps$1, TextProps, TextProps as TextProps$1, ThemeMode, ToastAction, ToastApi, ToastOptions, UIProviderProps, UIProviderProps as UIProviderProps$1, useBrand, useColorScheme, useTheme, useToast } from "@satellatickets/core";
2
3
  import { CSSProperties, Ref } from "react";
3
4
  import "react-native";
@@ -181,20 +182,22 @@ export declare function Modal(props: ModalWebProps): import("react").JSX.Element
181
182
  //#region src/select/Select.types.d.ts
182
183
  /** Extensión solo web del contrato (ADR-009): atributos de formulario y escape hatches. */
183
184
  interface SelectWebProps extends SelectProps$1 {
184
- /** `id` del `<select>`. Dentro de un `FormField` no hace falta: lo pone él. */
185
+ /** `id` del botón que abre la lista. Dentro de un `FormField` no hace falta: lo pone él. */
185
186
  id?: string | undefined;
186
187
  /** Nombre con el que viaja en un `<form>`. */
187
188
  name?: string | undefined;
188
- /** Se aplican a la caja del campo, no al `<select>`. */
189
+ /** Se aplican a la caja del campo, no al botón. */
189
190
  className?: string | undefined;
190
191
  style?: CSSProperties | undefined;
191
- ref?: Ref<HTMLSelectElement> | undefined;
192
+ /** El botón que abre la lista (ADR-042). */
193
+ ref?: Ref<HTMLButtonElement> | undefined;
192
194
  }
193
195
  //#endregion
194
196
  //#region src/select/Select.web.d.ts
195
197
  /**
196
- * Un `<select>` real (ADR-038): el teclado, los lectores de pantalla y el selector del
197
- * sistema en móvil son los del navegador.
198
+ * Un botón que abre una lista propia (ADR-042). El foco no sale del botón: la opción
199
+ * resaltada se comunica con `aria-activedescendant`, y la lista se pinta en la capa
200
+ * superior del navegador, por encima de todo y sin que la recorte ningún contenedor.
198
201
  */
199
202
  export declare function Select({ options, value, defaultValue, onValueChange, placeholder, size, disabled, invalid, required, accessibilityLabel, id, name, className, style, testID, ref }: SelectWebProps): import("react").JSX.Element;
200
203
  //#endregion
package/dist/web/index.js CHANGED
@@ -1,7 +1,8 @@
1
- import { FormFieldContext, ToastContext, UIContext, buttonIconSize, createFormFieldValue, createToastStore, createUIContextValue, firstEnabledTab, getTabInDirection, iconButtonIconSize, iconSizePx, inputIconSize, isFeedbackTone, textVariantStyles, useBrand, useButton, useColorScheme, useControllableState, useFormFieldControl, useLink, useTheme, useToast } from "@satellatickets/core";
1
+ 'use client';
2
+ import { FormFieldContext, ToastContext, UIContext, buttonIconSize, createFormFieldValue, createToastStore, createUIContextValue, findOptionByText, firstEnabledTab, getOptionInDirection, getTabInDirection, iconButtonIconSize, iconSizePx, inputIconSize, isFeedbackTone, textVariantStyles, useBrand, useButton, useColorScheme, useControllableState, useFormFieldControl, useLink, useTheme, useToast } from "@satellatickets/core";
2
3
  import { cssVariables } from "@satellatickets/tokens";
3
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
- import { useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
+ import { useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
6
  //#region src/_internal/cx.ts
6
7
  /** Une clases CSS ignorando valores falsos. */
7
8
  function cx(...parts) {
@@ -1084,13 +1085,19 @@ function Modal(props) {
1084
1085
  //#endregion
1085
1086
  //#region src/select/Select.module.css
1086
1087
  var Select_module_default = {
1088
+ "active": "sui-active-Rxx4wG",
1087
1089
  "chevron": "sui-chevron-Rxx4wG",
1090
+ "disabled": "sui-disabled-Rxx4wG",
1091
+ "focused": "sui-focused-Rxx4wG",
1092
+ "label": "sui-label-Rxx4wG",
1088
1093
  "lg": "sui-lg-Rxx4wG",
1094
+ "list": "sui-list-Rxx4wG",
1089
1095
  "md": "sui-md-Rxx4wG",
1096
+ "option": "sui-option-Rxx4wG",
1090
1097
  "placeholder": "sui-placeholder-Rxx4wG",
1091
1098
  "root": "sui-root-Rxx4wG",
1092
- "select": "sui-select-Rxx4wG",
1093
- "sm": "sui-sm-Rxx4wG"
1099
+ "sm": "sui-sm-Rxx4wG",
1100
+ "trigger": "sui-trigger-Rxx4wG"
1094
1101
  };
1095
1102
  //#endregion
1096
1103
  //#region src/select/Select.web.tsx
@@ -1099,57 +1106,260 @@ const sizeClass = {
1099
1106
  md: Select_module_default.md,
1100
1107
  lg: Select_module_default.lg
1101
1108
  };
1109
+ const keyDirection$1 = {
1110
+ ArrowDown: "next",
1111
+ ArrowUp: "previous",
1112
+ Home: "first",
1113
+ End: "last",
1114
+ PageDown: "nextPage",
1115
+ PageUp: "previousPage"
1116
+ };
1117
+ /** Pasado este tiempo sin escribir, la siguiente letra empieza una búsqueda nueva. */
1118
+ const SEARCH_TIMEOUT_MS = 500;
1119
+ /** Lo escrito para buscar una opción y el momento de la última letra. */
1120
+ const NO_SEARCH = {
1121
+ text: "",
1122
+ at: Number.NEGATIVE_INFINITY
1123
+ };
1102
1124
  /**
1103
- * Un `<select>` real (ADR-038): el teclado, los lectores de pantalla y el selector del
1104
- * sistema en móvil son los del navegador.
1125
+ * Un botón que abre una lista propia (ADR-042). El foco no sale del botón: la opción
1126
+ * resaltada se comunica con `aria-activedescendant`, y la lista se pinta en la capa
1127
+ * superior del navegador, por encima de todo y sin que la recorte ningún contenedor.
1105
1128
  */
1106
1129
  function Select({ options, value, defaultValue, onValueChange, placeholder, size = "md", disabled, invalid, required, accessibilityLabel, id, name, className, style, testID, ref }) {
1107
1130
  const control = useFormFieldControl({
1108
1131
  id,
1109
1132
  invalid,
1110
1133
  disabled,
1111
- required
1134
+ required,
1135
+ accessibilityLabel
1112
1136
  });
1113
1137
  const [current, setCurrent] = useControllableState({
1114
1138
  value,
1115
1139
  defaultValue: defaultValue ?? "",
1116
1140
  onChange: onValueChange
1117
1141
  });
1142
+ const [opened, setOpened] = useState(false);
1143
+ const [active, setActive] = useState();
1144
+ const baseId = useId();
1145
+ const root = useRef(null);
1146
+ const list = useRef(null);
1147
+ const search = useRef(NO_SEARCH);
1148
+ const pointer = useRef(void 0);
1149
+ if (opened && control.disabled) setOpened(false);
1150
+ const open = opened && !control.disabled;
1151
+ const selected = options.find((option) => option.value === current);
1152
+ const activeIndex = open ? options.findIndex((option) => option.value === active?.value) : -1;
1153
+ const listId = `${baseId}-list`;
1154
+ const activeId = activeIndex === -1 ? void 0 : `${baseId}-option-${activeIndex}`;
1155
+ /** Abre la lista con una opción resaltada: la indicada o, si no, la elegida. */
1156
+ function show(keyboard, highlighted = selected?.disabled === true ? void 0 : selected) {
1157
+ setActive(highlighted === void 0 ? void 0 : {
1158
+ value: highlighted.value,
1159
+ keyboard
1160
+ });
1161
+ setOpened(true);
1162
+ if (keyboard) pointer.current = void 0;
1163
+ }
1164
+ function choose(next) {
1165
+ setCurrent(next);
1166
+ setOpened(false);
1167
+ }
1168
+ useLayoutEffect(() => {
1169
+ const anchor = root.current;
1170
+ const popup = list.current;
1171
+ if (!open || anchor === null || popup === null) return;
1172
+ let above = false;
1173
+ const place = () => {
1174
+ const { top, bottom, left, width } = anchor.getBoundingClientRect();
1175
+ const viewport = document.documentElement.clientHeight;
1176
+ popup.dataset.side = above ? "top" : "bottom";
1177
+ popup.style.setProperty("--select-list-left", `${left}px`);
1178
+ popup.style.setProperty("--select-list-width", `${width}px`);
1179
+ popup.style.setProperty("--select-list-offset", `${above ? viewport - top : bottom}px`);
1180
+ popup.style.setProperty("--select-list-space", `${above ? top : viewport - bottom}px`);
1181
+ };
1182
+ const { top, bottom, width } = anchor.getBoundingClientRect();
1183
+ popup.style.setProperty("--select-list-width", `${width}px`);
1184
+ if (typeof popup.showPopover === "function" && !popup.matches(":popover-open")) popup.showPopover();
1185
+ const wanted = popup.offsetHeight + parseFloat(getComputedStyle(popup).marginTop) * 2;
1186
+ const below = document.documentElement.clientHeight - bottom;
1187
+ above = below < wanted && top > below;
1188
+ place();
1189
+ const chosen = popup.querySelector("[aria-selected=\"true\"]");
1190
+ if (chosen !== null) popup.scrollTop = chosen.offsetTop - (popup.clientHeight - chosen.offsetHeight) / 2;
1191
+ const handlePointerDown = (event) => {
1192
+ if (event.target instanceof Node && !anchor.contains(event.target)) setOpened(false);
1193
+ };
1194
+ const handleScroll = (event) => {
1195
+ if (event.target instanceof Node && popup.contains(event.target)) return;
1196
+ place();
1197
+ };
1198
+ document.addEventListener("pointerdown", handlePointerDown, true);
1199
+ window.addEventListener("scroll", handleScroll, true);
1200
+ window.addEventListener("resize", place);
1201
+ return () => {
1202
+ document.removeEventListener("pointerdown", handlePointerDown, true);
1203
+ window.removeEventListener("scroll", handleScroll, true);
1204
+ window.removeEventListener("resize", place);
1205
+ };
1206
+ }, [open]);
1207
+ useLayoutEffect(() => {
1208
+ if (activeId !== void 0) document.getElementById(activeId)?.scrollIntoView({ block: "nearest" });
1209
+ }, [activeId]);
1210
+ function handleClick(event) {
1211
+ event.currentTarget.focus();
1212
+ pointer.current = {
1213
+ x: event.clientX,
1214
+ y: event.clientY
1215
+ };
1216
+ if (open) setOpened(false);
1217
+ else show(false);
1218
+ }
1219
+ function handlePointerMove(event, option) {
1220
+ if (event.pointerType === "touch") return;
1221
+ const last = pointer.current;
1222
+ pointer.current = {
1223
+ x: event.clientX,
1224
+ y: event.clientY
1225
+ };
1226
+ if (last === void 0 || last.x === event.clientX && last.y === event.clientY) return;
1227
+ if (option.disabled === true) return;
1228
+ if (active?.value !== option.value || active.keyboard) setActive({
1229
+ value: option.value,
1230
+ keyboard: false
1231
+ });
1232
+ }
1233
+ function handleKeyDown(event) {
1234
+ const { key } = event;
1235
+ if (event.ctrlKey || event.metaKey) return;
1236
+ const searching = event.timeStamp - search.current.at < SEARCH_TIMEOUT_MS;
1237
+ if (key.length === 1 && !event.altKey && (key !== " " || searching)) {
1238
+ event.preventDefault();
1239
+ const text = searching ? search.current.text + key : key;
1240
+ search.current = {
1241
+ text,
1242
+ at: event.timeStamp
1243
+ };
1244
+ const match = findOptionByText(options, text, open ? active?.value : selected?.value);
1245
+ const option = options.find((candidate) => candidate.value === match);
1246
+ if (!open) show(true, option);
1247
+ else if (option !== void 0) setActive({
1248
+ value: option.value,
1249
+ keyboard: true
1250
+ });
1251
+ return;
1252
+ }
1253
+ const direction = keyDirection$1[key];
1254
+ const confirms = key === "Enter" || key === " ";
1255
+ if (direction === void 0 && !confirms && key !== "Escape" && key !== "Tab") return;
1256
+ search.current = NO_SEARCH;
1257
+ if (key === "Escape") {
1258
+ if (!open) return;
1259
+ event.preventDefault();
1260
+ event.stopPropagation();
1261
+ setOpened(false);
1262
+ return;
1263
+ }
1264
+ if (key === "Tab") {
1265
+ if (open && active !== void 0) choose(active.value);
1266
+ return;
1267
+ }
1268
+ event.preventDefault();
1269
+ if (!open) {
1270
+ const value = direction === "first" || direction === "last" ? getOptionInDirection(options, void 0, direction) : void 0;
1271
+ show(true, options.find((option) => option.value === value));
1272
+ return;
1273
+ }
1274
+ if (confirms || event.altKey && key === "ArrowUp") {
1275
+ if (active === void 0) setOpened(false);
1276
+ else choose(active.value);
1277
+ return;
1278
+ }
1279
+ if (direction === void 0 || event.altKey) return;
1280
+ const next = getOptionInDirection(options, active?.value, direction);
1281
+ setActive(next === void 0 ? void 0 : {
1282
+ value: next,
1283
+ keyboard: true
1284
+ });
1285
+ }
1118
1286
  return /* @__PURE__ */ jsxs("div", {
1287
+ ref: root,
1119
1288
  className: cx(field_module_default.field, Select_module_default.root, sizeClass[size], control.invalid && field_module_default.invalid, control.disabled && field_module_default.disabled, className),
1120
1289
  style,
1121
- children: [/* @__PURE__ */ jsxs("select", {
1122
- ref,
1123
- id: control.id,
1124
- className: cx(field_module_default.control, Select_module_default.select, current === "" && Select_module_default.placeholder),
1125
- name,
1126
- value: current,
1127
- onChange: (event) => setCurrent(event.target.value),
1128
- disabled: control.disabled,
1129
- "aria-label": accessibilityLabel,
1130
- "aria-describedby": control.describedBy,
1131
- "aria-invalid": control.invalid ? true : void 0,
1132
- "aria-required": control.required ? true : void 0,
1133
- "data-testid": testID,
1134
- children: [/* @__PURE__ */ jsx("option", {
1135
- value: "",
1136
- disabled: true,
1137
- hidden: true,
1138
- children: placeholder ?? ""
1139
- }), options.map((option) => /* @__PURE__ */ jsx("option", {
1140
- value: option.value,
1141
- disabled: option.disabled,
1142
- children: option.label
1143
- }, option.value))]
1144
- }), /* @__PURE__ */ jsx("span", {
1145
- className: Select_module_default.chevron,
1146
- "aria-hidden": "true",
1147
- children: /* @__PURE__ */ jsx(Icon, {
1148
- name: "chevron-down",
1149
- size: inputIconSize[size],
1150
- color: control.disabled ? "disabled" : "muted"
1290
+ children: [
1291
+ /* @__PURE__ */ jsx("button", {
1292
+ ref,
1293
+ type: "button",
1294
+ role: "combobox",
1295
+ id: control.id,
1296
+ className: cx(field_module_default.control, Select_module_default.trigger, selected === void 0 && Select_module_default.placeholder),
1297
+ disabled: control.disabled,
1298
+ "aria-haspopup": "listbox",
1299
+ "aria-expanded": open,
1300
+ "aria-controls": open ? listId : void 0,
1301
+ "aria-activedescendant": activeId,
1302
+ "aria-label": accessibilityLabel,
1303
+ "aria-describedby": control.describedBy,
1304
+ "aria-invalid": control.invalid ? true : void 0,
1305
+ "aria-required": control.required ? true : void 0,
1306
+ "data-testid": testID,
1307
+ onClick: handleClick,
1308
+ onKeyDown: handleKeyDown,
1309
+ onKeyUp: (event) => {
1310
+ if (event.key === " ") event.preventDefault();
1311
+ },
1312
+ onBlur: () => setOpened(false),
1313
+ children: selected?.label ?? placeholder ?? ""
1314
+ }),
1315
+ /* @__PURE__ */ jsx("span", {
1316
+ className: Select_module_default.chevron,
1317
+ "aria-hidden": "true",
1318
+ children: /* @__PURE__ */ jsx(Icon, {
1319
+ name: "chevron-down",
1320
+ size: inputIconSize[size],
1321
+ color: control.disabled ? "disabled" : "muted"
1322
+ })
1323
+ }),
1324
+ open ? /* @__PURE__ */ jsx("div", {
1325
+ ref: list,
1326
+ popover: "manual",
1327
+ role: "listbox",
1328
+ id: listId,
1329
+ tabIndex: -1,
1330
+ "aria-label": control.accessibilityLabel,
1331
+ className: Select_module_default.list,
1332
+ onMouseDown: (event) => event.preventDefault(),
1333
+ children: options.map((option, index) => {
1334
+ const isSelected = option.value === current;
1335
+ const isDisabled = option.disabled === true;
1336
+ const isActive = index === activeIndex;
1337
+ return /* @__PURE__ */ jsxs("div", {
1338
+ id: `${baseId}-option-${index}`,
1339
+ role: "option",
1340
+ "aria-selected": isSelected,
1341
+ "aria-disabled": isDisabled ? true : void 0,
1342
+ className: cx(Select_module_default.option, isActive && (active?.keyboard === true ? Select_module_default.focused : Select_module_default.active), isDisabled && Select_module_default.disabled),
1343
+ onClick: isDisabled ? void 0 : () => choose(option.value),
1344
+ onPointerMove: (event) => handlePointerMove(event, option),
1345
+ children: [/* @__PURE__ */ jsx("span", {
1346
+ className: Select_module_default.label,
1347
+ children: option.label
1348
+ }), isSelected ? /* @__PURE__ */ jsx(Icon, {
1349
+ name: "check",
1350
+ size: inputIconSize[size],
1351
+ color: "link"
1352
+ }) : null]
1353
+ }, option.value);
1354
+ })
1355
+ }) : null,
1356
+ name === void 0 ? null : /* @__PURE__ */ jsx("input", {
1357
+ type: "hidden",
1358
+ name,
1359
+ value: current,
1360
+ disabled: control.disabled
1151
1361
  })
1152
- })]
1362
+ ]
1153
1363
  });
1154
1364
  }
1155
1365
  //#endregion
@@ -784,27 +784,26 @@
784
784
  --select-icon: var(--space-5);
785
785
  }
786
786
 
787
- .sui-select-Rxx4wG {
787
+ .sui-trigger-Rxx4wG {
788
788
  min-height: calc(var(--select-height) - var(--border-width-thin) * 2);
789
789
  padding: var(--space-0) calc(var(--select-padding-x) + var(--select-icon) + var(--space-2))
790
790
  var(--space-0) var(--select-padding-x);
791
791
  border-radius: inherit;
792
+ text-align: start;
792
793
  text-overflow: ellipsis;
794
+ white-space: nowrap;
793
795
  cursor: inherit;
796
+ -webkit-tap-highlight-color: transparent;
794
797
  align-self: stretch;
798
+ overflow: hidden;
795
799
  }
796
800
 
797
801
  .sui-placeholder-Rxx4wG {
798
802
  color: var(--color-text-muted);
799
803
  }
800
804
 
801
- .sui-select-Rxx4wG option {
802
- background-color: var(--color-bg-surface);
803
- color: var(--color-text-primary);
804
- }
805
-
806
- .sui-select-Rxx4wG option:disabled {
807
- color: var(--color-text-disabled);
805
+ .sui-placeholder-Rxx4wG:disabled {
806
+ color: inherit;
808
807
  }
809
808
 
810
809
  .sui-chevron-Rxx4wG {
@@ -812,10 +811,85 @@
812
811
  right: var(--select-padding-x);
813
812
  bottom: var(--space-0);
814
813
  pointer-events: none;
814
+ transition: transform var(--duration-fast);
815
815
  align-items: center;
816
816
  display: flex;
817
817
  position: absolute;
818
818
  }
819
+
820
+ .sui-trigger-Rxx4wG[aria-expanded="true"] + .sui-chevron-Rxx4wG {
821
+ transform: rotate(180deg);
822
+ }
823
+
824
+ .sui-list-Rxx4wG {
825
+ inset: auto;
826
+ top: var(--select-list-offset);
827
+ left: var(--select-list-left);
828
+ z-index: var(--z-index-dropdown);
829
+ box-sizing: border-box;
830
+ width: max-content;
831
+ min-width: var(--select-list-width);
832
+ max-width: calc(100vw - var(--select-list-left, 0px) - var(--space-2));
833
+ max-height: min(calc(var(--size-control-md) * 7),
834
+ calc(var(--select-list-space, 100vh) - var(--space-1) * 2));
835
+ margin: var(--space-1) var(--space-0);
836
+ padding: var(--space-1);
837
+ overscroll-behavior: contain;
838
+ scroll-padding-block: var(--space-1);
839
+ scrollbar-color: var(--color-border-strong) transparent;
840
+ scrollbar-width: thin;
841
+ border: var(--border-width-thin) solid var(--color-border-default);
842
+ border-radius: var(--radius-md);
843
+ background-color: var(--color-bg-elevated);
844
+ box-shadow: var(--shadow-md);
845
+ color: var(--color-text-primary);
846
+ cursor: default;
847
+ position: fixed;
848
+ overflow-y: auto;
849
+ }
850
+
851
+ .sui-list-Rxx4wG[data-side="top"] {
852
+ top: auto;
853
+ bottom: var(--select-list-offset);
854
+ }
855
+
856
+ .sui-option-Rxx4wG {
857
+ align-items: center;
858
+ gap: var(--space-2);
859
+ box-sizing: border-box;
860
+ min-height: var(--select-height);
861
+ padding: var(--space-2) calc(var(--select-padding-x) - var(--space-1));
862
+ border-radius: var(--radius-sm);
863
+ cursor: pointer;
864
+ -webkit-tap-highlight-color: transparent;
865
+ display: flex;
866
+ }
867
+
868
+ .sui-label-Rxx4wG {
869
+ overflow-wrap: anywhere;
870
+ flex: auto;
871
+ min-width: 0;
872
+ }
873
+
874
+ .sui-active-Rxx4wG, .sui-option-Rxx4wG:active:not(.sui-disabled-Rxx4wG) {
875
+ background-color: var(--color-bg-muted);
876
+ }
877
+
878
+ .sui-focused-Rxx4wG {
879
+ outline: var(--border-width-thick) solid var(--color-border-focus);
880
+ outline-offset: calc(var(--border-width-thick) * -1);
881
+ }
882
+
883
+ .sui-disabled-Rxx4wG {
884
+ color: var(--color-text-disabled);
885
+ cursor: not-allowed;
886
+ }
887
+
888
+ @media (prefers-reduced-motion: reduce) {
889
+ .sui-chevron-Rxx4wG {
890
+ transition: none;
891
+ }
892
+ }
819
893
  .sui-root-KsqoQq {
820
894
  --skeleton-width: initial;
821
895
  --skeleton-height: initial;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@satellatickets/ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Componentes de satella-ui con una sola API para React (web) y React Native",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -54,7 +54,7 @@
54
54
  }
55
55
  },
56
56
  "dependencies": {
57
- "@satellatickets/core": "0.2.0",
57
+ "@satellatickets/core": "0.3.0",
58
58
  "@satellatickets/tokens": "0.1.0"
59
59
  },
60
60
  "devDependencies": {
@@ -80,8 +80,8 @@
80
80
  "tsdown": "^0.23.0",
81
81
  "typescript": "^5.9.3",
82
82
  "@satellatickets/eslint-config": "0.0.0",
83
- "@satellatickets/icons": "0.0.0",
84
- "@satellatickets/tsconfig": "0.0.0"
83
+ "@satellatickets/tsconfig": "0.0.0",
84
+ "@satellatickets/icons": "0.0.0"
85
85
  },
86
86
  "scripts": {
87
87
  "build": "tsdown",