@tinybigui/react 0.4.2 → 0.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.
package/dist/index.js CHANGED
@@ -1113,81 +1113,113 @@ var IconButtonHeadless = forwardRef(
1113
1113
  tabIndex = 0,
1114
1114
  onMouseDown,
1115
1115
  type,
1116
- selected,
1116
+ isSelected,
1117
+ isToggle = false,
1118
+ isDisabled = false,
1117
1119
  "aria-label": ariaLabel,
1118
1120
  title,
1119
1121
  ...props
1120
1122
  }, forwardedRef) => {
1121
1123
  const internalRef = useRef(null);
1122
1124
  const ref = forwardedRef ?? internalRef;
1123
- const { buttonProps } = useButton(
1125
+ const { buttonProps, isPressed } = useButton(
1124
1126
  {
1125
1127
  ...props,
1126
- // Ensure element type is 'button' for proper semantics
1127
1128
  elementType: "button",
1128
- // Pass aria-label
1129
- "aria-label": ariaLabel
1129
+ "aria-label": ariaLabel,
1130
+ isDisabled
1130
1131
  },
1131
1132
  ref
1132
1133
  );
1134
+ const { isHovered, hoverProps } = useHover({ isDisabled });
1135
+ const { isFocusVisible, focusProps } = useFocusRing();
1133
1136
  const domProps = filterDOMProps(props);
1134
- const mergedProps = mergeProps(
1135
- buttonProps,
1136
- domProps,
1137
+ const mergedProps = mergeProps(buttonProps, hoverProps, focusProps, domProps, {
1138
+ tabIndex,
1139
+ className,
1140
+ onMouseDown,
1141
+ type: type ?? "button",
1142
+ ...title && { title },
1143
+ // aria-pressed only when acting as a toggle button
1144
+ ...isToggle && { "aria-pressed": isSelected ?? false }
1145
+ });
1146
+ return /* @__PURE__ */ jsx(
1147
+ "button",
1137
1148
  {
1138
- tabIndex,
1139
- className,
1140
- onMouseDown,
1141
- type: type ?? "button",
1142
- // Add aria-pressed for toggle buttons (only if selected is defined)
1143
- ...selected !== void 0 && { "aria-pressed": selected },
1144
- // Add title if provided
1145
- ...title && { title }
1149
+ ...mergedProps,
1150
+ ref,
1151
+ type: type === "submit" ? "submit" : type === "reset" ? "reset" : "button",
1152
+ ...getInteractionDataAttributes({
1153
+ isHovered,
1154
+ isFocusVisible,
1155
+ isPressed,
1156
+ ...isToggle ? { isSelected: isSelected ?? false } : {},
1157
+ isDisabled
1158
+ }),
1159
+ "data-toggle": isToggle ? "" : void 0,
1160
+ children
1146
1161
  }
1147
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1148
- );
1149
- return (
1150
- // eslint-disable-next-line react/button-has-type
1151
- /* @__PURE__ */ jsx("button", { ...mergedProps, ref, type: type ?? "button", children })
1152
1162
  );
1153
1163
  }
1154
1164
  );
1155
1165
  IconButtonHeadless.displayName = "IconButtonHeadless";
1156
- var iconButtonVariants = cva(
1166
+ var iconButtonRootVariants = cva(
1157
1167
  [
1158
- // Base classes (always applied)
1159
- "relative inline-flex items-center justify-center cursor-pointer",
1160
- "overflow-hidden rounded-full",
1161
- // Circular shape
1162
- // Split MD3 transition: btn-transition handles spatial (border-radius) with asymmetric
1163
- // easing (decelerate by default, switched to expressive via btn-transition-selected when
1164
- // the button is group-selected) and effects (color/bg/shadow) with standard spring.
1168
+ // Layout
1169
+ "relative inline-flex items-center justify-center",
1170
+ "cursor-pointer select-none",
1171
+ "overflow-hidden",
1172
+ // Corner radius driven by CSS variable set per shape×size in compoundVariants.
1173
+ // Fallback 9999px is only reached if both shape and size props are absent,
1174
+ // which cannot happen in normal usage.
1175
+ "rounded-[var(--ib-radius,9999px)]",
1176
+ // Split MD3 transition via the existing btn-transition utility:
1177
+ // border-radius → emphasized-decelerate (no overshoot, no sharp-corner flash)
1178
+ // color/bg/border/opacity → standard-fast-effects (no overshoot on effects)
1179
+ // This is identical to the approach used by Button/connected ButtonGroup and is
1180
+ // the standard fix for the 9999px overshoot problem documented in styles.css.
1165
1181
  "btn-transition",
1166
- "focus-visible:outline-primary focus-visible:outline-2 focus-visible:outline-offset-2",
1167
- // State layers — effects token: opacity only, no overshoot (separate ::before pseudo-element)
1168
- "before:absolute before:inset-0 before:rounded-[inherit]",
1169
- "before:transition-opacity before:duration-spring-standard-fast-effects before:ease-spring-standard-fast-effects",
1170
- "before:bg-current before:opacity-0",
1171
- "hover:before:opacity-8",
1172
- "focus-visible:before:opacity-12",
1173
- "active:before:opacity-12"
1182
+ // Background + border + text driven from CSS role variables
1183
+ "bg-[var(--ib-bg,transparent)]",
1184
+ "border border-[var(--ib-border,transparent)]",
1185
+ "text-[var(--ib-fg,currentColor)]",
1186
+ // Toggle: off state (data-toggle present but data-selected absent)
1187
+ // Uses doubly-chained selector to beat single-chain specificity of defaults
1188
+ "data-[toggle]:bg-[var(--ib-bg-off,var(--ib-bg,transparent))]",
1189
+ "data-[toggle]:text-[var(--ib-fg-off,var(--ib-fg,currentColor))]",
1190
+ // Selected state
1191
+ "data-[selected]:bg-[var(--ib-bg-on,var(--ib-bg,transparent))]",
1192
+ "data-[selected]:text-[var(--ib-fg-on,var(--ib-fg,currentColor))]",
1193
+ "data-[selected]:border-transparent",
1194
+ // Press shape-morph: radius collapses to --ib-radius-press on press
1195
+ // (only has visual effect when --ib-radius-press differs from --ib-radius)
1196
+ "data-[pressed]:rounded-[var(--ib-radius-press,var(--ib-radius,9999px))]",
1197
+ // Focus ring (outline, not a state layer — stays outside overflow-hidden
1198
+ // because it's drawn as outline on the root element itself)
1199
+ "outline-none",
1200
+ "group-data-[focus-visible]/icon-button:outline-2",
1201
+ "group-data-[focus-visible]/icon-button:outline-offset-2",
1202
+ "group-data-[focus-visible]/icon-button:outline-secondary",
1203
+ // Disabled — content opacity 38%, container handled per variant via CSS vars
1204
+ "data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none",
1205
+ "data-[disabled]:text-on-surface/38"
1206
+ // Filled/tonal/outlined-selected backgrounds collapse to on-surface/12 — set
1207
+ // via compoundVariants on the root for variants that have a container.
1208
+ // For variants with transparent bg (standard, outlined-unselected) we do nothing.
1174
1209
  ],
1175
1210
  {
1176
1211
  variants: {
1177
1212
  /**
1178
- * Button variant (MD3 specification)
1213
+ * Visual style variant (MD3 icon button types)
1179
1214
  */
1180
1215
  variant: {
1181
- standard: "bg-transparent",
1182
- // No background
1183
- filled: "shadow-none",
1184
- // Solid background
1185
- tonal: "",
1186
- // Container background
1187
- outlined: "bg-transparent border border-outline"
1216
+ standard: "",
1217
+ filled: "data-[disabled]:bg-on-surface/12",
1218
+ tonal: "data-[disabled]:bg-on-surface/12",
1219
+ outlined: ""
1188
1220
  },
1189
1221
  /**
1190
- * Color scheme (MD3 color roles)
1222
+ * Color scheme sets CSS role variables via compoundVariants.
1191
1223
  */
1192
1224
  color: {
1193
1225
  primary: "",
@@ -1196,180 +1228,400 @@ var iconButtonVariants = cva(
1196
1228
  error: ""
1197
1229
  },
1198
1230
  /**
1199
- * Button size (square dimensions)
1231
+ * Size tier (M3 Expressive 5-tier)
1200
1232
  */
1201
1233
  size: {
1202
- small: "h-8 w-8",
1203
- // 32×32px
1204
- medium: "h-10 w-10",
1205
- // 40×40px (default)
1206
- large: "h-12 w-12"
1207
- // 48×48px
1234
+ xsmall: "h-8",
1235
+ small: "h-10",
1236
+ medium: "h-14",
1237
+ large: "h-24",
1238
+ xlarge: "h-[8.5rem]"
1208
1239
  },
1209
1240
  /**
1210
- * Selected state (for toggle buttons)
1241
+ * Width variant adjusts container width
1211
1242
  */
1212
- selected: {
1213
- true: "",
1214
- false: ""
1243
+ width: {
1244
+ narrow: "",
1245
+ default: "",
1246
+ wide: ""
1215
1247
  },
1216
1248
  /**
1217
- * Disabled state
1249
+ * Shape — base values only; per-size radii set via compoundVariants below.
1250
+ *
1251
+ * round: --ib-radius = half the container height (true circle), set per size.
1252
+ * --ib-radius-press = square corner for that size (set per size).
1253
+ * Morph distance is small (e.g. 28px → 16px for medium), so the
1254
+ * emphasized-decelerate curve from btn-transition produces a smooth,
1255
+ * non-overshooting transition. The old 9999px fallback caused the
1256
+ * spring to overshoot below 0 = sharp-corner flash.
1257
+ * square: --ib-radius = size-tiered MD3 corner, set per size. No press morph.
1218
1258
  */
1219
- isDisabled: {
1220
- true: "pointer-events-none cursor-not-allowed opacity-38",
1221
- false: ""
1259
+ shape: {
1260
+ round: [],
1261
+ square: []
1222
1262
  }
1223
1263
  },
1224
- /**
1225
- * Compound variants - combinations of variant + color + selected
1226
- */
1227
1264
  compoundVariants: [
1228
- // ====================
1229
- // STANDARD VARIANTS
1230
- // ====================
1231
- {
1232
- variant: "standard",
1233
- selected: false,
1234
- className: "text-on-surface-variant"
1235
- },
1265
+ // ══════════════════════════════════════════════════════════════════════
1266
+ // SIZE × WIDTH — container width
1267
+ // ══════════════════════════════════════════════════════════════════════
1268
+ { size: "xsmall", width: "narrow", className: "w-6" },
1269
+ { size: "xsmall", width: "default", className: "w-8" },
1270
+ { size: "xsmall", width: "wide", className: "w-10" },
1271
+ { size: "small", width: "narrow", className: "w-8" },
1272
+ { size: "small", width: "default", className: "w-10" },
1273
+ { size: "small", width: "wide", className: "w-13" },
1274
+ { size: "medium", width: "narrow", className: "w-12" },
1275
+ { size: "medium", width: "default", className: "w-14" },
1276
+ { size: "medium", width: "wide", className: "w-18" },
1277
+ { size: "large", width: "narrow", className: "w-18" },
1278
+ { size: "large", width: "default", className: "w-24" },
1279
+ { size: "large", width: "wide", className: "w-32" },
1280
+ { size: "xlarge", width: "narrow", className: "w-24" },
1281
+ { size: "xlarge", width: "default", className: "w-[8.5rem]" },
1282
+ { size: "xlarge", width: "wide", className: "w-42" },
1283
+ // ══════════════════════════════════════════════════════════════════════
1284
+ // SHAPE × SIZE — corner radii for both round and square shapes
1285
+ // ══════════════════════════════════════════════════════════════════════
1286
+ //
1287
+ // Round rest radius = half container height (true circle).
1288
+ // Using the exact half-height keeps the morph distance small, so the
1289
+ // no-overshoot emphasized-decelerate curve in btn-transition produces a
1290
+ // smooth animation. Using 9999px was the original cause of the sharp-
1291
+ // corner flash (the spring overshoots below 0 before settling).
1292
+ //
1293
+ // xsmall h-8 = 32px → half = 16px = 1rem
1294
+ // small h-10 = 40px → half = 20px = 1.25rem
1295
+ // medium h-14 = 56px → half = 28px = 1.75rem
1296
+ // large h-24 = 96px → half = 48px = 3rem
1297
+ // xlarge h-34 = 136px → half = 68px = 4.25rem
1298
+ //
1299
+ // Round press-morph target = MD3 square corner for that size tier.
1300
+ // Square rest radius = same MD3 corner (no morph).
1301
+ // ── round: rest radius (half height) ──────────────────────────────────
1302
+ { shape: "round", size: "xsmall", className: "[--ib-radius:1rem]" },
1303
+ { shape: "round", size: "small", className: "[--ib-radius:1.25rem]" },
1304
+ { shape: "round", size: "medium", className: "[--ib-radius:1.75rem]" },
1305
+ { shape: "round", size: "large", className: "[--ib-radius:3rem]" },
1306
+ { shape: "round", size: "xlarge", className: "[--ib-radius:4.25rem]" },
1307
+ // ── round: press-morph target (square corner for that size) ───────────
1308
+ { shape: "round", size: "xsmall", className: "[--ib-radius-press:0.75rem]" },
1309
+ { shape: "round", size: "small", className: "[--ib-radius-press:0.75rem]" },
1310
+ { shape: "round", size: "medium", className: "[--ib-radius-press:1rem]" },
1311
+ { shape: "round", size: "large", className: "[--ib-radius-press:1.75rem]" },
1312
+ { shape: "round", size: "xlarge", className: "[--ib-radius-press:1.75rem]" },
1313
+ // ── square: rest radius (MD3 shape scale) ─────────────────────────────
1314
+ // xsmall / small → 12px (0.75rem), medium → 16px (1rem), large / xlarge → 28px (1.75rem)
1315
+ { shape: "square", size: "xsmall", className: "[--ib-radius:0.75rem]" },
1316
+ { shape: "square", size: "small", className: "[--ib-radius:0.75rem]" },
1317
+ { shape: "square", size: "medium", className: "[--ib-radius:1rem]" },
1318
+ { shape: "square", size: "large", className: "[--ib-radius:1.75rem]" },
1319
+ { shape: "square", size: "xlarge", className: "[--ib-radius:1.75rem]" },
1320
+ // ══════════════════════════════════════════════════════════════════════
1321
+ // VARIANT × COLOR — CSS role variable assignments
1322
+ // Only variant × color (design-time decisions); no state variants here.
1323
+ // ══════════════════════════════════════════════════════════════════════
1324
+ // ── STANDARD ──────────────────────────────────────────────────────────
1325
+ // Non-toggle standard: transparent bg, on-surface-variant fg
1326
+ // Selected: primary fg
1327
+ // State layer: on-surface-variant (unselected) / primary (selected)
1236
1328
  {
1237
1329
  variant: "standard",
1238
- selected: true,
1239
- className: "text-primary"
1240
- },
1241
- // ====================
1242
- // FILLED VARIANTS (UNSELECTED)
1243
- // ====================
1244
- {
1245
- variant: "filled",
1246
1330
  color: "primary",
1247
- selected: false,
1248
- className: "bg-primary text-on-primary"
1331
+ className: [
1332
+ "[--ib-bg:transparent]",
1333
+ "[--ib-fg:var(--color-on-surface-variant)]",
1334
+ "[--ib-fg-on:var(--color-primary)]",
1335
+ "[--ib-sl:var(--color-on-surface-variant)]",
1336
+ // toggle-off same as non-toggle
1337
+ "[--ib-bg-off:transparent]",
1338
+ "[--ib-fg-off:var(--color-on-surface-variant)]",
1339
+ // toggle-on: selected
1340
+ "[--ib-bg-on:transparent]"
1341
+ ]
1249
1342
  },
1250
1343
  {
1251
- variant: "filled",
1344
+ variant: "standard",
1252
1345
  color: "secondary",
1253
- selected: false,
1254
- className: "bg-secondary text-on-secondary"
1346
+ className: [
1347
+ "[--ib-bg:transparent]",
1348
+ "[--ib-fg:var(--color-on-surface-variant)]",
1349
+ "[--ib-fg-on:var(--color-secondary)]",
1350
+ "[--ib-sl:var(--color-on-surface-variant)]",
1351
+ "[--ib-bg-off:transparent]",
1352
+ "[--ib-fg-off:var(--color-on-surface-variant)]",
1353
+ "[--ib-bg-on:transparent]"
1354
+ ]
1255
1355
  },
1256
1356
  {
1257
- variant: "filled",
1357
+ variant: "standard",
1258
1358
  color: "tertiary",
1259
- selected: false,
1260
- className: "bg-tertiary text-on-tertiary"
1359
+ className: [
1360
+ "[--ib-bg:transparent]",
1361
+ "[--ib-fg:var(--color-on-surface-variant)]",
1362
+ "[--ib-fg-on:var(--color-tertiary)]",
1363
+ "[--ib-sl:var(--color-on-surface-variant)]",
1364
+ "[--ib-bg-off:transparent]",
1365
+ "[--ib-fg-off:var(--color-on-surface-variant)]",
1366
+ "[--ib-bg-on:transparent]"
1367
+ ]
1261
1368
  },
1262
1369
  {
1263
- variant: "filled",
1370
+ variant: "standard",
1264
1371
  color: "error",
1265
- selected: false,
1266
- className: "bg-error text-on-error"
1372
+ className: [
1373
+ "[--ib-bg:transparent]",
1374
+ "[--ib-fg:var(--color-on-surface-variant)]",
1375
+ "[--ib-fg-on:var(--color-error)]",
1376
+ "[--ib-sl:var(--color-on-surface-variant)]",
1377
+ "[--ib-bg-off:transparent]",
1378
+ "[--ib-fg-off:var(--color-on-surface-variant)]",
1379
+ "[--ib-bg-on:transparent]"
1380
+ ]
1267
1381
  },
1268
- // ====================
1269
- // FILLED VARIANTS (SELECTED - uses container colors)
1270
- // ====================
1382
+ // ── FILLED ────────────────────────────────────────────────────────────
1383
+ // Non-toggle: bg primary / fg on-primary
1384
+ // Toggle off: bg surface-container-highest / fg primary
1385
+ // Toggle on (selected): bg primary / fg on-primary
1386
+ // State layer: on-primary (non-toggle / selected), primary (toggle-off)
1271
1387
  {
1272
1388
  variant: "filled",
1273
1389
  color: "primary",
1274
- selected: true,
1275
- className: "bg-primary-container text-on-primary-container"
1390
+ className: [
1391
+ "[--ib-bg:var(--color-primary)]",
1392
+ "[--ib-fg:var(--color-on-primary)]",
1393
+ "[--ib-sl:var(--color-on-primary)]",
1394
+ "[--ib-bg-off:var(--color-surface-container-highest)]",
1395
+ "[--ib-fg-off:var(--color-primary)]",
1396
+ "[--ib-bg-on:var(--color-primary)]",
1397
+ "[--ib-fg-on:var(--color-on-primary)]"
1398
+ ]
1276
1399
  },
1277
1400
  {
1278
1401
  variant: "filled",
1279
1402
  color: "secondary",
1280
- selected: true,
1281
- className: "bg-secondary-container text-on-secondary-container"
1403
+ className: [
1404
+ "[--ib-bg:var(--color-secondary)]",
1405
+ "[--ib-fg:var(--color-on-secondary)]",
1406
+ "[--ib-sl:var(--color-on-secondary)]",
1407
+ "[--ib-bg-off:var(--color-surface-container-highest)]",
1408
+ "[--ib-fg-off:var(--color-secondary)]",
1409
+ "[--ib-bg-on:var(--color-secondary)]",
1410
+ "[--ib-fg-on:var(--color-on-secondary)]"
1411
+ ]
1282
1412
  },
1283
1413
  {
1284
1414
  variant: "filled",
1285
1415
  color: "tertiary",
1286
- selected: true,
1287
- className: "bg-tertiary-container text-on-tertiary-container"
1416
+ className: [
1417
+ "[--ib-bg:var(--color-tertiary)]",
1418
+ "[--ib-fg:var(--color-on-tertiary)]",
1419
+ "[--ib-sl:var(--color-on-tertiary)]",
1420
+ "[--ib-bg-off:var(--color-surface-container-highest)]",
1421
+ "[--ib-fg-off:var(--color-tertiary)]",
1422
+ "[--ib-bg-on:var(--color-tertiary)]",
1423
+ "[--ib-fg-on:var(--color-on-tertiary)]"
1424
+ ]
1288
1425
  },
1289
1426
  {
1290
1427
  variant: "filled",
1291
1428
  color: "error",
1292
- selected: true,
1293
- className: "bg-error-container text-on-error-container"
1429
+ className: [
1430
+ "[--ib-bg:var(--color-error)]",
1431
+ "[--ib-fg:var(--color-on-error)]",
1432
+ "[--ib-sl:var(--color-on-error)]",
1433
+ "[--ib-bg-off:var(--color-surface-container-highest)]",
1434
+ "[--ib-fg-off:var(--color-error)]",
1435
+ "[--ib-bg-on:var(--color-error)]",
1436
+ "[--ib-fg-on:var(--color-on-error)]"
1437
+ ]
1294
1438
  },
1295
- // ====================
1296
- // TONAL VARIANTS (UNSELECTED)
1297
- // ====================
1439
+ // ── TONAL ─────────────────────────────────────────────────────────────
1440
+ // Non-toggle: bg secondary-container / fg on-secondary-container
1441
+ // Toggle off: bg surface-container-highest / fg on-surface-variant
1442
+ // Toggle on (selected): bg secondary-container / fg on-secondary-container
1298
1443
  {
1299
1444
  variant: "tonal",
1300
1445
  color: "primary",
1301
- selected: false,
1302
- className: "bg-secondary-container text-on-secondary-container"
1446
+ className: [
1447
+ "[--ib-bg:var(--color-secondary-container)]",
1448
+ "[--ib-fg:var(--color-on-secondary-container)]",
1449
+ "[--ib-sl:var(--color-on-secondary-container)]",
1450
+ "[--ib-bg-off:var(--color-surface-container-highest)]",
1451
+ "[--ib-fg-off:var(--color-on-surface-variant)]",
1452
+ "[--ib-bg-on:var(--color-secondary-container)]",
1453
+ "[--ib-fg-on:var(--color-on-secondary-container)]"
1454
+ ]
1303
1455
  },
1304
1456
  {
1305
1457
  variant: "tonal",
1306
1458
  color: "secondary",
1307
- selected: false,
1308
- className: "bg-secondary-container text-on-secondary-container"
1459
+ className: [
1460
+ "[--ib-bg:var(--color-secondary-container)]",
1461
+ "[--ib-fg:var(--color-on-secondary-container)]",
1462
+ "[--ib-sl:var(--color-on-secondary-container)]",
1463
+ "[--ib-bg-off:var(--color-surface-container-highest)]",
1464
+ "[--ib-fg-off:var(--color-on-surface-variant)]",
1465
+ "[--ib-bg-on:var(--color-secondary-container)]",
1466
+ "[--ib-fg-on:var(--color-on-secondary-container)]"
1467
+ ]
1309
1468
  },
1310
1469
  {
1311
1470
  variant: "tonal",
1312
1471
  color: "tertiary",
1313
- selected: false,
1314
- className: "bg-tertiary-container text-on-tertiary-container"
1472
+ className: [
1473
+ "[--ib-bg:var(--color-tertiary-container)]",
1474
+ "[--ib-fg:var(--color-on-tertiary-container)]",
1475
+ "[--ib-sl:var(--color-on-tertiary-container)]",
1476
+ "[--ib-bg-off:var(--color-surface-container-highest)]",
1477
+ "[--ib-fg-off:var(--color-on-surface-variant)]",
1478
+ "[--ib-bg-on:var(--color-tertiary-container)]",
1479
+ "[--ib-fg-on:var(--color-on-tertiary-container)]"
1480
+ ]
1315
1481
  },
1316
1482
  {
1317
1483
  variant: "tonal",
1318
1484
  color: "error",
1319
- selected: false,
1320
- className: "bg-error-container text-on-error-container"
1485
+ className: [
1486
+ "[--ib-bg:var(--color-error-container)]",
1487
+ "[--ib-fg:var(--color-on-error-container)]",
1488
+ "[--ib-sl:var(--color-on-error-container)]",
1489
+ "[--ib-bg-off:var(--color-surface-container-highest)]",
1490
+ "[--ib-fg-off:var(--color-on-surface-variant)]",
1491
+ "[--ib-bg-on:var(--color-error-container)]",
1492
+ "[--ib-fg-on:var(--color-on-error-container)]"
1493
+ ]
1321
1494
  },
1322
- // ====================
1323
- // TONAL VARIANTS (SELECTED - uses tertiary container)
1324
- // ====================
1495
+ // ── OUTLINED ──────────────────────────────────────────────────────────
1496
+ // Non-toggle: transparent bg, border-outline, on-surface-variant fg
1497
+ // Toggle off: same as non-toggle
1498
+ // Toggle on (selected): inverse-surface bg, inverse-on-surface fg, no border
1499
+ // Disabled: border becomes on-surface/12 (set via Tailwind utility on root)
1325
1500
  {
1326
- variant: "tonal",
1327
- selected: true,
1328
- className: "bg-tertiary-container text-on-tertiary-container"
1501
+ variant: "outlined",
1502
+ color: "primary",
1503
+ className: [
1504
+ "[--ib-bg:transparent]",
1505
+ "[--ib-fg:var(--color-on-surface-variant)]",
1506
+ "[--ib-sl:var(--color-on-surface-variant)]",
1507
+ "[--ib-border:var(--color-outline)]",
1508
+ "[--ib-bg-off:transparent]",
1509
+ "[--ib-fg-off:var(--color-on-surface-variant)]",
1510
+ "[--ib-bg-on:var(--color-inverse-surface)]",
1511
+ "[--ib-fg-on:var(--color-inverse-on-surface)]",
1512
+ // Disabled outlined border
1513
+ "data-[disabled]:border-on-surface/12"
1514
+ ]
1329
1515
  },
1330
- // ====================
1331
- // OUTLINED VARIANTS (UNSELECTED)
1332
- // ====================
1333
1516
  {
1334
1517
  variant: "outlined",
1335
- selected: false,
1336
- className: "text-on-surface-variant"
1518
+ color: "secondary",
1519
+ className: [
1520
+ "[--ib-bg:transparent]",
1521
+ "[--ib-fg:var(--color-on-surface-variant)]",
1522
+ "[--ib-sl:var(--color-on-surface-variant)]",
1523
+ "[--ib-border:var(--color-outline)]",
1524
+ "[--ib-bg-off:transparent]",
1525
+ "[--ib-fg-off:var(--color-on-surface-variant)]",
1526
+ "[--ib-bg-on:var(--color-inverse-surface)]",
1527
+ "[--ib-fg-on:var(--color-inverse-on-surface)]",
1528
+ "data-[disabled]:border-on-surface/12"
1529
+ ]
1337
1530
  },
1338
- // ====================
1339
- // OUTLINED VARIANTS (SELECTED - uses inverse colors)
1340
- // ====================
1341
1531
  {
1342
1532
  variant: "outlined",
1343
- selected: true,
1344
- className: "bg-inverse-surface text-inverse-on-surface border-transparent"
1533
+ color: "tertiary",
1534
+ className: [
1535
+ "[--ib-bg:transparent]",
1536
+ "[--ib-fg:var(--color-on-surface-variant)]",
1537
+ "[--ib-sl:var(--color-on-surface-variant)]",
1538
+ "[--ib-border:var(--color-outline)]",
1539
+ "[--ib-bg-off:transparent]",
1540
+ "[--ib-fg-off:var(--color-on-surface-variant)]",
1541
+ "[--ib-bg-on:var(--color-inverse-surface)]",
1542
+ "[--ib-fg-on:var(--color-inverse-on-surface)]",
1543
+ "data-[disabled]:border-on-surface/12"
1544
+ ]
1545
+ },
1546
+ {
1547
+ variant: "outlined",
1548
+ color: "error",
1549
+ className: [
1550
+ "[--ib-bg:transparent]",
1551
+ "[--ib-fg:var(--color-on-surface-variant)]",
1552
+ "[--ib-sl:var(--color-on-surface-variant)]",
1553
+ "[--ib-border:var(--color-outline)]",
1554
+ "[--ib-bg-off:transparent]",
1555
+ "[--ib-fg-off:var(--color-on-surface-variant)]",
1556
+ "[--ib-bg-on:var(--color-inverse-surface)]",
1557
+ "[--ib-fg-on:var(--color-inverse-on-surface)]",
1558
+ "data-[disabled]:border-on-surface/12"
1559
+ ]
1345
1560
  }
1346
1561
  ],
1347
- /**
1348
- * Default variants
1349
- */
1350
1562
  defaultVariants: {
1351
1563
  variant: "standard",
1352
1564
  color: "primary",
1353
1565
  size: "medium",
1354
- selected: false,
1355
- isDisabled: false
1566
+ width: "default",
1567
+ shape: "round"
1568
+ }
1569
+ }
1570
+ );
1571
+ var iconButtonStateLayerVariants = cva([
1572
+ "absolute inset-0 rounded-[inherit] pointer-events-none opacity-0",
1573
+ "bg-[var(--ib-sl,currentColor)]",
1574
+ // Effects transition (opacity — no spatial overshoot)
1575
+ "transition-opacity duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
1576
+ // Interaction opacities (MD3: hover 8%, focus/pressed 10%)
1577
+ "group-data-[hovered]/icon-button:opacity-8",
1578
+ "group-data-[focus-visible]/icon-button:opacity-10",
1579
+ "group-data-[pressed]/icon-button:opacity-10",
1580
+ // No state layer when disabled
1581
+ "group-data-[disabled]/icon-button:hidden"
1582
+ ]);
1583
+ var iconButtonIconVariants = cva(
1584
+ [
1585
+ "relative z-10 inline-flex shrink-0 items-center justify-center",
1586
+ "transition-colors duration-spring-standard-fast-effects ease-spring-standard-fast-effects"
1587
+ ],
1588
+ {
1589
+ variants: {
1590
+ size: {
1591
+ xsmall: "size-5",
1592
+ // 20dp
1593
+ small: "size-6",
1594
+ // 24dp
1595
+ medium: "size-6",
1596
+ // 24dp
1597
+ large: "size-8",
1598
+ // 32dp
1599
+ xlarge: "size-10"
1600
+ // 40dp
1601
+ }
1602
+ },
1603
+ defaultVariants: {
1604
+ size: "medium"
1356
1605
  }
1357
1606
  }
1358
1607
  );
1359
1608
  var IconButton = forwardRef(
1360
1609
  ({
1361
- // Variant props (CVA)
1610
+ // Variant props (CVA / design-time)
1362
1611
  variant = "standard",
1363
1612
  color = "primary",
1364
1613
  size = "medium",
1614
+ width = "default",
1615
+ shape = "round",
1365
1616
  // IconButton specific props
1366
1617
  children,
1618
+ selectedIcon,
1367
1619
  value,
1368
1620
  selected,
1369
1621
  disableRipple = false,
1370
1622
  className,
1371
1623
  // React Aria props
1372
- isDisabled: propIsDisabled = false,
1624
+ isDisabled = false,
1373
1625
  onPress,
1374
1626
  onMouseDown,
1375
1627
  "aria-label": ariaLabel,
@@ -1388,7 +1640,8 @@ var IconButton = forwardRef(
1388
1640
  console.warn("[IconButton] IconButton should have an icon as children.");
1389
1641
  }
1390
1642
  }
1391
- const isDisabled = propIsDisabled;
1643
+ const isToggle = selected !== void 0;
1644
+ const isSelected = isToggle ? selected ?? false : false;
1392
1645
  const { onMouseDown: handleRipple, ripples } = useRipple({
1393
1646
  disabled: isDisabled || disableRipple
1394
1647
  });
@@ -1406,32 +1659,37 @@ var IconButton = forwardRef(
1406
1659
  ...getConnectedRadiusClasses(groupCtx, value),
1407
1660
  groupCtx.enforceMinWidth ? "min-w-12" : ""
1408
1661
  ] : [];
1662
+ const iconNode = isToggle && isSelected && selectedIcon ? selectedIcon : children;
1409
1663
  return /* @__PURE__ */ jsxs(
1410
1664
  IconButtonHeadless,
1411
1665
  {
1412
1666
  ref,
1413
1667
  className: cn(
1414
- // CVA variants includes btn-transition for asymmetric border-radius easing
1415
- iconButtonVariants({ variant, color, size, selected: selected ?? false, isDisabled }),
1416
- // Asymmetric border-radius easing: expressive when selected, decelerate when not.
1417
- // btn-transition-selected overrides --_btn-radius-easing to the bouncy spring while
1418
- // the button is gaining the pill shape; removal restores decelerate for the return
1419
- // path, preventing the overshoot-to-0px sharp-corner flash.
1668
+ // Root CVA — sets CSS role variables, dimensions, shape, transitions
1669
+ iconButtonRootVariants({ variant, color, size, width, shape }),
1670
+ // Group scope for child slot selectors
1671
+ "group/icon-button",
1672
+ // ButtonGroup asymmetric border-radius easing (connected selection morph)
1420
1673
  isGroupSelected ? "btn-transition-selected" : "",
1421
1674
  ...connectedClasses,
1422
- // User custom classes
1675
+ // Consumer custom classes
1423
1676
  className
1424
1677
  ),
1425
1678
  "aria-label": ariaLabel,
1679
+ isSelected,
1680
+ isToggle,
1426
1681
  "data-variant": variant,
1427
1682
  "data-color": color,
1683
+ "data-size": size,
1684
+ "data-width": width,
1685
+ "data-shape": shape,
1428
1686
  "data-group-selected": isGroupSelected ? "" : void 0,
1429
- ...selected !== void 0 && { selected },
1430
1687
  ...title && { title },
1431
1688
  ...mergedPropsValue,
1432
1689
  children: [
1690
+ /* @__PURE__ */ jsx("span", { className: iconButtonStateLayerVariants(), "aria-hidden": "true", "data-state-layer": "" }),
1433
1691
  ripples,
1434
- /* @__PURE__ */ jsx("span", { className: "relative z-10 inline-flex shrink-0", children })
1692
+ /* @__PURE__ */ jsx("span", { className: iconButtonIconVariants({ size }), "data-icon-slot": "", "aria-hidden": "true", children: iconNode })
1435
1693
  ]
1436
1694
  }
1437
1695
  );