All files / src/styles pillVariants.ts

100% Statements 206/206
100% Branches 3/3
100% Functions 2/2
100% Lines 206/206

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x 141x  
import { css } from 'lit';
 
/**
 * TextIt Design System pill color variants — single source of truth.
 *
 * Provides the color triple (background / foreground / border) keyed
 * off `.pill-{type}` plus the JS-side taxonomy (`PILL_TYPES`,
 * `PILL_TYPE_ICONS`, `iconToPillType`) consumed by Select.ts and
 * flow/utils.ts.
 *
 * Variant theming:
 *   - .pill-contact / .pill-group: derived from --recipient (a fixed
 *     blue), NOT the accent ramp. This keeps recipient pills stable
 *     across brand re-theming via --primary-rgb.
 *   - .pill-flow, .pill-channel: bg/border derived from --flow /
 *     --channel via color-mix; text + icon use the anchor directly.
 *   - .pill-field: bg/border are fixed at the Tailwind yellow ramp
 *     (yellow-100 / yellow-300) because yellow has too little
 *     contrast against white to color-mix into a recognizable swatch.
 *     Text uses yellow-900 for readability; the icon uses --field
 *     directly, which is the only knob a host page can re-theme.
 *   - .pill-neutral / .pill-label / .pill-keyword: greys; not
 *     anchor-driven.
 *
 * Shape (height, padding, radius, icon spacing) is the consumer's
 * concern, since pill use-cases differ: Select chips have a remove
 * button on the right, ContactDetails pills are clickable links, etc.
 *
 * To add a new variant: extend `PILL_TYPES`, optionally add an entry
 * to `PILL_TYPE_ICONS` / `ICON_TO_PILL_TYPE`, append a `.pill-{type}`
 * block below, and reference an anchor in `designTokens.ts`.
 */
 
/** Recognized pill variants. Anything outside this set falls back to
 * `pill-neutral` (or is rejected by callers as not a pill at all). */
export const PILL_TYPES: ReadonlySet<string> = new Set([
  'neutral',
  'flow',
  'group',
  'contact',
  'field',
  'label',
  'keyword',
  'channel',
  'topic',
  'campaign',
  'removed'
]);
 
/** Default icon name for each pill variant. Used when a consumer
 * specifies `type` but not `icon` — keeps Omnibox-style options and
 * Django-form-rendered options visually consistent without making the
 * data layer set both fields. */
export const PILL_TYPE_ICONS: Readonly<Record<string, string>> = {
  group: 'group',
  contact: 'contact',
  field: 'fields',
  flow: 'flow',
  label: 'label',
  topic: 'topic',
  campaign: 'campaign'
};
 
/** Inverse mapping: icon name (alias or resolved SVG id) → pill type.
 * Both forms are valid since flow-action items pass through either. */
const ICON_TO_PILL_TYPE: Readonly<Record<string, string>> = {
  flow: 'flow',
  group: 'group',
  contact: 'contact',
  contacts: 'contact',
  field: 'field',
  fields: 'field',
  label: 'label',
  campaign: 'campaign',
  // resolved Icon enum SVG ids
  'users-01': 'group',
  'atom-01': 'group',
  'user-01': 'contact',
  'tag-01': 'label',
  'clock-refresh': 'campaign'
};
 
export const iconToPillType = (icon?: string): string | undefined => {
  if (!icon) return undefined;
  if (ICON_TO_PILL_TYPE[icon]) return ICON_TO_PILL_TYPE[icon];
  // Legacy alias prefix (e.g. 'group_smart' → 'group').
  if (icon.startsWith('group')) return 'group';
  return undefined;
};
 
