@ntmats/hc-ui 0.10.0 → 0.12.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/README.md CHANGED
@@ -95,4 +95,4 @@ Fixed colors that stay the same in both themes are HeroClix tokens: `color-dial-
95
95
 
96
96
  `PhysicalCardView` is the printed card in HCUnits' format (375×525 px, absolute positions), scaled to its container's width: front and back side by side while each keeps about 86% of its size, otherwise one under the other, never above 1.2×. With `fitHeight` the front also fits the visible height of the scrolling area found by `fitHeightWithin`. Everything comes resolved: the backgrounds (`images.front`, `images.back`, `images.legacy`, `layout.rarityImage`, `layout.dialImage`: addresses the app serves; HCUI's are in its `public/card/` and do not ship here), the title (its type shrinks from 28px to fit), header lines, team abilities (buttons that open a tip, with the shield the caller passes in `teamAbilityIcon`), the collection number and set symbol, the token (`portrait` drawn by the caller with the class the card gives, `terrain` art or an `object` ring in its color), the bystander's token replica, keyphrases, the rules (already translated, with an optional `icon`; tapping a paragraph calls `onCopy(copyText)`), improved abilities, the printed dial as a node (`dial.node`, usually `CombatDialView` with `presentation="printed"`), the terrain dial, points (buttons that call `onSelectPoint`, an object's line or nothing) and the black back bar. Rules that do not fit the front move to the backs, two columns each, one block at a time (`nextPhysicalCardDistribution`); the split starts over when `contentKey` changes. Under a mouse the card tilts up to 3° toward the pointer, with parallax and a shine (nearly still over the dial); on a phone only the focus view moves, by the motion sensor (`requestPhysicalCardMotionAccess` asks iOS on the first touch). Holding the card with a finger for 450 ms calls `onRequestFocus`; the caller opens `PhysicalCardFocusView` (a modal `<dialog>` over a dimmed, blurred backdrop; Esc and a tap outside, once the opening finger is lifted, call `onClose`) with a second card with `focus`, which shows one side at a time and draws `focusActions({ nextSide, backs, flip })` under it (the library's `Button` `outline` there gets the surface fill). Tips are controlled as in the dial: hover with a mouse opens a passive tip described by the control, a press or Enter opens it as a dialog, leaving closes the passive one; the caller keeps `tip` and draws it in `tipSlot`. Paper, ink, shine and grain follow the theme (`color-card-*`, `filter-card-background`, `blend-card-*`, `opacity-card-grain*`); the printed black and white, the team-up band and the terrain dial are fixed (`color-card-print-*`, `color-card-team-up`, `color-card-terrain-panel*`). Letters are upright (`--hc-rond: 0` on the card, which its printed dial reads too). Motion only with a fine pointer or in the phone's focus view, and none under `prefers-reduced-motion`.
97
97
 
98
- `MapBoardView` draws a map already fetched (`map`: the hcmaps JSON, `HCMapJson`) with the hcmaps engine (`HCMap`) in a canvas named by `canvasLabel`, square-cornered and white in both themes (`color-map-board`), and shades the inspected square or draws the inspected edge (`selection`, from `inspectMap`; `color-map-selection*`). `onInspect(x, y, clientX, clientY)` gets the pointer in squares: mouse and pen while passing over, touch on press; `onLeave` when the mouse leaves. Other attributes (role, `tabIndex`, `onKeyDown`, `aria-*`) go to the container, so the caller adds the keyboard (HCUI: arrows move one square, Esc closes). `MapBoardPlaceholder` is the empty grid while it loads, a busy status whose `label` is visually hidden by the library. `MapTooltipView` is fixed next to `anchor` without leaving the screen (portal it out of a clipping container): the title, a wall's color, and for each square (one, or two on an edge) its coordinate, terrain and elevation swatches with the caller's names, the environment (a node, e.g. `MapEnvironmentView`, or `environmentNote`) and notes. `MapSwatch` is the board's square in a legend; `MapLegendView` lists them (the 3D model's legend) with children after; `MapEnvironmentView` shows indoor/outdoor with the caller's icons (black line icons, turned light in the dark theme by `filter-map-environment-icon`) and names. `renderMapThumbnail(json, height)` returns a PNG data URL. The engines are plain functions with no DOM beyond a canvas context: `inspectMap` (square, edge or wall at a point, in squares), `mapSquare`, `mapMaxLevel`, `mapElevationLevel`, `mapSquareCoordinate`; `buildMapScene` (the 3D model's heights, walls, ramps and ziplines), the camera (`makeMapProjection`, `pickMapScene`, `zoomMapCamera`, `clampMapPan`) and `drawMapScene` (Canvas 2D). The camera is parallel by default; `makeMapProjection(scene, camera, viewport, { perspective: MAP_PERSPECTIVE_DISTANCE })` adds a light perspective (near squares larger, far ones smaller) in the same screen box, with `ground` still exact and `factor(x, y, z)` telling how much a point grew, e.g. to size a figure drawn over the model. `MapBoardView` draws again when the browser gives its canvas back blank (`contextrestored`). The 3D model's component, its gestures and its icons stay in the app.
98
+ `MapBoardView` draws a map already fetched (`map`: the hcmaps JSON, `HCMapJson`) with the hcmaps engine (`HCMap`) in a canvas named by `canvasLabel`, square-cornered and white in both themes (`color-map-board`), and shades the inspected square or draws the inspected edge (`selection`, from `inspectMap`; `color-map-selection*`). `onInspect(x, y, clientX, clientY)` gets the pointer in squares: mouse and pen while passing over, touch on press; `onLeave` when the mouse leaves. Other attributes (role, `tabIndex`, `onKeyDown`, `aria-*`) go to the container, so the caller adds the keyboard (HCUI: arrows move one square, Esc closes). `MapBoardPlaceholder` is the empty grid while it loads, a busy status whose `label` is visually hidden by the library. `MapTooltipView` is fixed next to `anchor` without leaving the screen (portal it out of a clipping container): the title, a wall's color, and for each square (one, or two on an edge) its coordinate, terrain and elevation swatches with the caller's names, the environment (a node, e.g. `MapEnvironmentView`, or `environmentNote`) and notes. `MapSwatch` is the board's square in a legend; `MapLegendView` lists them (the 3D model's legend) with children after; `MapEnvironmentView` shows indoor/outdoor with the caller's icons (black line icons, turned light in the dark theme by `filter-map-environment-icon`) and names. `renderMapThumbnail(json, height)` returns a PNG data URL. The engines are plain functions with no DOM beyond a canvas context: `inspectMap` (square, edge or wall at a point, in squares), `mapSquare`, `mapMaxLevel`, `mapElevationLevel`, `mapSquareCoordinate`; `buildMapScene` (the 3D model's heights, walls, ramps and ziplines), the camera (`makeMapProjection`, `pickMapScene`, `zoomMapCamera`, `clampMapPan`) and `drawMapScene` (Canvas 2D). The camera is parallel by default; `makeMapProjection(scene, camera, viewport, { perspective: MAP_PERSPECTIVE_DISTANCE })` adds a light perspective (near squares larger, far ones smaller) in the same screen box, with `ground` still exact and `factor(x, y, z)` telling how much a point grew, e.g. to size a figure drawn over the model. `MapBoardView` draws again when the browser gives its canvas back blank (`contextrestored`). `drawMapScene(..., { decorate })` lets the app paint a side over its default face (`side(ctx, face)`, with the face's screen `quad`) and draw what stands on a square right after it in the painter's order (`cell(ctx, cell)`), or draw a ramp its own way (`ramp(ctx, ramp)` returning `true`), e.g. facades, props and stairs, without changing terrain or picking. The 3D model's component, its gestures and its icons stay in the app.
package/dist/index.d.ts CHANGED
@@ -97,7 +97,14 @@ export declare const IconButton: ForwardRefExoticComponent<IconButtonProps & Ref
97
97
  interface ChipBaseProps extends ButtonHTMLAttributes<HTMLButtonElement> {
98
98
  /** Toggle state for filters; omit for a plain action chip. */
99
99
  selected?: boolean;
100
+ /** Decorative icon before the label (18px at `md`); its side of the pill gets a little less room than text. */
100
101
  icon?: ReactNode;
102
+ /**
103
+ * Round picture before the label, concentric with the pill's start: 24px at `md` and `sm`, 18px at
104
+ * `xs` (a character, a player). Pass an `img` (or an `AvatarView`); the chip sizes and rounds it.
105
+ * Decorative: the label names the chip. Takes the place of `icon` when both are given.
106
+ */
107
+ avatar?: ReactNode;
101
108
  /**
102
109
  * `md` (default): 36px, 13px text (HCUI `chip`). `sm`: 36px, 12px text and 12px padding (HCUI's
103
110
  * desktop filter pills and sort trigger). `xs`: 26px, 11px text, muted fill without ring (HCUI's
@@ -1591,6 +1598,31 @@ export declare function mapFootprintOutline({ x, y, width, height, cells }: {
1591
1598
  edges: [Point, Point][];
1592
1599
  corners: Point[];
1593
1600
  };
1601
+ /** A square's visible side, as `drawMapScene` draws it: from the neighbor's top (`low`) to the square's. */
1602
+ export interface MapSceneFace {
1603
+ cell: MapSceneCell;
1604
+ /** The side's outward direction (one of `MAP_CELL_SIDES`). */
1605
+ dx: number;
1606
+ dy: number;
1607
+ low: number;
1608
+ top: number;
1609
+ /** Screen corners: low at the side's start, low at its end, top at its end, top at its start. */
1610
+ quad: [[number, number], [number, number], [number, number], [number, number]];
1611
+ /** How lit the side is, 0..1 (the default face is shaded by it). */
1612
+ light: number;
1613
+ }
1614
+ /**
1615
+ * What the app adds to the 3D model, in the painter's order of `drawMapScene`: a side
1616
+ * drawn over the default face (a facade, a wall's stones), and whatever stands on a
1617
+ * square, drawn right after that square (a car, a tree, a pillar), so nearer squares
1618
+ * and walls still cover it. Terrain, heights and picking do not change.
1619
+ */
1620
+ export interface MapSceneDecoration {
1621
+ side?(ctx: CanvasRenderingContext2D, face: MapSceneFace): void;
1622
+ cell?(ctx: CanvasRenderingContext2D, cell: MapSceneCell): void;
1623
+ /** Draws a ramp (e.g. as stairs) in place of the default wedge; return `true` when it did. */
1624
+ ramp?(ctx: CanvasRenderingContext2D, ramp: MapSceneRamp): boolean;
1625
+ }
1594
1626
  export interface MapSceneFootprint {
1595
1627
  x: number;
1596
1628
  y: number;
@@ -1615,8 +1647,9 @@ export interface MapSceneHighlight {
1615
1647
  /** The target of a piece being moved: floor and volume, cut to the board. */
1616
1648
  footprint?: MapSceneFootprint;
1617
1649
  }
1618
- export declare function drawMapScene(ctx: CanvasRenderingContext2D, scene: MapScene, view: MapProjection, texture: HTMLCanvasElement, highlight?: MapSceneHighlight | null, { tufts }?: {
1650
+ export declare function drawMapScene(ctx: CanvasRenderingContext2D, scene: MapScene, view: MapProjection, texture: HTMLCanvasElement, highlight?: MapSceneHighlight | null, { tufts, decorate }?: {
1619
1651
  tufts?: boolean;
1652
+ decorate?: MapSceneDecoration;
1620
1653
  }): void;
1621
1654
  /** The thumbnail's default height, in pixels. */
1622
1655
  export declare const MAP_THUMBNAIL_HEIGHT = 192;
package/dist/index.js CHANGED
@@ -156,18 +156,18 @@ var glyph = (size, d) => /* @__PURE__ */ jsx("svg", { width: size, height: size,
156
156
  var CHEVRON_DOWN = "M6 9L12 15L18 9";
157
157
  var XMARK = "M6.75827 17.2426L12.0009 12M17.2435 6.75736L12.0009 12M12.0009 12L6.75827 6.75736M12.0009 12L17.2435 17.2426";
158
158
  var visible = (node) => node !== void 0 && node !== null && node !== false && node !== "";
159
- var Chip = forwardRef(function Chip2({ selected, icon, size = "md", variant = "outline", applied = false, count, badge, badgePlacement = "inline", informative = false, chevron, onClear, clearLabel, type = "button", className, children, ...props }, ref) {
159
+ var Chip = forwardRef(function Chip2({ selected, icon, avatar, size = "md", variant = "outline", applied = false, count, badge, badgePlacement = "inline", informative = false, chevron, onClear, clearLabel, type = "button", className, children, ...props }, ref) {
160
160
  const popup = props["aria-haspopup"];
161
161
  const withChevron = chevron ?? (popup !== void 0 && popup !== false && popup !== "false");
162
162
  const look = {
163
163
  "data-size": size === "md" ? void 0 : size,
164
164
  "data-variant": variant === "outline" ? void 0 : variant,
165
165
  "data-applied": applied || void 0,
166
- "data-icon-only": icon && !visible(children) || void 0,
166
+ "data-icon-only": icon && !avatar && !visible(children) || void 0,
167
167
  "data-informative": informative || void 0
168
168
  };
169
169
  const content = /* @__PURE__ */ jsxs(Fragment2, { children: [
170
- icon && /* @__PURE__ */ jsx("span", { className: "hc-ui-chip__icon", "aria-hidden": "true", children: icon }),
170
+ avatar ? /* @__PURE__ */ jsx("span", { className: "hc-ui-chip__avatar", "aria-hidden": "true", children: avatar }) : icon && /* @__PURE__ */ jsx("span", { className: "hc-ui-chip__icon", "aria-hidden": "true", children: icon }),
171
171
  children,
172
172
  visible(count) && /* @__PURE__ */ jsx("span", { className: "hc-ui-chip__count", children: count }),
173
173
  visible(badge) && /* @__PURE__ */ jsx("span", { className: "hc-ui-chip__badge", "data-placement": badgePlacement === "corner" ? "corner" : void 0, children: badge }),
@@ -2962,7 +2962,7 @@ function sideColor(cell) {
2962
2962
  if (cell.terrain === "window") return MAP_TERRAIN_EDGE_COLORS.window;
2963
2963
  return SIDE_COLORS[cell.environment] ?? SIDE_COLORS.unknown;
2964
2964
  }
2965
- function drawCellSides(ctx, scene, view, cell) {
2965
+ function drawCellSides(ctx, scene, view, cell, decorate) {
2966
2966
  const { point } = view;
2967
2967
  for (const side of MAP_CELL_SIDES) {
2968
2968
  if (side.dx * view.sin + side.dy * view.cos <= 0) continue;
@@ -2971,7 +2971,13 @@ function drawCellSides(ctx, scene, view, cell) {
2971
2971
  if (low >= cell.top) continue;
2972
2972
  const ax = cell.x + side.ax, ay = cell.y + side.ay, bx = cell.x + side.bx, by = cell.y + side.by;
2973
2973
  const light = faceLight(view, side.dx, side.dy);
2974
- face(ctx, [point(ax, ay, low), point(bx, by, low), point(bx, by, cell.top), point(ax, ay, cell.top)], shade(sideColor(cell), light));
2974
+ const quad = [point(ax, ay, low), point(bx, by, low), point(bx, by, cell.top), point(ax, ay, cell.top)];
2975
+ face(ctx, quad, shade(sideColor(cell), light));
2976
+ if (decorate?.side) {
2977
+ ctx.save();
2978
+ decorate.side(ctx, { cell, dx: side.dx, dy: side.dy, low, top: cell.top, quad, light });
2979
+ ctx.restore();
2980
+ }
2975
2981
  ctx.strokeStyle = "rgba(0,0,0,.26)";
2976
2982
  ctx.lineWidth = 1;
2977
2983
  for (let z = 0; z < cell.top - 0.01; z += MAP_SCENE_LEVEL_HEIGHT) {
@@ -3153,7 +3159,7 @@ function drawHighlight(ctx, scene, view, highlight) {
3153
3159
  ctx.lineWidth = 2;
3154
3160
  ctx.stroke();
3155
3161
  }
3156
- function drawMapScene(ctx, scene, view, texture, highlight, { tufts = true } = {}) {
3162
+ function drawMapScene(ctx, scene, view, texture, highlight, { tufts = true, decorate } = {}) {
3157
3163
  const pad = 0.35;
3158
3164
  polygon(ctx, [view.point(-pad, -pad, MAP_SCENE_BASE_Z - 0.16), view.point(scene.width + pad, -pad, MAP_SCENE_BASE_Z - 0.16), view.point(scene.width + pad, scene.height + pad, MAP_SCENE_BASE_Z - 0.16), view.point(-pad, scene.height + pad, MAP_SCENE_BASE_Z - 0.16)]);
3159
3165
  ctx.fillStyle = "rgba(0,0,0,.14)";
@@ -3176,11 +3182,24 @@ function drawMapScene(ctx, scene, view, texture, highlight, { tufts = true } = {
3176
3182
  items.push({
3177
3183
  depth: view.depth(cell.x + 0.5, cell.y + 0.5),
3178
3184
  draw() {
3179
- drawCellSides(ctx, scene, view, cell);
3185
+ drawCellSides(ctx, scene, view, cell, decorate);
3180
3186
  drawCellTop(ctx, scene, view, cell, texture, shadows);
3181
3187
  if (mixed && cell.environment === "indoor") drawIndoorCurb(ctx, scene, view, cell);
3182
- for (const ramp of rampsByCell.get(cell.y * scene.width + cell.x) ?? []) drawRamp(ctx, view, ramp);
3188
+ for (const ramp of rampsByCell.get(cell.y * scene.width + cell.x) ?? []) {
3189
+ let drawn = false;
3190
+ if (decorate?.ramp) {
3191
+ ctx.save();
3192
+ drawn = decorate.ramp(ctx, ramp);
3193
+ ctx.restore();
3194
+ }
3195
+ if (!drawn) drawRamp(ctx, view, ramp);
3196
+ }
3183
3197
  if (tufts && cell.terrain === "hindering") drawHindering(ctx, view, cell);
3198
+ if (decorate?.cell) {
3199
+ ctx.save();
3200
+ decorate.cell(ctx, cell);
3201
+ ctx.restore();
3202
+ }
3184
3203
  }
3185
3204
  });
3186
3205
  }
package/dist/styles.css CHANGED
@@ -64,7 +64,8 @@
64
64
  min-width: var(--hc-segmented-option-min-width, 0);
65
65
  min-height: 38px;
66
66
  margin: 0;
67
- padding: 0 var(--hc-space-3);
67
+ /* Text gets the pill's room (as Chip); an icon before the label, a little less. */
68
+ padding: 0 var(--hc-space-4);
68
69
  color: var(--hc-color-text-secondary);
69
70
  font-family: var(--hc-font-body);
70
71
  font-size: var(--hc-font-size-sm);
@@ -84,7 +85,8 @@
84
85
  box-shadow var(--hc-transition-fast), font-weight 220ms var(--hc-ease-standard);
85
86
  }
86
87
 
87
- [data-hc-ds] .hc-ui-segmented[data-density='compact'] .hc-ui-segmented__option { min-height: 32px; }
88
+ [data-hc-ds] .hc-ui-segmented[data-density='compact'] .hc-ui-segmented__option { min-height: 32px; padding: 0 14px; }
89
+ [data-hc-ds] .hc-ui-segmented:not([data-labels='hidden']) .hc-ui-segmented__option:has(> .hc-ui-segmented__icon + .hc-ui-segmented__label) { padding-inline-start: var(--hc-space-3); }
88
90
 
89
91
  [data-hc-ds] .hc-ui-segmented[data-labels='hidden'] .hc-ui-segmented__option {
90
92
  width: 40px;
@@ -216,9 +218,11 @@
216
218
 
217
219
  /* Button — HCUI btn-base / btn-primary / btn-outline / btn-danger / btn-lg. */
218
220
  [data-hc-ds] .hc-ui-button {
221
+ --hc-button-pad: var(--hc-space-5);
222
+ --hc-button-pad-icon: var(--hc-space-4);
219
223
  gap: var(--hc-space-2);
220
224
  min-height: var(--hc-control-height-md);
221
- padding: 0 var(--hc-space-5);
225
+ padding: 0 var(--hc-button-pad);
222
226
  color: var(--hc-color-text-primary);
223
227
  background-color: var(--hc-color-bg-muted);
224
228
  font-size: var(--hc-font-size-md);
@@ -237,8 +241,11 @@
237
241
  [data-hc-ds] .hc-ui-button:is([data-variant='secondary'], [data-variant='outline'], [data-variant='ghost'])[data-tone='danger'] { color: var(--hc-color-danger); }
238
242
  /* An open popup trigger: the 1.5px ring in the text color, as Chip (HCUI's Importar, Entrar and Convidar). */
239
243
  [data-hc-ds] .hc-ui-button:is([data-variant='secondary'], [data-variant='outline'], [data-variant='ghost'])[aria-haspopup]:not([aria-haspopup='false'])[aria-expanded='true'] { box-shadow: inset 0 0 0 1.5px var(--hc-color-text-primary); }
240
- [data-hc-ds] .hc-ui-button[data-size='sm'] { min-height: var(--hc-control-height-chip); padding: 0 var(--hc-space-4); font-size: var(--hc-font-size-sm); }
241
- [data-hc-ds] .hc-ui-button[data-size='lg'] { min-height: var(--hc-control-height-lg); padding: 0 var(--hc-space-6); font-size: var(--hc-font-size-base); }
244
+ [data-hc-ds] .hc-ui-button[data-size='sm'] { --hc-button-pad: var(--hc-space-4); --hc-button-pad-icon: var(--hc-space-3); min-height: var(--hc-control-height-chip); font-size: var(--hc-font-size-sm); }
245
+ [data-hc-ds] .hc-ui-button[data-size='lg'] { --hc-button-pad: var(--hc-space-6); --hc-button-pad-icon: var(--hc-space-5); min-height: var(--hc-control-height-lg); font-size: var(--hc-font-size-base); }
246
+ /* An icon (or the spinner) at a side takes a little less room than text (Material 3: 16 and 24 at 40). */
247
+ [data-hc-ds] .hc-ui-button:has(> :is(.hc-ui-button__icon, .hc-ui-button__spinner):first-child) { padding-inline-start: var(--hc-button-pad-icon); }
248
+ [data-hc-ds] .hc-ui-button:has(> .hc-ui-button__icon:last-child) { padding-inline-end: var(--hc-button-pad-icon); }
242
249
  [data-hc-ds] .hc-ui-button[data-full-width] { width: 100%; }
243
250
  [data-hc-ds] .hc-ui-button:disabled { opacity: 0.4; cursor: default; }
244
251
  [data-hc-ds] .hc-ui-button { max-width: 100%; }
@@ -297,11 +304,21 @@
297
304
  [data-hc-ds] .hc-ui-icon-button:disabled { opacity: 0.4; cursor: default; }
298
305
 
299
306
  /* Chip — HCUI chip / chip-active. */
307
+ /*
308
+ * Side space of a pill (Chip, Button, SegmentedControl): text needs more room than an icon or an
309
+ * avatar, because the round end eats into the text and a glyph is already round. A side with text
310
+ * gets about half the height (Material 3: 16/32 and 24/40; eBay Playbook: 16/32 and 20/40); a side
311
+ * that starts or ends with an icon gets a little less; an avatar sits concentric with the end.
312
+ */
300
313
  [data-hc-ds] .hc-ui-chip {
314
+ --hc-chip-pad: 16px;
315
+ --hc-chip-pad-icon: 12px;
316
+ --hc-chip-pad-avatar: 6px;
317
+ --hc-chip-avatar: 24px;
301
318
  flex: 0 0 auto;
302
319
  gap: 6px;
303
320
  min-height: var(--hc-control-height-chip);
304
- padding: 0 14px;
321
+ padding: 0 var(--hc-chip-pad);
305
322
  color: var(--hc-color-text-primary);
306
323
  background-color: var(--hc-color-bg-surface);
307
324
  box-shadow: inset 0 0 0 1px var(--hc-color-border-strong);
@@ -335,16 +352,33 @@
335
352
  font-variant-numeric: tabular-nums;
336
353
  }
337
354
  [data-hc-ds] .hc-ui-chip__chevron { display: inline-flex; flex: 0 0 auto; }
355
+ /* An icon or avatar at the start, a chevron or an inline badge at the end: less room on that side. */
356
+ [data-hc-ds] .hc-ui-chip:not([data-icon-only]):has(> .hc-ui-chip__icon:first-child) { padding-inline-start: var(--hc-chip-pad-icon); }
357
+ [data-hc-ds] .hc-ui-chip:not([data-icon-only]):has(> .hc-ui-chip__avatar:first-child) { padding-inline-start: var(--hc-chip-pad-avatar); }
358
+ [data-hc-ds] .hc-ui-chip:has(> :is(.hc-ui-chip__chevron, .hc-ui-chip__badge:not([data-placement='corner'])):last-child) { padding-inline-end: var(--hc-chip-pad-icon); }
359
+ /* Avatar: a round picture concentric with the pill's start (24px at 36px, 18px at 26px). */
360
+ [data-hc-ds] .hc-ui-chip__avatar {
361
+ display: inline-flex;
362
+ flex: 0 0 auto;
363
+ width: var(--hc-chip-avatar);
364
+ height: var(--hc-chip-avatar);
365
+ overflow: hidden;
366
+ border-radius: var(--hc-radius-full);
367
+ }
368
+ [data-hc-ds] .hc-ui-chip__avatar > :is(img, svg, .hc-ui-avatar) { display: block; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
338
369
  [data-hc-ds] .hc-ui-chip__chevron svg { display: block; }
339
370
  /* Applied filter, or the trigger of an open popup: a 1.5px ring (the selected fill below wins). */
340
371
  [data-hc-ds] .hc-ui-chip:is([data-applied], [aria-expanded='true']):not([aria-pressed='true'], [aria-checked='true'], [data-selected]) { box-shadow: inset 0 0 0 1.5px var(--hc-color-text-primary); }
341
372
  /* Line height `normal`, as HCUI's filter pills: on 2x screens 18px moves the text by half a pixel. */
342
- [data-hc-ds] .hc-ui-chip[data-size='sm'] { padding: 0 var(--hc-space-3); font-size: var(--hc-font-size-xs); line-height: normal; }
373
+ [data-hc-ds] .hc-ui-chip[data-size='sm'] { --hc-chip-pad: 14px; --hc-chip-pad-icon: 10px; font-size: var(--hc-font-size-xs); line-height: normal; }
343
374
  [data-hc-ds] .hc-ui-chip[data-size='xs'] {
344
375
  gap: var(--hc-space-1);
376
+ --hc-chip-pad: 10px;
377
+ --hc-chip-pad-icon: 7px;
378
+ --hc-chip-pad-avatar: 4px;
379
+ --hc-chip-avatar: 18px;
345
380
  min-height: 26px;
346
381
  height: 26px;
347
- padding: 0 var(--hc-space-2);
348
382
  background-color: var(--hc-color-bg-muted);
349
383
  box-shadow: none;
350
384
  font-size: var(--hc-font-size-2xs);
@@ -352,8 +386,9 @@
352
386
  [data-hc-ds] .hc-ui-chip[data-size='xs']:is([data-applied], [aria-expanded='true']):not([aria-pressed='true'], [aria-checked='true'], [data-selected]) { box-shadow: inset 0 0 0 1.5px var(--hc-color-text-primary); }
353
387
  /* Quiet: HCUI's rule filter pills, lighter until applied. */
354
388
  [data-hc-ds] .hc-ui-chip[data-variant='quiet'] {
389
+ --hc-chip-pad: 15px;
390
+ --hc-chip-pad-icon: 11px;
355
391
  gap: 5px;
356
- padding: 0 13px;
357
392
  color: var(--hc-color-text-secondary);
358
393
  box-shadow: inset 0 0 0 1px var(--hc-color-border);
359
394
  font-weight: 560;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ntmats/hc-ui",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "description": "Shared HCUI/Arena React presentation primitives.",
6
6
  "license": "UNLICENSED",