@sableui/react 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +148 -0
  3. package/CONTRIBUTING.md +1 -1
  4. package/THIRD_PARTY_NOTICES.md +11 -0
  5. package/bin/sable-lint.mjs +2 -1
  6. package/bin/sable.mjs +3 -2
  7. package/dist/{_contract-Cn3lUpih.d.ts → _contract-C2M1SjBf.d.ts} +20 -2
  8. package/dist/blocks/index.js +19 -51
  9. package/dist/{chunk-RCLKXF2N.js → chunk-7QSDYBXO.js} +610 -30
  10. package/dist/{chunk-GU3T7SBA.js → chunk-EDXLM7YL.js} +1 -1
  11. package/dist/chunk-FKZXAFIY.js +12 -0
  12. package/dist/{chunk-OQACYT2D.js → chunk-NETUFI34.js} +23 -1
  13. package/dist/{chunk-ZBZMKOO7.js → chunk-VVW4UCDI.js} +3 -3
  14. package/dist/foundations/index.js +6 -0
  15. package/dist/index.d.ts +127 -11
  16. package/dist/index.js +23 -13
  17. package/dist/ios/index.d.ts +6 -1
  18. package/dist/ios/index.js +4 -3
  19. package/dist/ios/styles.css +9 -0
  20. package/dist/patterns/index.js +22 -28
  21. package/dist/presets/index.d.ts +108 -5
  22. package/dist/presets/index.js +239 -6
  23. package/dist/styles.css +50 -0
  24. package/dist/{theme-provider-CGeiusgu.d.ts → theme-provider-kQszHYji.d.ts} +1 -1
  25. package/dist/themes/atelier/atelier.css +31 -3
  26. package/dist/themes/atelier/index.d.ts +1 -1
  27. package/dist/themes/ember/ember.css +38 -7
  28. package/dist/themes/ember/index.d.ts +1 -1
  29. package/dist/themes/index.d.ts +2 -2
  30. package/dist/themes/meridian/index.d.ts +1 -1
  31. package/dist/themes/meridian/meridian.css +33 -4
  32. package/dist/themes/poppy/index.d.ts +1 -1
  33. package/dist/themes/poppy/poppy.css +28 -0
  34. package/dist/themes/prism/index.d.ts +1 -1
  35. package/dist/themes/prism/prism.css +26 -0
  36. package/dist/themes/sable/index.d.ts +1 -1
  37. package/dist/themes/sable/sable.css +24 -11
  38. package/dist/themes/verdant/index.d.ts +1 -1
  39. package/dist/themes/verdant/verdant.css +34 -4
  40. package/llms/blocks/danger-zone-block.md +1 -1
  41. package/llms/blocks/event-card-block.md +1 -1
  42. package/llms/blocks/feedback-form-block.md +1 -1
  43. package/llms/blocks/invite-team-block.md +1 -1
  44. package/llms/blocks/media-player-block.md +3 -16
  45. package/llms/blocks/notification-list-block.md +3 -3
  46. package/llms/blocks/product-card-block.md +3 -17
  47. package/llms/blocks/profile-card-block.md +4 -17
  48. package/llms/blocks/profile-form-block.md +2 -2
  49. package/llms/blocks/team-members-block.md +1 -1
  50. package/llms/blocks/testimonial-block.md +1 -1
  51. package/llms/components/avatar.md +13 -1
  52. package/llms/components/card.md +1 -1
  53. package/llms/components/gradient-art.md +80 -0
  54. package/llms/components/sheet.md +1 -1
  55. package/llms/foundations/accessibility.md +1 -0
  56. package/llms/patterns/auth-page.md +5 -19
  57. package/llms/patterns/brand-landing-page.md +1 -1
  58. package/llms/patterns/chat-panel.md +1 -1
  59. package/llms/patterns/dashboard-page.md +4 -4
  60. package/llms/patterns/feed-page.md +4 -4
  61. package/llms/patterns/landing-page.md +1 -1
  62. package/llms/patterns/list-detail-page.md +3 -2
  63. package/llms/patterns/settings-page.md +3 -3
  64. package/llms/reference/token-intent.md +9 -1
  65. package/llms/tasks/auditing-an-app.md +6 -0
  66. package/llms/tasks/building-an-ios-prototype.md +3 -1
  67. package/llms/tasks/converting-an-app.md +1 -1
  68. package/llms/tasks/creating-a-preset.md +24 -0
  69. package/llms/tasks/reviewing-a-design.md +9 -0
  70. package/llms/tasks/using-presets.md +6 -1
  71. package/llms-full.txt +201 -104
  72. package/llms.txt +3 -2
  73. package/package.json +9 -7
  74. package/registry.json +283 -30
  75. package/scripts/design-presets.mjs +27 -4
  76. package/scripts/grade.mjs +2 -1
  77. package/scripts/label-contrast.mjs +174 -0
  78. package/scripts/lint-rules.mjs +39 -2
  79. package/skills/sable/SKILL.md +3 -1
  80. package/targets/ios.json +18 -2
  81. package/third-party/paper-shaders-LICENSE.txt +201 -0
  82. package/third-party/paper-shaders-NOTICE.txt +5 -0
@@ -1,3 +1,4 @@
1
+ import { StackedActionsContext, CardDepthContext, usePresentation, PresentationProvider } from './chunk-FKZXAFIY.js';
1
2
  import { cn } from './chunk-SRQWQUTW.js';
2
3
  import { Icon } from './chunk-QJ6FWTUG.js';
3
4
  import { createContext, useMemo, useState, useRef, useLayoutEffect, useEffect, useSyncExternalStore, useCallback, useContext, isValidElement, Children, useId, cloneElement } from 'react';
@@ -11,6 +12,7 @@ import { Input as Input$1 } from '@base-ui/react/input';
11
12
  import { Field as Field$1 } from '@base-ui/react/field';
12
13
  import { Tooltip as Tooltip$1 } from '@base-ui/react/tooltip';
13
14
  import { Switch as Switch$1 } from '@base-ui/react/switch';
15
+ import { grainGradientMeta, staticMeshGradientMeta, getShaderNoiseTexture, grainGradientFragmentShader, staticMeshGradientFragmentShader } from '@paper-design/shaders';
14
16
  import { Avatar as Avatar$1 } from '@base-ui/react/avatar';
15
17
  import { Progress as Progress$1 } from '@base-ui/react/progress';
16
18
  import { Select as Select$1 } from '@base-ui/react/select';