export const pillVariants = css`
  /* The grey variants border off --pill-border, falling back to
     --border. The indirection lets a host context (e.g. a selected
     list row) retint the border without redefining --border, which
     each component re-declares in its own shadow DOM via the design
     tokens and so can't be overridden from outside. */
  .pill-neutral {
    background: var(--sunken);
    color: var(--text-1);
    border-color: var(--pill-border, var(--border));
    --icon-color: var(--text-2);
  }
  /* Anchor-derived variants carry static pre-mixed bg/border values
     for browsers without color-mix() (pre-Chrome 111); the @supports
     block at the bottom re-derives them from the anchors so host
     re-theming still works where color-mix() is available. */
  .pill-flow {
    background: #e3f4e9;
    color: var(--flow);
    border-color: #c5e8d2;
    --icon-color: var(--flow);
  }
  /* Recipient color — shared by contacts and groups. Anchored to
     --recipient (fixed blue), NOT the accent ramp, so pills keep their
     identity even when the host page re-themes via --primary-rgb. */
  .pill-contact,
  .pill-group {
    background: #e5eef6;
    color: var(--recipient);
    border-color: #cadbec;
    --icon-color: var(--recipient);
  }
  .pill-channel {
    background: #ede4f5;
    color: var(--channel);
    border-color: #dac8e9;
    --icon-color: var(--channel);
  }
  .pill-topic {
    background: #faefe1;
    color: var(--topic);
    border-color: #f6ddc1;
    --icon-color: var(--topic);
  }
  .pill-campaign {
    background: #e1f2f6;
    color: var(--campaign);
    border-color: #c1e4ec;
    --icon-color: var(--campaign);
  }
  .pill-field {
    /* Yellow has very low contrast against white, so the color-mix
       approach used by other variants washes out at any readable mix
       percentage. We use the Tailwind yellow ramp directly for bg
       (yellow-100) / border (yellow-300) / text (yellow-900). The
       icon hue stays anchored to --field so a host page can still
       re-theme the variant by overriding that one token. */
    background: #fef9c3;
    color: #854d0e;
    border-color: #fde68a;
    --icon-color: var(--field);
  }
  .pill-removed {
    /* Removal pill — used when an entity is taken away (e.g. removed
       from a group in the chat history). Fixed at the Tailwind red
       ramp (red-100 bg / red-200 border / red-800 text) for the same
       reason .pill-field is fixed at yellow: color-mixing a light red
       into white washes out before it reads as "red". */
    background: #fee2e2;
    color: #991b1b;
    border-color: #fecaca;
    --icon-color: #991b1b;
  }
  .pill-keyword {
    background: var(--sunken);
    color: var(--text-1);
    border-color: var(--pill-border, var(--border));
    --icon-color: var(--text-2);
    font-family: var(--font-mono);
    font-size: 11.5px;
  }
  .pill-label {
    background: var(--sunken);
    color: var(--text-2);
    border-color: var(--pill-border, var(--border));
    --icon-color: var(--text-2);
  }
 
  /* Anchor-derived bg/border for browsers with color-mix(). These
     can't be simple same-rule fallbacks: a declaration containing
     var() isn't dropped at parse time, it goes invalid at
     computed-value time and takes the earlier declaration with it. */
  @supports (color: color-mix(in srgb, red, red)) {
    .pill-flow {
      background: color-mix(in srgb, var(--flow) 12%, white);
      border-color: color-mix(in srgb, var(--flow) 25%, white);
    }
    .pill-contact,
    .pill-group {
      background: color-mix(in srgb, var(--recipient) 12%, white);
      border-color: color-mix(in srgb, var(--recipient) 25%, white);
    }
    .pill-channel {
      background: color-mix(in srgb, var(--channel) 12%, white);
      border-color: color-mix(in srgb, var(--channel) 25%, white);
    }
    .pill-topic {
      background: color-mix(in srgb, var(--topic) 12%, white);
      border-color: color-mix(in srgb, var(--topic) 25%, white);
    }
    .pill-campaign {
      background: color-mix(in srgb, var(--campaign) 12%, white);
      border-color: color-mix(in srgb, var(--campaign) 25%, white);
    }
  }
`;