@ntmats/hc-ui 0.3.0 → 0.4.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
@@ -6,6 +6,8 @@ React presentation primitives, with React and React DOM supplied by each consume
6
6
 
7
7
  ```ts
8
8
  import { Button, Checkbox, Chip, CommandPalette, Dialog, Field, FilterBar, IconButton, ListRow, Menu, MenuItem, MenuItemRadio, MenuRadioGroup, Meter, PageHeader, PanelHeader, Popover, SegmentedControl, Sheet, SheetChips, SheetOptions, Skeleton, SortMenu, Surface, Table } from '@hc/ui';
9
+ import { AvatarView, CombatDialView, EquipmentIcon, MiniDialView, PowerIconView, SetIconView, SpecialIconView, StatGlyphView, StatIcon } from '@hc/ui'; // HeroClix game pieces
10
+ import { MapBoardView, MapTooltipView, PhysicalCardFocusView, PhysicalCardView, inspectMap } from '@hc/ui'; // the card and the map
9
11
  import '@hc/ui/styles.css';
10
12
  import '@hc/tokens/css';
11
13
  ```
@@ -63,3 +65,21 @@ Focus rings read `--hc-focus-ring-offset` (default 2px). Set it to `-2px` on a c
63
65
  `Skeleton` is a static shape that pulses, hidden from assistive technology; the enclosing region announces loading once. Its base rule has zero specificity (`:where`), so the consumer sizes each shape with `width`/`height` or its own class.
64
66
 
65
67
  License: `UNLICENSED` (HCUI and Arena only). Component state and styles still need validation in Arena before a 1.0.
68
+
69
+ ## HeroClix game pieces (experimental)
70
+
71
+ The game pieces ship in `@hc/ui` itself (decision of 2026-11-06). They only draw: the caller (HCUI's adapters) reads the unit, applies the rules, picks colors and contrast, builds the image addresses (the official symbols stay on the image server and never enter the package) and passes localized labels. They never fetch, translate or import an icon library. They have the props of HCUI's `src/heroclix-ui/`.
72
+
73
+ `MiniDialView` draws the dial as small squares: one column per click (`columns`, each with `click` and the four `cells`: `color`, `round`, `special`), one row per value. Without a color a cell is the dial paper (`color-dial-paper`, white in both themes); a special power is the paper with a 1px `color-dial-outline` ring. `currentClick` outlines that click in the danger color and fades the earlier ones (in play). It is a `role="img"` named by `label`. The cell size is `--hc-mini-dial-cell` (7px), set on the dial or any ancestor (HCUI: 6px in tables, 5px in the narrow list); the 5px top margin has zero specificity.
74
+
75
+ `PowerIconView` is a circle in the power `color` with the black value symbol (`glyphSrc`) at 60%; `invertGlyph` turns the symbol white (`filter-symbol-light`) for dark colors. `StatGlyphView` is the symbol alone in neutral gray (`filter-symbol-muted`), 22px by default, with `alt`. `SpecialIconView` lays a symbol (`glyphSrc`, 22% inset) over the round white background (`backgroundSrc`) inside a thin dark border. `EquipmentIcon` is a white diamond on the purple power, decorative. `StatIcon` is an arrow, shield or diamond drawn in its own SVG in `currentColor` (not the official symbols). Sizes are `size` in px (24 by default).
76
+
77
+ `AvatarView` is the piece's portrait: it tries `sources` in order (a thumbnail, then the original) and, when none loads, shows `fallbackSrc` with `data-fallback` and the muted fill, calling `onFallback`. The image cannot be dragged, selected or opened in a context menu; it loads lazily. `shape`: `round` (default; the caller's class sizes and crops it), `card` (one-shot: the width ÷ 1.42, corners 13.6% / 19.4%), `square` (maps: square, 21% corners, cover) and `square-fit` (terrain markers: the whole image on the muted fill). The shapes beat the caller's class by specificity, as HCUI's `!important` rules did; display, `max-width` and `box-sizing` defaults have zero specificity. `size` (px) sizes it without a class, round when `round`. `SetIconView` tries `sources` the same way and renders nothing when none loads; it is decorative (`aria-hidden`), with an optional `title`. Both use `useImageChain(sources)`, exported for other image chains (`src`, `index`, `next`).
78
+
79
+ `CombatDialView` is the combat dial: the legend (range, target bolts and four symbols: `glyph` ones are `StatGlyphView`, others the piece's own combat symbol), one column per click (`columns`: `click`, `label`, `selectLabel`, `dimmed`, `startColor` and the four resolved `cells`: `value`, `label`, `background`, `color`, `round`, `outlined`, `tip`) and the KO column (`labels.koCell`), plus a header with `labels.heading`, the KO mark and the point value chips (`points`, `selectedPointIndex`, `onSelectPoint`; one value shows a disabled chip, none with a `valueLabel` hides the row). Without `onClickChange` it is static: no click is marked, cells without a tip are `role="img"`, and only cells with a power react. With it, the number of each click is a toggle button (`aria-pressed`) and every cell a button that also sets the click. The tip is controlled: the view decides when to open and close it (a mouse hover opens a passive tip described by the cell; a press, touch or Enter opens it as a dialog; leaving closes the passive one) and reports through `onTipChange`; the caller keeps `tip`, closes it when the piece changes and draws it in `tipSlot` with the `tipId` it gave. When the point value changes, the dial centers `focusClick` in its own track (never scrolling the page; instantly under reduced motion). Cells are 40px with 8px gaps; a cell with a tip grows its hit area to about 48px. `presentation="printed"` is the physical card's dial: no header, 20px cells in rows of 12 clicks, condensed numbers, red click numbers and KO with a paper outline (`color-dial-number-printed`), the white legend for the card's dark window and a red circle on the current click in play. `--hc-dial-bleed` (and `--hc-dial-bleed-end`) is the container's padding, so the tracks scroll to the panel's edge; `--hc-dial-surface` is the fill behind the sticky legend (the surface by default). The tracks hide their scrollbar, except on a desktop with a mouse (960px and up), where they draw a thin 8px one whose thumb shows while the pointer is over the track or focus is inside it. Point chips shrink on press and transitions stop under `prefers-reduced-motion`. It forwards its ref to the `<section>`.
80
+
81
+ Fixed colors that stay the same in both themes are HeroClix tokens: `color-dial-paper`, `color-dial-outline`, `color-dial-outline-printed`, `color-dial-number-printed`, `color-dial-legend-printed`, `color-symbol-outline` (`-soft`, `-strong`), `color-symbol-light`, and the filters `filter-symbol-light` and `filter-symbol-muted` that turn the black official symbols white or gray. Colors of powers come from the caller (HCUI's powers dictionary), so they are inline styles.
82
+
83
+ `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 (`nextDistribution`); 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 (`requestMotionAccess` 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`.
84
+
85
+ `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`, `elevationLevel`, `squareCoordinate`; `buildMapScene` (the 3D model's heights, walls, ramps and ziplines), the orthographic camera (`makeProjection`, `pickScene`, `zoomCamera`, `clampPan`) and `drawMapScene` (Canvas 2D). The 3D model's component, its gestures and its icons stay in the app.