@@ -462,7 +464,8 @@ function Button({
462
464
  }) {
463
465
  const tier = useTypeTier();
464
466
  const isNativeButton = render === void 0;
465
- const resolvedSize = size ?? "md";
467
+ const stacked = useContext(StackedActionsContext) && !String(size ?? "md").startsWith("icon");
468
+ const resolvedSize = stacked ? "cta" : size ?? "md";
466
469
  const hasLabel = children != null && children !== false;
467
470
  const spinner = /* @__PURE__ */ jsx(Spinner, { className: "size-(--button-icon)", thickness: 2 });
468
471
  const start = loading ? spinner : iconStart;
@@ -486,7 +489,8 @@ function Button({
486
489
  props: {
487
490
  ...props,
488
491
  className: cn(
489
- buttonVariants({ variant, size }),
492
+ buttonVariants({ variant, size: resolvedSize }),
493
+ stacked ? "w-full" : null,
490
494
  tier.action,
491
495
  optical,
492
496
  // Busy is not disabled. Loading sets `disabled` so the action cannot
@@ -793,7 +797,9 @@ function Card({
793
797
  ref,
794
798
  ...props
795
799
  }) {
796
- return useRender({
800
+ const depth = useContext(CardDepthContext);
801
+ const bleed = usePresentation() === "sheet" && variant === "flat" && depth === 0;
802
+ const element = useRender({
797
803
  render: render ?? /* @__PURE__ */ jsx("div", {}),
798
804
  ref,
799
805
  props: {
@@ -818,10 +824,15 @@ function Card({
818
824
  INTERACTIVE_HOVER[variant],
819
825
  className
820
826
  ),
821
- style: { "--card-p": DENSITY_PAD[density], ...style }
827
+ style: {
828
+ "--card-p": DENSITY_PAD[density],
829
+ ...bleed ? { margin: "calc(var(--card-p) * -1)", width: "calc(100% + var(--card-p) * 2)", maxWidth: "none" } : null,
830
+ ...style
831
+ }
822
832
  },
823
833
  defaultTagName: "div"
824
834
  });
835
+ return /* @__PURE__ */ jsx(CardDepthContext.Provider, { value: depth + 1, children: element });
825
836
  }
826
837
  Card.displayName = "Card";
827
838
  function CardHeader({
@@ -924,20 +935,27 @@ function CardList({
924
935
  );
925
936
  }
926
937
  function CardFooter({
938
+ actions = false,
927
939
  className,
940
+ children,
928
941
  ...props
929
942
  }) {
943
+ const depth = useContext(CardDepthContext);
944
+ const stacked = usePresentation() === "sheet" && actions && depth <= 1;
930
945
  return /* @__PURE__ */ jsx(
931
946
  "div",
932
947
  {
933
948
  "data-slot": "card-footer",
949
+ "data-actions": actions ? stacked ? "stack" : "row" : void 0,
934
950
  className: cn(
935
951
  "flex items-center gap-2 p-[var(--card-p,--spacing(6))] pt-0",
936
952
  "[[data-slot=card-header]+&]:pt-[var(--card-p,--spacing(6))]",
937
953
  "[:is([data-slot=card-list],[data-slot=card-content][data-flush])+&]:border-t [:is([data-slot=card-list],[data-slot=card-content][data-flush])+&]:border-border [:is([data-slot=card-list],[data-slot=card-content][data-flush])+&]:pt-[var(--card-p,--spacing(6))]",
938
- className
954
+ className,
955
+ stacked && "mt-auto flex-col-reverse items-stretch justify-start"
939
956
  ),
940
- ...props
957
+ ...props,
958
+ children: stacked ? /* @__PURE__ */ jsx(StackedActionsContext.Provider, { value: true, children }) : children
941
959
  }
942
960
  );
943
961
  }
@@ -1072,8 +1090,8 @@ var cardMeta = {
1072
1090
  },
1073
1091
  {
1074
1092
  name: "CardFooter",
1075
- type: 'ComponentPropsWithRef<"div">',
1076
- description: "Bottom row for actions/meta. Padding follows the density knob."
1093
+ type: 'ComponentPropsWithRef<"div"> & { actions?: boolean }',
1094
+ description: "Bottom row for actions/meta. Padding follows the density knob. `actions` marks it as the card's action row (buttons only): in a sheet (IosSheet, a bottom SheetContent) it becomes a full-width stack pinned to the bottom, primary on top; on a page nothing changes."
1077
1095
  }
1078
1096
  ],
1079
1097
  variants: {
@@ -1325,16 +1343,18 @@ var MATRIX = {
1325
1343
  // raw tone clears 4.5:1 on the page, but on its own 15% wash it did not
1326
1344
  // (light info 4.2:1, dark edition 3.6:1, most of Verdant's tones ~4.1:1).
1327
1345
  // Mixing toward --foreground moves it AWAY from the wash in both modes and
1328
- // every theme, and keeps the hue.
1346
+ // every theme, and keeps the hue. Status tones mix from their -text token,
1347
+ // not the fill: dark fills now sit low enough for a white label, and ink
1348
+ // mixed from them fell under 4.5:1 (Meridian dark destructive, 3.8:1).
1329
1349
  soft: {
1330
1350
  neutral: "bg-secondary text-secondary-foreground",
1331
1351
  standard: "bg-tone-standard/15 text-[color-mix(in_oklch,var(--tone-standard)_75%,var(--foreground))]",
1332
1352
  collectible: "bg-tone-collectible/15 text-[color-mix(in_oklch,var(--tone-collectible)_75%,var(--foreground))]",
1333
1353
  edition: "bg-tone-edition/15 text-[color-mix(in_oklch,var(--tone-edition)_75%,var(--foreground))]",
1334
- info: "bg-tone-info/15 text-[color-mix(in_oklch,var(--tone-info)_75%,var(--foreground))]",
1335
- success: "bg-success/15 text-[color-mix(in_oklch,var(--success)_75%,var(--foreground))]",
1336
- warning: "bg-tone-warning/15 text-[color-mix(in_oklch,var(--tone-warning)_75%,var(--foreground))]",
1337
- destructive: "bg-destructive/15 text-[color-mix(in_oklch,var(--destructive)_75%,var(--foreground))]"
1354
+ info: "bg-tone-info/15 text-[color-mix(in_oklch,var(--tone-info-text)_75%,var(--foreground))]",
1355
+ success: "bg-success/15 text-[color-mix(in_oklch,var(--success-text)_75%,var(--foreground))]",
1356
+ warning: "bg-tone-warning/15 text-[color-mix(in_oklch,var(--tone-warning-text)_75%,var(--foreground))]",
1357
+ destructive: "bg-destructive/15 text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))]"
1338
1358
  },
1339
1359
  outline: {
1340
1360
  neutral: "border border-border text-foreground",
@@ -1639,16 +1659,16 @@ function usePortalThemeScope() {
1639
1659
  const [portal, setPortal] = useState(null);
1640
1660
  const [probed, setProbed] = useState(null);
1641
1661
  const probeRef = useRef(null);
1642
- const pending = portal !== null && probed !== portal;
1662
+ const pending2 = portal !== null && probed !== portal;
1643
1663
  useIsomorphicLayoutEffect(() => {
1644
- if (!pending || !portal) return;
1664
+ if (!pending2 || !portal) return;
1645
1665
  const probe = probeRef.current;
1646
1666
  if (probe) applyPortalThemeScope(portal, resolvePortalThemeScope(probe));
1647
1667
  setProbed(portal);
1648
- }, [pending, portal]);
1668
+ }, [pending2, portal]);
1649
1669
  return {
1650
1670
  portalRef: setPortal,
1651
- probe: pending ? /* @__PURE__ */ jsx("span", { ref: probeRef, hidden: true, "data-sable-portal-probe": "" }) : null
1671
+ probe: pending2 ? /* @__PURE__ */ jsx("span", { ref: probeRef, hidden: true, "data-sable-portal-probe": "" }) : null
1652
1672
  };
1653
1673
  }
1654
1674
  function withPortalThemeScope(Portal) {
@@ -1720,7 +1740,7 @@ function FullscreenContent({
1720
1740
  ),
1721
1741
  ...props,
1722
1742
  children: [
1723
- children,
1743
+ /* @__PURE__ */ jsx(PresentationProvider, { value: "page", children }),
1724
1744
  showCloseButton ? (
1725
1745
  // The floating Button: Sable's control over content. Its own
1726
1746
  // translucent popover chip and border keep it legible over bright
@@ -1778,7 +1798,7 @@ function CardContent2({
1778
1798
  ),
1779
1799
  ...props,
1780
1800
  children: [
1781
- scroll ? /* @__PURE__ */ jsx("div", { className: "-m-2 min-h-0 overflow-y-auto overscroll-contain p-2 scrollbar-quiet", children }) : children,
1801
+ /* @__PURE__ */ jsx(PresentationProvider, { value: "page", children: scroll ? /* @__PURE__ */ jsx("div", { className: "-m-2 min-h-0 overflow-y-auto overscroll-contain p-2 scrollbar-quiet", children }) : children }),
1782
1802
  showCloseButton ? /* @__PURE__ */ jsx(
1783
1803
  Dialog$1.Close,
1784
1804
  {
@@ -3027,6 +3047,539 @@ var switchMeta = {
3027
3047
  }
3028
3048
  ]
3029
3049
  };
3050
+ var VERTEX = `#version 300 es
3051
+ precision mediump float;
3052
+
3053
+ layout(location = 0) in vec4 a_position;
3054
+
3055
+ uniform vec2 u_resolution;
3056
+ uniform float u_pixelRatio;
3057
+ uniform float u_originX;
3058
+ uniform float u_originY;
3059
+ uniform float u_worldWidth;
3060
+ uniform float u_worldHeight;
3061
+ uniform float u_fit;
3062
+ uniform float u_scale;
3063
+ uniform float u_rotation;
3064
+ uniform float u_offsetX;
3065
+ uniform float u_offsetY;
3066
+
3067
+ out vec2 v_objectUV;
3068
+ out vec2 v_objectBoxSize;
3069
+ out vec2 v_patternUV;
3070
+ out vec2 v_patternBoxSize;
3071
+
3072
+ vec3 getBoxSize(float boxRatio, vec2 givenBoxSize) {
3073
+ vec2 box = vec2(0.);
3074
+ box.x = boxRatio * min(givenBoxSize.x / boxRatio, givenBoxSize.y);
3075
+ float noFitBoxWidth = box.x;
3076
+ if (u_fit == 1.) {
3077
+ box.x = boxRatio * min(u_resolution.x / boxRatio, u_resolution.y);
3078
+ } else if (u_fit == 2.) {
3079
+ box.x = boxRatio * max(u_resolution.x / boxRatio, u_resolution.y);
3080
+ }
3081
+ box.y = box.x / boxRatio;
3082
+ return vec3(box, noFitBoxWidth);
3083
+ }
3084
+
3085
+ void main() {
3086
+ gl_Position = a_position;
3087
+
3088
+ vec2 uv = gl_Position.xy * .5;
3089
+ vec2 boxOrigin = vec2(.5 - u_originX, u_originY - .5);
3090
+ vec2 givenBoxSize = vec2(u_worldWidth, u_worldHeight);
3091
+ givenBoxSize = max(givenBoxSize, vec2(1.)) * u_pixelRatio;
3092
+ float r = u_rotation * 3.14159265358979323846 / 180.;
3093
+ mat2 graphicRotation = mat2(cos(r), sin(r), -sin(r), cos(r));
3094
+ vec2 graphicOffset = vec2(-u_offsetX, u_offsetY);
3095
+
3096
+ vec2 fixedRatioBoxGivenSize = vec2(
3097
+ (u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x,
3098
+ (u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y
3099
+ );
3100
+ v_objectBoxSize = getBoxSize(1., fixedRatioBoxGivenSize).xy;
3101
+ vec2 objectWorldScale = u_resolution.xy / v_objectBoxSize;
3102
+ v_objectUV = uv;
3103
+ v_objectUV *= objectWorldScale;
3104
+ v_objectUV += boxOrigin * (objectWorldScale - 1.);
3105
+ v_objectUV += graphicOffset;
3106
+ v_objectUV /= u_scale;
3107
+ v_objectUV = graphicRotation * v_objectUV;
3108
+
3109
+ vec2 patternBoxGivenSize = vec2(
3110
+ (u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x,
3111
+ (u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y
3112
+ );
3113
+ float patternBoxRatio = patternBoxGivenSize.x / patternBoxGivenSize.y;
3114
+ vec3 boxSizeData = getBoxSize(patternBoxRatio, patternBoxGivenSize);
3115
+ v_patternBoxSize = boxSizeData.xy;
3116
+ float patternBoxNoFitBoxWidth = boxSizeData.z;
3117
+ vec2 patternBoxScale = u_resolution.xy / v_patternBoxSize;
3118
+ v_patternUV = uv;
3119
+ v_patternUV += graphicOffset / patternBoxScale;
3120
+ v_patternUV += boxOrigin;
3121
+ v_patternUV -= boxOrigin / patternBoxScale;
3122
+ v_patternUV *= u_resolution.xy;
3123
+ v_patternUV /= u_pixelRatio;
3124
+ if (u_fit > 0.) {
3125
+ v_patternUV *= (patternBoxNoFitBoxWidth / v_patternBoxSize.x);
3126
+ }
3127
+ v_patternUV /= u_scale;
3128
+ v_patternUV = graphicRotation * v_patternUV;
3129
+ v_patternUV += boxOrigin / patternBoxScale;
3130
+ v_patternUV -= boxOrigin;
3131
+ v_patternUV *= .01;
3132
+ }
3133
+ `;
3134
+ var FRAGMENTS = { mesh: staticMeshGradientFragmentShader, grain: grainGradientFragmentShader };
3135
+ var MAX_COLORS = { mesh: staticMeshGradientMeta.maxColorCount, grain: grainGradientMeta.maxColorCount };
3136
+ var GRAIN_CORNERS = 4;
3137
+ var state;
3138
+ var noiseImage = null;
3139
+ var listeners = /* @__PURE__ */ new Set();
3140
+ function onArtInvalidate(listener) {
3141
+ listeners.add(listener);
3142
+ return () => listeners.delete(listener);
3143
+ }
3144
+ var invalidate = () => {
3145
+ for (const listener of listeners) listener();
3146
+ };
3147
+ function compile(gl, type, source) {
3148
+ const shader = gl.createShader(type);
3149
+ if (!shader) return null;
3150
+ gl.shaderSource(shader, source);
3151
+ gl.compileShader(shader);
3152
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
3153
+ console.error("GradientArt: shader failed to compile", gl.getShaderInfoLog(shader));
3154
+ gl.deleteShader(shader);
3155
+ return null;
3156
+ }
3157
+ return shader;
3158
+ }
3159
+ function programFor(s, variant) {
3160
+ const cached = s.programs[variant];
3161
+ if (cached !== void 0) return cached;
3162
+ s.programs[variant] = null;
3163
+ const { gl } = s;
3164
+ const vertex = compile(gl, gl.VERTEX_SHADER, VERTEX);
3165
+ const fragment = compile(gl, gl.FRAGMENT_SHADER, FRAGMENTS[variant]);
3166
+ if (!vertex || !fragment) return null;
3167
+ const program = gl.createProgram();
3168
+ if (!program) return null;
3169
+ gl.attachShader(program, vertex);
3170
+ gl.attachShader(program, fragment);
3171
+ gl.linkProgram(program);
3172
+ gl.deleteShader(vertex);
3173
+ gl.deleteShader(fragment);
3174
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
3175
+ console.error("GradientArt: shader failed to link", gl.getProgramInfoLog(program));
3176
+ return null;
3177
+ }
3178
+ const entry = { program, uniforms: /* @__PURE__ */ new Map() };
3179
+ s.programs[variant] = entry;
3180
+ return entry;
3181
+ }
3182
+ function setup(s) {
3183
+ const { gl } = s;
3184
+ const buffer = gl.createBuffer();
3185
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
3186
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]), gl.STATIC_DRAW);
3187
+ gl.enableVertexAttribArray(0);
3188
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
3189
+ s.programs = {};
3190
+ s.noise = null;
3191
+ if (noiseImage?.complete && noiseImage.naturalWidth) uploadNoise(s, noiseImage);
3192
+ }
3193
+ function uploadNoise(s, image) {
3194
+ const { gl } = s;
3195
+ const texture = gl.createTexture();
3196
+ gl.activeTexture(gl.TEXTURE0);
3197
+ gl.bindTexture(gl.TEXTURE_2D, texture);
3198
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
3199
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
3200
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
3201
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
3202
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
3203
+ s.noise = texture;
3204
+ }
3205
+ function getState() {
3206
+ if (state !== void 0) return state;
3207
+ if (typeof document === "undefined") return null;
3208
+ const canvas = document.createElement("canvas");
3209
+ const gl = canvas.getContext("webgl2", { alpha: true, premultipliedAlpha: true, antialias: false, preserveDrawingBuffer: true });
3210
+ if (!gl) {
3211
+ state = null;
3212
+ return state;
3213
+ }
3214
+ const s = { canvas, gl, programs: {}, noise: null, lost: false };
3215
+ setup(s);
3216
+ canvas.addEventListener("webglcontextlost", (event) => {
3217
+ event.preventDefault();
3218
+ s.lost = true;
3219
+ });
3220
+ canvas.addEventListener("webglcontextrestored", () => {
3221
+ s.lost = false;
3222
+ setup(s);
3223
+ invalidate();
3224
+ });
3225
+ state = s;
3226
+ return state;
3227
+ }
3228
+ function noiseReady(s) {
3229
+ if (s.noise) return true;
3230
+ if (!noiseImage) {
3231
+ noiseImage = getShaderNoiseTexture() ?? null;
3232
+ noiseImage?.decode().then(
3233
+ () => {
3234
+ const current = getState();
3235
+ if (current && noiseImage) uploadNoise(current, noiseImage);
3236
+ invalidate();
3237
+ },
3238
+ () => {
3239
+ }
3240
+ );
3241
+ }
3242
+ return false;
3243
+ }
3244
+ function artSupported() {
3245
+ return getState() !== null;
3246
+ }
3247
+ function paintArt(target, job) {
3248
+ const s = getState();
3249
+ if (!s || s.lost) return false;
3250
+ if (job.variant === "grain" && !noiseReady(s)) return false;
3251
+ const entry = programFor(s, job.variant);
3252
+ if (!entry) return false;
3253
+ const { gl, canvas } = s;
3254
+ const { width, height } = job;
3255
+ if (canvas.width !== width || canvas.height !== height) {
3256
+ canvas.width = width;
3257
+ canvas.height = height;
3258
+ }
3259
+ gl.viewport(0, 0, width, height);
3260
+ gl.useProgram(entry.program);
3261
+ const at = (name) => {
3262
+ if (!entry.uniforms.has(name)) entry.uniforms.set(name, gl.getUniformLocation(entry.program, name));
3263
+ return entry.uniforms.get(name) ?? null;
3264
+ };
3265
+ const float = (name, value) => gl.uniform1f(at(name), value);
3266
+ gl.uniform2f(at("u_resolution"), width, height);
3267
+ float("u_pixelRatio", job.pixelRatio);
3268
+ float("u_originX", 0.5);
3269
+ float("u_originY", 0.5);
3270
+ float("u_worldWidth", 0);
3271
+ float("u_worldHeight", 0);
3272
+ float("u_fit", 2);
3273
+ float("u_scale", 1);
3274
+ float("u_offsetX", 0);
3275
+ float("u_offsetY", 0);
3276
+ const max = MAX_COLORS[job.variant];
3277
+ const fill = (colors) => {
3278
+ const flat = new Float32Array(max * 4);
3279
+ colors.slice(0, max).forEach((color, i) => flat.set(color, i * 4));
3280
+ gl.uniform4fv(at("u_colors"), flat);
3281
+ float("u_colorsCount", Math.min(colors.length, max));
3282
+ };
3283
+ if (job.variant === "mesh") {
3284
+ float("u_scale", 0.72);
3285
+ float("u_rotation", job.seed * 360);
3286
+ fill(job.colors);
3287
+ float("u_positions", job.seed * 100);
3288
+ float("u_waveX", job.warp);
3289
+ float("u_waveXShift", job.seed);
3290
+ float("u_waveY", job.warp);
3291
+ float("u_waveYShift", job.seed * 7.3 % 1);
3292
+ float("u_mixing", job.softness);
3293
+ float("u_grainMixer", job.grain * 0.6);
3294
+ float("u_grainOverlay", job.grain);
3295
+ } else {
3296
+ const [ground2 = [0, 0, 0, 1], ...bands] = job.colors;
3297
+ float("u_rotation", job.seed * 360);
3298
+ gl.uniform4fv(at("u_colorBack"), ground2);
3299
+ fill(bands.length ? bands : [ground2]);
3300
+ float("u_time", job.seed * 100);
3301
+ float("u_softness", job.softness * 0.8);
3302
+ float("u_intensity", 0.15 + job.warp * 0.2);
3303
+ float("u_noise", 0.15 + job.grain * 0.5);
3304
+ float("u_shape", GRAIN_CORNERS);
3305
+ gl.activeTexture(gl.TEXTURE0);
3306
+ gl.bindTexture(gl.TEXTURE_2D, s.noise);
3307
+ gl.uniform1i(at("u_noiseTexture"), 0);
3308
+ }
3309
+ gl.clearColor(0, 0, 0, 0);
3310
+ gl.clear(gl.COLOR_BUFFER_BIT);
3311
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
3312
+ if (target.width !== width || target.height !== height) {
3313
+ target.width = width;
3314
+ target.height = height;
3315
+ }
3316
+ const context = target.getContext("2d");
3317
+ if (!context) return false;
3318
+ context.clearRect(0, 0, width, height);
3319
+ context.drawImage(canvas, 0, 0);
3320
+ return true;
3321
+ }
3322
+ var swatch;
3323
+ var parsed = /* @__PURE__ */ new Map();
3324
+ function cssColorToRgba(color) {
3325
+ const hit = parsed.get(color);
3326
+ if (hit) return hit;
3327
+ if (swatch === void 0) {
3328
+ swatch = typeof document === "undefined" ? null : document.createElement("canvas").getContext("2d", { willReadFrequently: true });
3329
+ if (swatch) swatch.canvas.width = swatch.canvas.height = 1;
3330
+ }
3331
+ if (!swatch) return null;
3332
+ swatch.clearRect(0, 0, 1, 1);
3333
+ swatch.fillStyle = "transparent";
3334
+ swatch.fillStyle = color;
3335
+ swatch.fillRect(0, 0, 1, 1);
3336
+ const [r, g, b, a] = swatch.getImageData(0, 0, 1, 1).data;
3337
+ const out = [r / 255, g / 255, b / 255, a / 255];
3338
+ if (parsed.size > 512) parsed.clear();
3339
+ parsed.set(color, out);
3340
+ return out;
3341
+ }
3342
+ var themeListeners = /* @__PURE__ */ new Set();
3343
+ var observer = null;
3344
+ var pending = 0;
3345
+ function onThemeChange(listener) {
3346
+ themeListeners.add(listener);
3347
+ if (!observer && typeof MutationObserver !== "undefined") {
3348
+ const notify = () => {
3349
+ if (pending) return;
3350
+ pending = requestAnimationFrame(() => {
3351
+ pending = 0;
3352
+ for (const fn of themeListeners) fn();
3353
+ });
3354
+ };
3355
+ observer = new MutationObserver(notify);
3356
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "style", "data-sable-theme"] });
3357
+ observer.observe(document.head, { childList: true, subtree: true, characterData: true });
3358
+ }
3359
+ return () => {
3360
+ themeListeners.delete(listener);
3361
+ if (!themeListeners.size && observer) {
3362
+ observer.disconnect();
3363
+ observer = null;
3364
+ cancelAnimationFrame(pending);
3365
+ pending = 0;
3366
+ }
3367
+ };
3368
+ }
3369
+ var ART_SLOTS = ["var(--art-1)", "var(--art-2)", "var(--art-3)", "var(--art-4)", "var(--art-5)", "var(--art-6)"];
3370
+ var ART_GROUND = "var(--art-ground)";
3371
+ var lifted = (tone) => `oklch(from var(--tone-${tone}) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h)`;
3372
+ var ground = (tone) => `color-mix(in oklab, var(--primary) 55%, oklch(from var(--tone-${tone}) 0.3 c h))`;
3373
+ function seedValue(seed) {
3374
+ if (seed == null) return 0.37;
3375
+ if (typeof seed === "number") return Math.abs(seed * 0.618034) % 1;
3376
+ let hash = 2166136261;
3377
+ for (let i = 0; i < seed.length; i++) {
3378
+ hash ^= seed.charCodeAt(i);
3379
+ hash = Math.imul(hash, 16777619);
3380
+ }
3381
+ return (hash >>> 0) / 4294967296;
3382
+ }
3383
+ function fallback(palette, groundColor, seed) {
3384
+ const spots = palette.map((color, i) => {
3385
+ const angle = (seed + i / palette.length) * Math.PI * 2;
3386
+ const x = Math.round(50 + Math.cos(angle) * 38);
3387
+ const y = Math.round(50 + Math.sin(angle) * 38);
3388
+ return `radial-gradient(in oklab at ${x}% ${y}%, ${color}, transparent 60%)`;
3389
+ });
3390
+ return [...spots, groundColor].join(", ");
3391
+ }
3392
+ function rotate(items, seed) {
3393
+ if (items.length < 2) return [...items];
3394
+ const shift = Math.floor(seed * 7919) % items.length;
3395
+ return [...items.slice(shift), ...items.slice(0, shift)];
3396
+ }
3397
+ function param(style, name, fallbackValue) {
3398
+ const n = Number.parseFloat(style.getPropertyValue(name));
3399
+ return Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : fallbackValue;
3400
+ }
3401
+ var MAX_PIXELS = 15e5;
3402
+ function GradientArt({ variant = "mesh", tones, seed, className, style, children, ...props }) {
3403
+ const root = useRef(null);
3404
+ const canvas = useRef(null);
3405
+ const probes = useRef(null);
3406
+ const value = seedValue(seed);
3407
+ const palette = tones?.length ? tones.map(lifted) : ART_SLOTS;
3408
+ const groundColor = tones?.length ? ground(tones[0]) : ART_GROUND;
3409
+ const paletteKey = palette.join(" ");
3410
+ const background = fallback(rotate(palette, value), groundColor, value);
3411
+ useEffect(() => {
3412
+ const element = root.current;
3413
+ const target = canvas.current;
3414
+ const swatches = probes.current;
3415
+ if (!element || !target || !swatches || !artSupported()) return;
3416
+ if (typeof IntersectionObserver === "undefined" || typeof ResizeObserver === "undefined") return;
3417
+ let visible = false;
3418
+ let frame = 0;
3419
+ let last = "";
3420
+ const paint = () => {
3421
+ frame = 0;
3422
+ if (!visible) return;
3423
+ const rect = element.getBoundingClientRect();
3424
+ if (!rect.width || !rect.height) return;
3425
+ let ratio = Math.min(window.devicePixelRatio || 1, 2);
3426
+ const pixels = rect.width * rect.height * ratio * ratio;
3427
+ if (pixels > MAX_PIXELS) ratio *= Math.sqrt(MAX_PIXELS / pixels);
3428
+ const width = Math.max(1, Math.round(rect.width * ratio));
3429
+ const height = Math.max(1, Math.round(rect.height * ratio));
3430
+ const resolve = (probe) => cssColorToRgba(getComputedStyle(probe).color);
3431
+ const [groundProbe, ...colorProbes] = Array.from(swatches.children);
3432
+ const colors = rotate(
3433
+ colorProbes.map(resolve).filter((c) => c != null && c[3] > 0.01),
3434
+ value
3435
+ );
3436
+ const groundRgba = groundProbe ? resolve(groundProbe) : null;
3437
+ if (!colors.length) return;
3438
+ const own = getComputedStyle(element);
3439
+ const character = { warp: param(own, "--art-warp", 0.8), softness: param(own, "--art-softness", 0.88), grain: param(own, "--art-grain", 0) };
3440
+ const key = JSON.stringify([variant, value, width, height, colors, groundRgba, character]);
3441
+ if (key === last) return;
3442
+ const ordered = variant === "grain" && groundRgba ? [groundRgba, ...colors] : colors;
3443
+ if (paintArt(target, { variant, colors: ordered, seed: value, width, height, pixelRatio: ratio, ...character })) {
3444
+ last = key;
3445
+ target.dataset.painted = "";
3446
+ }
3447
+ };
3448
+ const schedule = () => {
3449
+ if (!frame) frame = requestAnimationFrame(paint);
3450
+ };
3451
+ const repaint = () => {
3452
+ last = "";
3453
+ schedule();
3454
+ };
3455
+ const intersection = new IntersectionObserver(
3456
+ ([entry]) => {
3457
+ visible = entry.isIntersecting;
3458
+ if (visible) schedule();
3459
+ },
3460
+ { rootMargin: "200px" }
3461
+ );
3462
+ intersection.observe(element);
3463
+ const resize = new ResizeObserver(schedule);
3464
+ resize.observe(element);
3465
+ const offTheme = onThemeChange(schedule);
3466
+ const offInvalidate = onArtInvalidate(repaint);
3467
+ return () => {
3468
+ cancelAnimationFrame(frame);
3469
+ intersection.disconnect();
3470
+ resize.disconnect();
3471
+ offTheme();
3472
+ offInvalidate();
3473
+ };
3474
+ }, [variant, value, paletteKey]);
3475
+ return /* @__PURE__ */ jsxs(
3476
+ "div",
3477
+ {
3478
+ ref: root,
3479
+ "data-slot": "gradient-art",
3480
+ "data-variant": variant,
3481
+ className: cn("relative isolate overflow-hidden", className),
3482
+ style: { background, ...style },
3483
+ ...props,
3484
+ children: [
3485
+ /* @__PURE__ */ jsx(
3486
+ "canvas",
3487
+ {
3488
+ ref: canvas,
3489
+ "aria-hidden": true,
3490
+ className: "pointer-events-none absolute inset-0 -z-10 size-full opacity-0 motion-interactive data-[painted]:opacity-100",
3491
+ style: { transitionProperty: "opacity" }
3492
+ }
3493
+ ),
3494
+ /* @__PURE__ */ jsxs("span", { ref: probes, hidden: true, children: [
3495
+ /* @__PURE__ */ jsx("span", { style: { color: groundColor } }),
3496
+ palette.map((color) => /* @__PURE__ */ jsx("span", { style: { color } }, color))
3497
+ ] }),
3498
+ children
3499
+ ]
3500
+ }
3501
+ );
3502
+ }
3503
+ GradientArt.displayName = "GradientArt";
3504
+
3505
+ // src/components/gradient-art/gradient-art.meta.ts
3506
+ var gradientArtMeta = {
3507
+ name: "GradientArt",
3508
+ summary: "Placeholder artwork painted by a static WebGL shader from the preset's own art direction (the --art-* tokens: palette, ground, warp, softness, grain) \u2014 Ember burns, Prism airbrushes, Meridian is grainy monochrome \u2014 with a CSS gradient underneath for prerendered pages and browsers without WebGL2.",
3509
+ status: "beta",
3510
+ keywords: ["gradient", "mesh gradient", "grain", "shader", "artwork", "placeholder image", "cover", "banner", "avatar fallback", "generative"],
3511
+ usage: {
3512
+ description: "Use GradientArt where a real image belongs but none exists yet, or where the image is decoration: a product cover before upload, a profile banner, an album tile, an avatar with no photo. It reads the theme's own tokens, so it follows every preset, mode and live edit; `seed` fixes each item's composition so a list of covers or avatars stays individually recognisable.",
3513
+ bestPractices: [
3514
+ {
3515
+ guidance: "do",
3516
+ description: "Pass a stable `seed` per item \u2014 the name for an avatar, the title for a cover. Each item then keeps the same art across renders, reloads and themes, which is what lets someone recognise it."
3517
+ },
3518
+ {
3519
+ guidance: "do",
3520
+ description: "Size it with layout classes (`aspect-[4/3]`, `h-20`, `size-10 rounded-full`) and put any glyph or initials inside as children; they render above the art."
3521
+ },
3522
+ {
3523
+ guidance: "do",
3524
+ description: "Keep the default `mesh` for covers, tiles and avatars. Reach for `grain` where a printed, tactile surface suits \u2014 a banner, a hero card \u2014 and keep one variant per surface type so art reads as a system."
3525
+ },
3526
+ {
3527
+ guidance: "dont",
3528
+ description: "Do not put body text over it. The art spans the palette's lightest and most saturated tones, so no single ink is guaranteed to clear contrast across it; reserve overlays for a short label on a scrim or an avatar's initials."
3529
+ },
3530
+ {
3531
+ guidance: "dont",
3532
+ description: "Do not pass colours, and reach for `tones` rarely. The art follows the preset's --art-* tokens on purpose; to change how a product's art looks, set the --theme-art-* hooks in its theme or preset, so every cover, banner and avatar changes together."
3533
+ },
3534
+ {
3535
+ guidance: "dont",
3536
+ description: "Do not use it as a page or hero background. It paints at the element's pixel size, so a full-viewport instance costs a large frame on every theme change; it is sized for covers, banners and tiles."
3537
+ }
3538
+ ]
3539
+ },
3540
+ related: { components: ["Avatar", "Card", "AspectBox"] },
3541
+ props: [
3542
+ { name: "variant", type: '"mesh" | "grain"', default: '"mesh"', description: "mesh: soft spots of colour pulled together by gentle waves. grain: glowing light over deep corners with a printed texture." },
3543
+ {
3544
+ name: "tones",
3545
+ type: '("info" | "standard" | "collectible" | "edition" | "warning")[]',
3546
+ description: "Paint these `--tone-*` colours instead of the preset's art palette. Leave unset to follow the preset's art direction."
3547
+ },
3548
+ { name: "seed", type: "string | number", description: "Fixes the composition: the same seed paints the same art in every theme. Pass a stable id." },
3549
+ { name: "children", type: "ReactNode", description: "Rendered above the art \u2014 initials, a glyph, a short label." },
3550
+ { name: "className", type: "string", description: "Size and shape: aspect, height, rounding." }
3551
+ ],
3552
+ variants: { variant: ["mesh", "grain"] },
3553
+ tokensUsed: ["art-1", "art-2", "art-3", "art-4", "art-5", "art-6", "art-ground", "art-warp", "art-softness", "art-grain", "primary", "tone-info", "tone-standard", "tone-collectible", "tone-edition", "tone-warning"],
3554
+ motion: [
3555
+ {
3556
+ slot: "arrival",
3557
+ recipe: "motion-interactive",
3558
+ part: "canvas",
3559
+ why: "The shader frame fades in over the CSS gradient it replaces, so the swap from fallback to shader reads as the art settling rather than a flash."
3560
+ },
3561
+ { slot: "response", recipe: "none", part: "root", why: "Artwork, not a control: it does not react to the pointer." },
3562
+ {
3563
+ slot: "transition",
3564
+ recipe: "custom",
3565
+ part: "canvas",
3566
+ why: "A theme change repaints the frame in place on the next animation frame; there are no states to move between."
3567
+ },
3568
+ { slot: "activity", recipe: "none", part: "root", why: "Static by design: nothing animates between paints, so it costs nothing while idle and respects reduced motion without a branch." }
3569
+ ],
3570
+ a11y: [
3571
+ "Decorative by default: the canvas is aria-hidden. When the art stands for something (an avatar), label the element that contains it, not the art.",
3572
+ "Children render above the art in the normal reading order, so initials or a label are announced as usual."
3573
+ ],
3574
+ examples: [
3575
+ { label: "Product cover", code: '<GradientArt seed="Untitled (gold rush)" className="aspect-[4/3] rounded-lg" />' },
3576
+ { label: "Grain banner", code: '<GradientArt variant="grain" seed="juno" tones={["info", "edition"]} className="h-20 rounded-md" />' },
3577
+ {
3578
+ label: "Avatar with initials",
3579
+ code: '<GradientArt seed="Mara Okafor" className="grid size-10 place-items-center rounded-full text-label-md text-overlay-foreground">MO</GradientArt>'
3580
+ }
3581
+ ]
3582
+ };
3030
3583
  var avatarVariants = cva(
3031
3584
  "relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden rounded-full bg-muted",
3032
3585
  {
@@ -3111,17 +3664,22 @@ AvatarFallback.displayName = "AvatarFallback";
3111
3664
  function Avatar({
3112
3665
  src,
3113
3666
  alt,
3114
- fallback,
3667
+ fallback: fallback2,
3115
3668
  fallbackDelay,
3116
3669
  size,
3117
3670
  status,
3671
+ variant = "neutral",
3672
+ seed,
3118
3673
  className,
3119
3674
  ...props
3120
3675
  }) {
3121
3676
  const named = !src && alt ? { role: "img", "aria-label": alt } : null;
3122
3677
  const avatar = /* @__PURE__ */ jsxs(AvatarRoot, { size, className: status == null ? className : void 0, ...named, ...props, children: [
3123
3678
  src ? /* @__PURE__ */ jsx(AvatarImage, { src, alt: alt ?? "" }) : null,
3124
- /* @__PURE__ */ jsx(AvatarFallback, { delay: fallbackDelay, "aria-hidden": named ? true : void 0, children: fallback })
3679
+ variant === "art" ? /* @__PURE__ */ jsx(AvatarFallback, { delay: fallbackDelay, "aria-hidden": named ? true : void 0, className: "size-full", children: /* @__PURE__ */ jsxs(GradientArt, { seed: seed ?? (typeof fallback2 === "string" && fallback2 || alt || void 0), className: "flex size-full items-center justify-center text-overlay-foreground", children: [
3680
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "absolute inset-0 bg-overlay/30" }),
3681
+ /* @__PURE__ */ jsx("span", { className: "relative", children: fallback2 })
3682
+ ] }) }) : /* @__PURE__ */ jsx(AvatarFallback, { delay: fallbackDelay, "aria-hidden": named ? true : void 0, children: fallback2 })
3125
3683
  ] });
3126
3684
  if (status == null) return avatar;
3127
3685
  return /* @__PURE__ */ jsxs("span", { "data-slot": "avatar-status-anchor", className: cn("relative inline-flex shrink-0 rounded-full", className), children: [
@@ -3247,6 +3805,10 @@ var avatarMeta = {
3247
3805
  usage: {
3248
3806
  description: 'Use Avatar to represent a person, team, or account with a compact visual identity. Wrap several in AvatarGroup for collaborators, assignees, or attendees \u2014 it overlaps them, rings each in the surface colour, and collapses the rest into a "+N" count; use the low-level parts only when you need custom image/fallback layout.',
3249
3807
  bestPractices: [
3808
+ {
3809
+ guidance: "do",
3810
+ description: 'Use `variant="art"` where people without photos sit side by side \u2014 a member list, an activity feed, a chat. Seeded by the person, each gets their own colour, so faces stay tellable apart at a glance; the neutral fallback makes everyone the same grey circle.'
3811
+ },
3250
3812
  {
3251
3813
  guidance: "do",
3252
3814
  description: "Provide meaningful alt text for profile images, or an empty alt when the adjacent text already names the person."
@@ -3341,6 +3903,17 @@ var avatarMeta = {
3341
3903
  type: '"sm" | "md" | "lg"',
3342
3904
  default: '"md"',
3343
3905
  description: "Square footprint: 32px / 40px / 48px."
3906
+ },
3907
+ {
3908
+ name: "variant",
3909
+ type: '"neutral" | "art"',
3910
+ default: '"neutral"',
3911
+ description: "What the fallback looks like. neutral: initials on the muted surface. art: initials over GradientArt seeded by the person, so each person without a photo gets their own stable colour from the theme."
3912
+ },
3913
+ {
3914
+ name: "seed",
3915
+ type: "string | number",
3916
+ description: "The art variant's seed. Defaults to the fallback text, then `alt`, so a person keeps one colour wherever they appear; pass a user id to tell apart two people who share initials."
3344
3917
  }
3345
3918
  ],
3346
3919
  subcomponents: [
@@ -3371,9 +3944,10 @@ var avatarMeta = {
3371
3944
  }
3372
3945
  ],
3373
3946
  variants: {
3374
- size: ["sm", "md", "lg"]
3947
+ size: ["sm", "md", "lg"],
3948
+ variant: ["neutral", "art"]
3375
3949
  },
3376
- tokensUsed: ["muted", "muted-foreground", "background", "card", "popover"],
3950
+ tokensUsed: ["muted", "muted-foreground", "background", "card", "popover", "overlay", "overlay-foreground"],
3377
3951
  a11y: [
3378
3952
  "Base UI tracks the image loading status and swaps to the fallback on missing/loading/error \u2014 no manual onError wiring.",
3379
3953
  "Provide meaningful alt text via the alt prop; pass an empty alt for decorative avatars.",
@@ -3390,6 +3964,10 @@ var avatarMeta = {
3390
3964
  label: "Fallback only",
3391
3965
  code: '<Avatar fallback="AM" alt="Alex Morgan" />'
3392
3966
  },
3967
+ {
3968
+ label: "Art fallback",
3969
+ code: '<Avatar variant="art" fallback="AM" alt="Alex Morgan" />'
3970
+ },
3393
3971
  {
3394
3972
  label: "Sizes",
3395
3973
  code: '<Avatar src="/pfp.png" alt="Alex Morgan" fallback="AM" size="sm" />\n<Avatar src="/pfp.png" alt="Alex Morgan" fallback="AM" size="md" />\n<Avatar src="/pfp.png" alt="Alex Morgan" fallback="AM" size="lg" />'
@@ -4120,9 +4698,11 @@ function MenuContent({
4120
4698
  }
4121
4699
  var MENU_ITEM_DESTRUCTIVE = (
4122
4700
  // The ink is pulled a quarter toward --foreground (as Badge's soft tones
4123
- // are): raw dark-mode destructive on the popover's 10% red wash measured
4124
- // 3.5-4.0:1 across themes. It reads as the same red and clears 4.5:1.
4125
- "text-[color-mix(in_oklch,var(--destructive)_75%,var(--foreground))] data-[highlighted]:bg-destructive/10 data-[highlighted]:text-[color-mix(in_oklch,var(--destructive)_75%,var(--foreground))]"
4701
+ // are), from --destructive-text rather than the fill: dark fills sit low
4702
+ // enough for a white label, and ink mixed from them measured 3.8:1 on
4703
+ // Meridian's popover. From the text token every theme clears 4.5:1 (worst
4704
+ // 5.4:1, Prism dark, on the 10% red wash).
4705
+ "text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))] data-[highlighted]:bg-destructive/10 data-[highlighted]:text-[color-mix(in_oklch,var(--destructive-text)_75%,var(--foreground))]"
4126
4706
  );
4127
4707
  function MenuItem({ className, variant = "default", ...props }) {
4128
4708
  const tier = useTypeTier();
@@ -5634,7 +6214,7 @@ function DestructiveActionModal({
5634
6214
  confirmLabel = "Delete",
5635
6215
  cancelLabel = "Cancel",
5636
6216
  onConfirm,
5637
- pending,
6217
+ pending: pending2,
5638
6218
  confirmingLabel,
5639
6219
  children,
5640
6220
  confirmText
@@ -5643,8 +6223,8 @@ function DestructiveActionModal({
5643
6223
  const [internalBusy, setInternalBusy] = useState(false);
5644
6224
  const inFlight = useRef(false);
5645
6225
  const cancelRef = useRef(null);
5646
- const controlledPending = pending !== void 0;
5647
- const busy = pending ?? internalBusy;
6226
+ const controlledPending = pending2 !== void 0;
6227
+ const busy = pending2 ?? internalBusy;
5648
6228
  useEffect(() => {
5649
6229
  if (!open) {
5650
6230
  setTyped("");
@@ -6838,4 +7418,4 @@ var attachmentMeta = {
6838
7418
  ]
6839
7419
  };
6840
7420
 
6841
- export { AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, Attachment, Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, AvatarRoot, BADGE_APPEARANCES, BADGE_TONES, Badge, Bubble, BubbleReaction, BubbleReactions, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, Code, CodeBlock, CodeHighlightProvider, Density, DensityProvider, DestructiveActionModal, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Entity, FIELD_INVALID, FIELD_SHELL, FIELD_SHELL_XS, Field, Form, HighlightGlide, Input, Label, MENU_ITEM_DESTRUCTIVE, MOBILE_FIELD_TIER, Menu, MenuCheckboxItem, MenuContent, MenuGroup, MenuGroupLabel, MenuItem, MenuPortal, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Message, MotionProvider, OverflowMenu, Pill, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, Select, SelectContent, SelectGroup, SelectGroupLabel, SelectItem, SelectPortal, SelectSeparator, SelectTrigger, SelectValue, Separator, SimpleTooltip, Spinner, StatusDot, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Textarea, Tooltip, TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger, anchoredListScroll, anchoredScroll, attachmentMeta, avatarGroupVariants, avatarMeta, avatarVariants, badgeMeta, badgeVariants, bubbleMeta, buttonMeta, buttonVariants, cardMeta, codeBlockMeta, codeMeta, destructiveActionModalMeta, dialogMeta, entityMeta, formMeta, inputMeta, labelMeta, menuMeta, mergeRefs, messageMeta, overflowMenuMeta, pillVariants, progressMeta, progressTrackVariants, selectMeta, separatorMeta, setRef, spinnerMeta, statusDotMeta, statusDotVariants, switchMeta, tableMeta, textareaMeta, tooltipMeta, trimLabel, useAnchoredDensity, useCopy, useDensity, useEdgeFade, useIsomorphicLayoutEffect, useMotion, useReducedMotion, useResizeObserver, useScrollActivity, useTypeTier, withPortalThemeScope };
7421
+ export { AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, Attachment, Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, AvatarRoot, BADGE_APPEARANCES, BADGE_TONES, Badge, Bubble, BubbleReaction, BubbleReactions, Button, Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardList, CardTitle, Code, CodeBlock, CodeHighlightProvider, Density, DensityProvider, DestructiveActionModal, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, Entity, FIELD_INVALID, FIELD_SHELL, FIELD_SHELL_XS, Field, Form, GradientArt, HighlightGlide, Input, Label, MENU_ITEM_DESTRUCTIVE, MOBILE_FIELD_TIER, Menu, MenuCheckboxItem, MenuContent, MenuGroup, MenuGroupLabel, MenuItem, MenuPortal, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Message, MotionProvider, OverflowMenu, Pill, Progress, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, Select, SelectContent, SelectGroup, SelectGroupLabel, SelectItem, SelectPortal, SelectSeparator, SelectTrigger, SelectValue, Separator, SimpleTooltip, Spinner, StatusDot, Switch, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Textarea, Tooltip, TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger, anchoredListScroll, anchoredScroll, attachmentMeta, avatarGroupVariants, avatarMeta, avatarVariants, badgeMeta, badgeVariants, bubbleMeta, buttonMeta, buttonVariants, cardMeta, codeBlockMeta, codeMeta, destructiveActionModalMeta, dialogMeta, entityMeta, formMeta, gradientArtMeta, inputMeta, labelMeta, menuMeta, mergeRefs, messageMeta, overflowMenuMeta, pillVariants, progressMeta, progressTrackVariants, selectMeta, separatorMeta, setRef, spinnerMeta, statusDotMeta, statusDotVariants, switchMeta, tableMeta, textareaMeta, tooltipMeta, trimLabel, useAnchoredDensity, useCopy, useDensity, useEdgeFade, useIsomorphicLayoutEffect, useMotion, useReducedMotion, useResizeObserver, useScrollActivity, useTypeTier, withPortalThemeScope };