FilamentSwatch.tsx 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import React, { useMemo } from 'react';
  2. import {
  3. parseStops,
  4. buildFilamentBackground,
  5. type FilamentEffect,
  6. type SwatchType,
  7. } from './filamentSwatchHelpers';
  8. /** Shared filament-colour swatch. See `filamentSwatchHelpers.ts` for the
  9. * pure helpers + constants this component composes. */
  10. export interface FilamentSwatchProps {
  11. /** RRGGBBAA hex without `#` (Bambu/AMS canonical). Falls back to grey when null. */
  12. rgba?: string | null;
  13. /** Comma-separated 6/8-char hex tokens (no `#`). Empty/undefined → solid. */
  14. extraColors?: string | null;
  15. /** Visual effect overlay. */
  16. effectType?: FilamentEffect | string | null;
  17. /** When `Multicolor`, a conic gradient is used instead of linear. */
  18. subtype?: string | null;
  19. /** Tailwind size token applied to width/height (e.g. `w-5 h-5`). Default: `w-5 h-5`. */
  20. className?: string;
  21. /** Override the rounded shape — defaults to `rounded-full` (circular). */
  22. shape?: 'circle' | 'pill' | 'square';
  23. /** Optional inline style overrides (e.g. height of a card banner). */
  24. style?: React.CSSProperties;
  25. /** Native title attribute for hover tooltip. */
  26. title?: string;
  27. /** Tune effect appearance based on target div size. */
  28. effectSize: SwatchType;
  29. }
  30. export function FilamentSwatch({
  31. rgba,
  32. extraColors,
  33. effectType,
  34. subtype,
  35. className = 'w-5 h-5',
  36. shape = 'circle',
  37. style,
  38. title,
  39. effectSize,
  40. }: FilamentSwatchProps) {
  41. const stops = useMemo(() => parseStops(extraColors), [extraColors]);
  42. const filamentBackground = useMemo(
  43. () => buildFilamentBackground({ effectSize, rgba, extraColors, effectType, subtype }),
  44. [effectSize, rgba, extraColors, effectType, subtype]
  45. );
  46. const backgroundImage = filamentBackground.backgroundImage;
  47. const backgroundSize = filamentBackground.backgroundSize;
  48. const shapeClass =
  49. shape === 'circle' ? 'rounded-full' : shape === 'pill' ? 'rounded-full' : 'rounded';
  50. // Compute a sensible title fallback — solid hex or gradient summary.
  51. // Show the full 8-char rgba when alpha < FF so the tooltip on a Clear /
  52. // translucent spool reflects what's actually painted (#1545).
  53. const titleHex = (() => {
  54. if (!rgba) return undefined;
  55. const clean = rgba.replace(/^#/, '');
  56. if (clean.length >= 8 && clean.substring(6, 8).toLowerCase() !== 'ff') {
  57. return `#${clean.substring(0, 8)}`;
  58. }
  59. return `#${clean.substring(0, 6)}`;
  60. })();
  61. const computedTitle =
  62. title ??
  63. (stops.length > 0
  64. ? stops.join(', ')
  65. : titleHex);
  66. return (
  67. <span
  68. data-testid="filament-swatch"
  69. className={`${className} ${shapeClass} border border-black/20 inline-block flex-shrink-0`}
  70. style={{ backgroundImage, backgroundSize, ...style }}
  71. title={computedTitle}
  72. />
  73. );
  74. }
  75. export default FilamentSwatch;