@scopetickets/icons
v0.2.1
Published
The Scope Tickets icon set and country flags, generated from STui.
Readme
@scopetickets/icons
The Scope Tickets icon set and country flags, generated from STui v1.0
(ot9Gya7jcroD6Ye2LMZITd).
Nothing here is traced, redrawn or approximated. Every path is the design system's own, fetched from the file and verified byte-for-byte against the export before generation.
icons 178 glyphs · 420 paths · all fills · 0 strokes, 0 gradients, 0 filters, 0 rasters
flags 32 countries · 19 vector assets · 0 rasters
status 8 statuses · 5 tones · every glyph one of the 178That audit is of the glyph DATA: every mark is filled paths and nothing else. The
gradient and the shadow that metal draws are a treatment the renderer applies on
top, exactly as the design system has it — the geometry carries neither.
Five entry points, because most consumers want one of them. The flags that carry a coat of arms are large — Spain's overlay alone is half a megabyte of path data — and nobody drawing a calendar should pay for that.
import { CalendarIcon, iconTokensCss } from '@scopetickets/icons';
import { CountryFlag } from '@scopetickets/icons/flags';
import { StatusTag } from '@scopetickets/icons/status';
<CalendarIcon size={48} appearance="metal" />
<CountryFlag country="Ireland" shape="round" size={40} />
<StatusTag status="Processing" /> // the tag, with its label
<StatusTag status="Processing" size="icon" fill /> // in an avatar's notchThe other two are for a renderer that is not this package's.
import { calendarPaths, ICON_TOKENS } from '@scopetickets/icons/paths';
import { IconGlyph } from '@scopetickets/icons/native'; // react-native-svg
<IconGlyph glyph="Calendar" paths={calendarPaths} size={48} appearance="metal" />;
// or draw it yourself — the colours ship with the geometry
const { solidFill, metalFill, metalGradientMax, metalGradientMin } = ICON_TOKENS.dark;./paths is the 178 path sets AND the colours that fill them, with nothing that
draws either — for a canvas, a sprite sheet, or a server rasterising to PNG.
ICON_TOKENS has a light and a dark set, and one set covers all 178 glyphs:
there is no per-glyph colour to look up. solid is solidFill on every path.
metal draws each path TWICE — flat in metalFill, then again in a
0,0 -> 20,20 gradient from metalGradientMax to metalGradientMin — under the
three-part shadow in METAL_SHADOWS — a drop shadow and two inner shadows, all
three lifted from STui v1.0's ScopeTicketsIcon (node 8865:9924). Those three are
the same on either ground: only the four colours they fall on change with the
mode. ./native draws them with
react-native-svg, from that same data: there is one copy of the geometry in this
package and both renderers read it.
Two differences are React Native's rather than this package's, and both are
props. The colours ARRIVE (tokens) because there is no cascade to read a custom
property from, and the spin is told whether to run (reduceMotion) because
nothing here will open an AccessibilityInfo subscription inside an icon. There
are no per-glyph components on the native side — ArrowDownIcon is a two-line
wrapper, and writing 178 of them would buy nothing over passing the paths.
React 18 is a peer dependency. react-native and react-native-svg are
optional peers: a web consumer installs neither and never resolves ./native.
Icons
See src/icons/README.md for the full picture: the two
appearances, the token chain for both modes, and the known gaps.
The short version: a glyph is geometry, and Solid and Metal are
treatments applied to it — Metal's filter is defined over the whole canvas in
absolute units and its gradient runs corner to corner of the glyph box, so
neither depends on which glyph it wraps. Colour comes from four CSS custom
properties (--scopetickets-icon-*), so one glyph serves both modes.
Flags
A flag is up to three things, in this order:
- a FlagBase — either a stack of solid bands (
rowfor vertical stripes,columnfor horizontal) or a full-bleed vector, for the cross flags whose geometry no stack of bands can express; - an overlay drawn across the whole flag (Malta, Portugal, Slovenia, Spain, Switzerland);
- a FlagEmblem, positioned (Croatia, Cyprus, Liechtenstein, Slovakia).
shape is round or square — in the file that is the clip and nothing else,
so it is a prop rather than two components.
Notes
FLAG_NAMESexists to override a display name where a variant key is not one. It is empty — the file'sLithuniatypo has been fixed at source — but the mechanism stays, so the next one is a one-line entry rather than a rename that drifts from the design system.- The set is the EU plus Iceland, Liechtenstein, Norway, Switzerland and the United Kingdom — 32 countries. There is no rest-of-world.
FLAGSusessatisfiesrather than a type annotation, so a country that is not in the file is a type error at the call site rather thanundefinedat runtime.
Status tags
OrderStatusTags (node 8608:12038) — eight statuses in five tones, at two
sizes. full is the tag with its label; icon is the glyph alone on a round
ground, which is what an avatar's status accessory holds.
Every one of its eight glyphs is an icon from this package's own set, matched byte for byte once normalised for the fact that the tag exports its glyphs at 14 where the set authors them at 20:
| status | tone | label | glyph |
| ----------------- | ---------- | -------------- | ---------------------------- |
| Paid | success | Paid | Check |
| Processing | processing | Pending | ArrowsCircling |
| Failed | error | Failed | XMarkInCircle |
| Refunded | warning | Refunded | ArrowReverse |
| PartiallyRefunded | warning | Part. Refunded | ArrowReverseWithTailDotted |
| Disputed | warning | Disputed | ExclamationMarkInCircle |
| PartiallyPaid | processing | Part. Paid | CheckInCircleDashed |
| Created | draft | Created | PlusInCircle |
Two things carried rather than tidied: Processing reads "Pending", and the
two partial statuses abbreviate to "Part.". Both are the file's own wording.
The padding is one token, and a length
$1, standardised at every size, with the glyph as the remainder:
standalone icon 30 box − 4×2 = 22 glyph
avatar $25 slot 48 box − 4×2 = 40 glyph
avatar $10 slot 20 box − 4×2 = 12 glyphSo fill grows the glyph rather than the ring. Scaling the padding with the box
instead would shrink the glyph as the tag grew, which is the wrong way round.
This departs from the reference file, deliberately. It draws $2 on the
standalone tag and on the avatar's $25 slot but $1 on the $10 slot — and
the $10 case is not self-consistent either, stating $1 across against $2
down on a 20 box, which cannot both hold. One token removes the discrepancy and
makes the ring read the same at every size. Worth reconciling in Figma.
Note the icon size's glyph is NOT the 14 the full size uses. There the glyph
is stated and the tag is sized by its content; here the box is given and the
glyph is what the padding leaves. The two are set differently on purpose.
Tones, both modes
Dark from the component (8608:12038), light from the light-mode grid
(8608:12607). Light is not an inversion: success and warning carry MORE
background opacity there (0.4 and 0.5 against 0.1 and 0.2) with much darker
foregrounds, because a saturated mid-tone that reads on near-black does not read
on near-white. error is the only tone whose background is identical in both.
Colour comes from ten CSS custom properties (--scopetickets-status-<tone>-bg / -fg);
statusTokensCss() emits them for both modes.
Re-syncing
Both sets are generated, not hand-written. Figma's asset URLs expire after about
a week, so a re-sync means re-fetching 8795:10532 (icons) and 11174:9692
(flags) through the MCP bridge and re-running the generators.
