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