@mirror-physics/fractal-ui 0.2.0-next.75 → 0.2.0-next.76

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
@@ -43,3 +43,11 @@ import { Button, Card, CardContent } from '@mirror-physics/fractal-ui'
43
43
  npm run typecheck
44
44
  npm run build
45
45
  ```
46
+
47
+ ### Shared control sizes
48
+
49
+ Use `controlSize="xs" | "sm" | "md" | "lg" | "xl"` on Button, Dropdown, Input, NumberInput, TableSearch, PasswordInput, and Label. The default is `md`. Matching sizes share typography and padding; their block size derives from the line box, padding, and borders. Existing `Button.buttonSize` and `Dropdown.size` remain aliases; `controlSize` wins if both are supplied. Native numeric `Input.size` retains its HTML meaning.
50
+
51
+ Compose a persistent label and control in `className="fractal-field"` to use the shared label gap. For dropdowns, connect `Label.htmlFor` to `Dropdown.triggerId` and provide a meaningful `triggerLabel`. Use matching sizes across a row. Icons add a shared leading slot; centered action labels remain centered.
52
+
53
+ This changes the rendered geometry of existing size presets, including default controls. Remove product CSS that hardcodes the previous heights. Compact sizes suit pointer interfaces; choose an appropriately large target for touch. Custom multiline dropdown content may grow beyond the single-line minimum.
package/dist/index.cjs CHANGED
@@ -161,31 +161,19 @@ var colorPalette = {
161
161
  700: "var(--green-700)"
162
162
  }
163
163
  };
164
- var sizeConfig = {
165
- xs: { height: "28px", padding: "4px 8px", iconOnly: "28px", fontSize: "0.75rem" },
166
- sm: { height: "32px", padding: "6px 12px", iconOnly: "32px", fontSize: "0.75rem" },
167
- md: { height: "40px", padding: "8px 16px", iconOnly: "40px", fontSize: "0.875rem" },
168
- lg: { height: "44px", padding: "10px 20px", iconOnly: "44px", fontSize: "1rem" },
169
- xl: { height: "56px", padding: "14px 28px", iconOnly: "56px", fontSize: "1rem" }
170
- };
171
- function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled, color, hasIconOnly) {
164
+ function getButtonStyles(buttonType, isHovered, isActive, disabled, color, hasIconOnly) {
172
165
  const palette = colorPalette[color];
173
- const size = sizeConfig[buttonSize];
174
166
  const base = {
175
- height: size.height,
176
- padding: hasIconOnly ? "0" : size.padding,
177
167
  borderRadius: hasIconOnly ? "var(--radius-lg, 8px)" : "var(--radius-full, 9999px)",
178
168
  cursor: disabled ? "not-allowed" : "pointer",
179
- fontSize: size.fontSize,
180
169
  transition: "background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease"
181
170
  };
182
171
  if (hasIconOnly) {
183
- base.width = size.iconOnly;
184
172
  base.justifyContent = "center";
185
173
  }
186
174
  if (disabled) {
187
175
  if (buttonType === "ghost") {
188
- return { ...base, backgroundColor: "transparent", border: "none", color: "var(--fg-disabled)" };
176
+ return { ...base, backgroundColor: "transparent", border: "1px solid transparent", color: "var(--fg-disabled)" };
189
177
  }
190
178
  return { ...base, backgroundColor: "transparent", border: "1px solid var(--fg-disabled)", color: "var(--fg-disabled)" };
191
179
  }
@@ -196,7 +184,7 @@ function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled,
196
184
  return {
197
185
  ...base,
198
186
  backgroundColor: isActive ? palette[700] : isHovered ? palette[500] : palette[600],
199
- border: "none",
187
+ border: "1px solid transparent",
200
188
  color: "var(--fg-inverse)"
201
189
  };
202
190
  }
@@ -220,7 +208,7 @@ function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled,
220
208
  return {
221
209
  ...base,
222
210
  backgroundColor: isHovered || isActive ? "var(--bg-surface-1)" : "transparent",
223
- border: "none",
211
+ border: "1px solid transparent",
224
212
  color: "var(--fg-primary)"
225
213
  };
226
214
  // Danger: low-intensity destructive action. Text stays steady; hover deepens the light red surface.
@@ -228,7 +216,7 @@ function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled,
228
216
  return {
229
217
  ...base,
230
218
  backgroundColor: isHovered || isActive ? "var(--danger-button-surface-hover)" : "var(--danger-button-surface)",
231
- border: "none",
219
+ border: "1px solid transparent",
232
220
  color: "var(--danger-button-fg)"
233
221
  };
234
222
  default:
@@ -236,10 +224,10 @@ function getButtonStyles(buttonType, isHovered, isActive, buttonSize, disabled,
236
224
  }
237
225
  }
238
226
  var Button = React.forwardRef(
239
- ({ className, buttonType = "primary", buttonSize = "md", color = "blue", icon, hasIconOnly, iconPosition = "left", style, disabled, children, ...props }, ref) => {
227
+ ({ className, buttonType = "primary", buttonSize = "md", controlSize, color = "blue", icon, hasIconOnly, iconPosition = "left", style, disabled, children, ...props }, ref) => {
240
228
  const [isHovered, setIsHovered] = React.useState(false);
241
229
  const [isActive, setIsActive] = React.useState(false);
242
- const typeStyles = getButtonStyles(buttonType, isHovered, isActive, buttonSize, !!disabled, color, hasIconOnly);
230
+ const typeStyles = getButtonStyles(buttonType, isHovered, isActive, !!disabled, color, hasIconOnly);
243
231
  const hasIconAndText = icon && children && !hasIconOnly;
244
232
  const finalStyles = hasIconAndText ? { ...typeStyles, gap: "var(--space-2, 8px)", ...style } : { ...typeStyles, ...style };
245
233
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -247,7 +235,9 @@ var Button = React.forwardRef(
247
235
  {
248
236
  ref,
249
237
  type: "button",
250
- className: cn("fractal-button", `fractal-button--${buttonType}`, className),
238
+ "data-control-size": controlSize ?? buttonSize,
239
+ "data-icon-only": hasIconOnly || void 0,
240
+ className: cn("fractal-control", "fractal-button", `fractal-button--${buttonType}`, className),
251
241
  style: finalStyles,
252
242
  disabled,
253
243
  onMouseEnter: (e) => {
@@ -649,7 +639,7 @@ var React2 = __toESM(require("react"), 1);
649
639
  var import_fractal_icons3 = require("@mirror-physics/fractal-icons");
650
640
  var import_jsx_runtime4 = require("react/jsx-runtime");
651
641
  var Card = React2.forwardRef(
652
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, className: cn("fractal-card", className), ...props })
642
+ ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, "data-fractal-border-surface": "surface-1", className: cn("fractal-card", className), ...props })
653
643
  );
654
644
  Card.displayName = "Card";
655
645
  var CardHeader = React2.forwardRef(
@@ -779,12 +769,13 @@ PromptGrid.displayName = "PromptGrid";
779
769
  var React4 = __toESM(require("react"), 1);
780
770
  var import_jsx_runtime6 = require("react/jsx-runtime");
781
771
  var Input = React4.forwardRef(
782
- ({ className, type, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
772
+ ({ className, type, controlSize = "md", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
783
773
  "input",
784
774
  {
785
775
  type,
786
776
  ref,
787
- className: cn("fractal-input", className),
777
+ "data-control-size": controlSize,
778
+ className: cn("fractal-control", "fractal-input", className),
788
779
  ...props
789
780
  }
790
781
  )
@@ -798,6 +789,7 @@ var import_jsx_runtime7 = require("react/jsx-runtime");
798
789
  var TableSearch = React5.forwardRef(
799
790
  ({
800
791
  searchType = "text",
792
+ controlSize = "md",
801
793
  className,
802
794
  inputClassName,
803
795
  placeholder,
@@ -811,12 +803,14 @@ var TableSearch = React5.forwardRef(
811
803
  {
812
804
  className: cn("fractal-table-search", className),
813
805
  "data-search-type": searchType,
806
+ "data-control-size": controlSize,
814
807
  children: [
815
808
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SearchIcon, { className: "fractal-table-search__icon", size: 17, "aria-hidden": "true" }),
816
809
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
817
810
  Input,
818
811
  {
819
812
  ...props,
813
+ controlSize,
820
814
  ref,
821
815
  type: "search",
822
816
  className: cn("fractal-table-search__input", inputClassName),
@@ -837,6 +831,7 @@ var import_fractal_icons5 = require("@mirror-physics/fractal-icons");
837
831
  var import_jsx_runtime8 = require("react/jsx-runtime");
838
832
  function NumberInput({
839
833
  unit,
834
+ controlSize = "md",
840
835
  className,
841
836
  value,
842
837
  defaultValue,
@@ -860,11 +855,12 @@ function NumberInput({
860
855
  input.dispatchEvent(new Event("input", { bubbles: true }));
861
856
  input.focus();
862
857
  };
863
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: cn("fractal-number-input", unit && "fractal-number-input--with-unit", className), children: [
858
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { "data-control-size": controlSize, className: cn("fractal-number-input", unit && "fractal-number-input--with-unit", className), children: [
864
859
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
865
860
  Input,
866
861
  {
867
862
  ref: inputRef,
863
+ controlSize,
868
864
  type: "number",
869
865
  value,
870
866
  defaultValue,
@@ -879,7 +875,7 @@ function NumberInput({
879
875
  "span",
880
876
  {
881
877
  className: "fractal-number-input-unit",
882
- style: { left: `calc(var(--space-3, 12px) + ${characterCount}ch)` },
878
+ style: { left: `calc(var(--control-padding-inline) + ${characterCount}ch)` },
883
879
  "aria-hidden": "true",
884
880
  children: unit
885
881
  }
@@ -1044,6 +1040,7 @@ var import_jsx_runtime10 = require("react/jsx-runtime");
1044
1040
  var PasswordInput = React7.forwardRef(
1045
1041
  ({
1046
1042
  className,
1043
+ controlSize = "md",
1047
1044
  disabled,
1048
1045
  showPasswordLabel = "Show password",
1049
1046
  hidePasswordLabel = "Hide password",
@@ -1055,12 +1052,14 @@ var PasswordInput = React7.forwardRef(
1055
1052
  "div",
1056
1053
  {
1057
1054
  className: cn("fractal-password-input", className),
1055
+ "data-control-size": controlSize,
1058
1056
  "data-password-visible": passwordVisible ? "" : void 0,
1059
1057
  children: [
1060
1058
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1061
1059
  Input,
1062
1060
  {
1063
1061
  ...props,
1062
+ controlSize,
1064
1063
  ref,
1065
1064
  className: "fractal-password-input-field",
1066
1065
  disabled,
@@ -1091,10 +1090,11 @@ PasswordInput.displayName = "PasswordInput";
1091
1090
  var React8 = __toESM(require("react"), 1);
1092
1091
  var import_jsx_runtime11 = require("react/jsx-runtime");
1093
1092
  var Label = React8.forwardRef(
1094
- ({ className, ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1093
+ ({ className, controlSize = "md", ...props }, ref) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1095
1094
  "label",
1096
1095
  {
1097
1096
  ref,
1097
+ "data-control-size": controlSize,
1098
1098
  className: cn("fractal-label", className),
1099
1099
  ...props
1100
1100
  }
@@ -1449,7 +1449,9 @@ function Dropdown({
1449
1449
  label,
1450
1450
  iconOnly = false,
1451
1451
  noChevron = false,
1452
- size = "md",
1452
+ size: sizeAlias = "md",
1453
+ controlSize,
1454
+ triggerId,
1453
1455
  triggerVariant = "outline",
1454
1456
  className,
1455
1457
  triggerClassName,
@@ -1457,6 +1459,7 @@ function Dropdown({
1457
1459
  closeOnSelect = true,
1458
1460
  disabled = false
1459
1461
  }) {
1462
+ const size = controlSize ?? sizeAlias;
1460
1463
  const [open, setOpen] = React12.useState(false);
1461
1464
  const triggerRef = React12.useRef(null);
1462
1465
  const panelRef = React12.useRef(null);
@@ -1531,11 +1534,13 @@ function Dropdown({
1531
1534
  document.addEventListener("mousedown", handleClickOutside);
1532
1535
  return () => document.removeEventListener("mousedown", handleClickOutside);
1533
1536
  }, [open]);
1534
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: cn("fractal-dropdown", className), children: [
1537
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { "data-control-size": size, className: cn("fractal-dropdown", className), children: [
1535
1538
  /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
1536
1539
  "button",
1537
1540
  {
1538
1541
  ref: triggerRef,
1542
+ id: triggerId,
1543
+ "data-control-size": size,
1539
1544
  type: "button",
1540
1545
  disabled,
1541
1546
  "aria-haspopup": "listbox",
@@ -1543,6 +1548,7 @@ function Dropdown({
1543
1548
  "aria-label": triggerLabel,
1544
1549
  onClick: () => setOpen((o) => !o),
1545
1550
  className: cn(
1551
+ "fractal-control",
1546
1552
  "fractal-dropdown-trigger",
1547
1553
  `fractal-dropdown-trigger--${size}`,
1548
1554
  `fractal-dropdown-trigger--${triggerVariant}`,
@@ -1632,6 +1638,7 @@ var Chip = React14.forwardRef(
1632
1638
  "span",
1633
1639
  {
1634
1640
  ref,
1641
+ "data-fractal-border-surface": tone === "neutral" ? "surface-2" : void 0,
1635
1642
  className: cn("fractal-chip", `fractal-chip--${tone}`, className),
1636
1643
  ...props
1637
1644
  }
@@ -2311,8 +2318,8 @@ function Ghost({ className, width, height, radius, style, ...props }) {
2311
2318
  function GhostLine({ className, size = "md", width = "100%", ...props }) {
2312
2319
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Ghost, { className: cn("fractal-ghost-line", `fractal-ghost-line--${size}`, className), width, ...props });
2313
2320
  }
2314
- function ButtonGhost({ className, size = "md", iconOnly = false, width, ...props }) {
2315
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Ghost, { className: cn("fractal-ghost-button", `fractal-ghost-button--${size}`, iconOnly && "fractal-ghost-button--icon-only", className), width, ...props });
2321
+ function ButtonGhost({ className, size = "md", controlSize, iconOnly = false, width, ...props }) {
2322
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Ghost, { "data-control-size": controlSize ?? size, className: cn("fractal-ghost-button", `fractal-ghost-button--${size}`, iconOnly && "fractal-ghost-button--icon-only", className), width, ...props });
2316
2323
  }
2317
2324
  function CardGhost({ className, lines = 3, showHeader = true, ...props }) {
2318
2325
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-card", className), ...props, children: [
@@ -2345,8 +2352,8 @@ function TextareaGhost({ className, labelWidth = "26%", ...props }) {
2345
2352
  function LabelGhost({ className, width = "26%", ...props }) {
2346
2353
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(GhostLine, { className: cn("fractal-ghost-label", className), size: "sm", width, ...props });
2347
2354
  }
2348
- function FormFieldGhost({ className, labelWidth = "26%", multiline = false, ...props }) {
2349
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-field", className), ...props, children: [
2355
+ function FormFieldGhost({ className, labelWidth = "26%", multiline = false, controlSize = "md", ...props }) {
2356
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { "aria-hidden": "true", "data-control-size": controlSize, className: cn("fractal-ghost-field", className), ...props, children: [
2350
2357
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(LabelGhost, { width: labelWidth }),
2351
2358
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Ghost, { className: cn("fractal-ghost-field__control", multiline && "fractal-ghost-field__control--multiline") })
2352
2359
  ] });
@@ -2371,8 +2378,8 @@ function LinkGhost({ className, width = 96, ...props }) {
2371
2378
  return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(GhostLine, { className: cn("fractal-ghost-link", className), size: "sm", width, ...props });
2372
2379
  }
2373
2380
  var TextButtonGhost = LinkGhost;
2374
- function DropdownGhost({ className, ...props }) {
2375
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { "aria-hidden": "true", className: cn("fractal-ghost-dropdown", className), ...props, children: [
2381
+ function DropdownGhost({ className, controlSize = "md", ...props }) {
2382
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { "aria-hidden": "true", "data-control-size": controlSize, className: cn("fractal-ghost-dropdown", className), ...props, children: [
2376
2383
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(GhostLine, { width: "38%" }),
2377
2384
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(Ghost, { className: "fractal-ghost-dropdown__chevron" })
2378
2385
  ] });