@weft-js/warp-icons 0.0.0-stage → 0.1.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/LICENSE +16 -0
- package/README.md +27 -2
- package/compile.mjs +524 -0
- package/package.json +42 -4
- package/plugin.mjs +33 -0
- package/src/action.tsx +12 -0
- package/src/all.tsx +9 -0
- package/src/gen/actions/bell.css +9 -0
- package/src/gen/actions/bell.tsx +6 -0
- package/src/gen/actions/check.css +10 -0
- package/src/gen/actions/check.tsx +6 -0
- package/src/gen/actions/copy.css +9 -0
- package/src/gen/actions/copy.tsx +6 -0
- package/src/gen/actions/refresh.css +11 -0
- package/src/gen/actions/refresh.tsx +6 -0
- package/src/gen/actions/trash.css +9 -0
- package/src/gen/actions/trash.tsx +6 -0
- package/src/gen/all.tsx +1966 -0
- package/src/gen/index.tsx +5 -0
- package/src/gen/names.ts +6 -0
- package/src/icon.tsx +18 -0
- package/src/icons.css +29 -0
- package/src/late.tsx +36 -0
- package/src/lookup.tsx +9 -0
- package/src/names.tsx +12 -0
- package/src/set.tsx +13 -0
- package/src/svg.tsx +31 -0
- package/src/tiers.css +15 -0
package/src/gen/names.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Generated by scripts/build.mjs from art/*.mjs: do not edit.
|
|
2
|
+
export const names = ["accessibility","activity","alarm","alert","alert-circle","align-center","align-justify","align-left","align-right","ampersand","anchor","antenna","api","archive","area-chart","arrow-down","arrow-down-circle","arrow-down-left","arrow-down-right","arrow-down-square","arrow-left","arrow-left-circle","arrow-left-right","arrow-right","arrow-right-circle","arrow-up","arrow-up-circle","arrow-up-down","arrow-up-left","arrow-up-right","arrow-up-square","asterisk","at","at-sign","atom","attachment","award","badge","badge-check","bag","ban","bandage","bank","banknote","bar-chart","bar-chart-h","barcode","basket","bath","battery","battery-charging","battery-full","battery-low","beaker","bed","beer","bell","bell-check","bell-off","bell-plus","bell-ring","bike","binary","board","bold","bolt","book","book-closed","book-open","bookmark","bookmark-check","bookmark-plus","bookmark-x","books","bot","box","braces","brackets","brain","branch","breadcrumb","briefcase","broadcast","broom","brush","bug","bug-off","building","bullseye","bus","cake","calculator","calendar","cam","cam-off","camera","camp","candlestick","captions","car","card","cart","cart-check","cart-plus","cart-x","cast","chat","chat-check","chat-dots","chat-heart","chat-off","chat-plus","chat-x","chats","check","check-circle","check-square","checks","chevron-down-circle","chevron-left-circle","chevron-right-circle","chevron-up-circle","chevrons-down","chevrons-left","chevrons-right","chevrons-up","chevrons-updown","circle","clapper","clear","clipboard","clipboard-check","clipboard-list","clipboard-plus","clipboard-x","clock","cloud","cloud-check","cloud-down","cloud-drizzle","cloud-fog","cloud-lightning","cloud-moon","cloud-off","cloud-rain","cloud-snow","cloud-sun","cloud-up","code","code-block","coffee","coin","coins","columns","command","comment","comment-lines","commit","compass","compasses","contact","container","cookie","copy","copy-check","copy-plus","copyright","corner-down-left","corner-down-right","corner-right-down","corner-right-up","corner-up-left","corner-up-right","cpu","credit-chip","crop","crosshair","crown","cube","cubes","cup","cursor","cursor-text","database","database-lock","database-plus","deploy","desktop","diamond","dice","disc","discount","divide","dollar","donut-chart","door","dot-circle","down","download","drag","drive","droplet","droplets","dropper","dumbbell","ear","ear-off","earbuds","edit","edit-box","envelope","environment","equalizer","equals","eraser","euro","exclamation","expand","external","eye","eye-off","face-laugh","face-neutral","face-sad","face-wink","factory","fast-forward","file","file-audio","file-chart","file-check","file-code","file-down","file-image","file-lock","file-minus","file-pen","file-plus","file-search","file-text","file-up","file-video","file-x","file-zip","files","film","filter","fingerprint","first-page","fish","flag","flame","flash","flash-off","flask","flip-horizontal","flip-vertical","flower","focus","folder","folder-check","folder-down","folder-lock","folder-minus","folder-open","folder-plus","folder-tree","folder-up","folder-x","forward","fuel","fullscreen","function","funnel","gallery","gamepad","gauge","gem","ghost","gift","gift-card","git-commit","git-compare","git-fork","git-merge","git-pull","glasses","globe","graduation-cap","greater-than","grid","grip","grip-horizontal","hammer","hand","hand-coins","hand-helping","hand-wave","hard-drive","hash","hat","heading","headphones","heart","heart-off","heart-plus","heart-pulse","help","hexagon","hierarchy","highlighter","histogram","history","home","hospital","hourglass","house","id-card","image","image-off","image-plus","inbox","inbox-box","indent","infinity","info","invoice","italic","joystick","kanban","key","key-round","keyboard","lamp","laptop","last-page","layers","layers-plus","layout","leaf","leave","left","less-than","library","lightbulb","lightbulb-off","line-chart","link","list","list-ordered","loader","locate","lock","lock-open","log-in","log-list","log-out","magnet","mail","mail-check","mail-open","mail-plus","mail-x","map","map-fold","matrix","medal","medical-cross","megaphone","memory","mention","menu","message-question","mic","mic-off","microscope","milestone","minus","minus-circle","minus-square","monitor","moon","moon-star","more","more-vertical","mountain","mouse","move","music","music-note","navigation","network","newspaper","nodes","not-equal","note","notebook","octagon","outbox","outdent","package","package-open","paint-bucket","palette","panel","panel-bottom","panel-top","paperclip","paragraph","parchment","parking","pause","pause-circle","paw","pen","pen-line","pen-tool","pencil","pentagon","percent","phone","phone-call","phone-in","phone-off","phone-out","pi","picture-in-picture","pie-chart","piggy-bank","pill","pin","pin-off","pizza","plane","play","play-circle","playlist","plug","plus","plus-circle","plus-minus","plus-square","podcast","pointer","pound","power","present","printer","projector","pulse","puzzle","qr-code","question","quote","rack","radio","radioactive","rainbow","receipt","record","recycle","redo","refresh","regex","registered","remote","repeat","repeat-one","reply","reply-all","rewind","right","rocket","rotate-ccw","rotate-cw","route","router","rss","ruler","safe","save","scale","scan","scatter-chart","school","scissors","scissors-line","scooter","screen","screwdriver","scroll","sd-card","search","search-x","section","send","server","settings","shapes","share","share-nodes","sheet","shield","shield-alert","shield-check","shield-off","shield-plus","shield-x","ship","shirt","shoe","shrink","shuffle","sidebar-left","sidebar-right","sigma","signal","signal-low","signpost","skip-back","skip-forward","skull","slash","sliders","smartphone","smile","snowflake","sofa","sort","sort-asc","sort-desc","sparkle","speaker","speed","spiral","sprout","square","stack","stacked-bars","stairs","stamp","star","star-half","stars","stethoscope","sticky-note","stop","stop-circle","stopwatch","store","strike","subtitles","sun","sunrise","sunset","swap","swap-vertical","sync","syringe","tab","table","table-columns","table-rows","tablet","tag","tape","target","team","tent","terminal","terminal-square","test-tube","text-cursor","thermometer","thumbs-down","thumbs-up","thumbtack","ticket","timer","to-bottom","to-top","toggle-off","toggle-on","token","tornado","traffic-cone","train","translate","trash","trash-x","tree","tree-round","trending-down","trending-up","triangle","trophy","truck","turn-left","turn-right","tv","type","u-turn","umbrella","underline","undo","unlink","unlock","unplug","up","upload","usb","user","user-check","user-circle","user-cog","user-minus","user-plus","user-search","user-x","users","utensils","variable","video","voicemail","volume","volume-low","volume-mute","volume-off","wallet","wand","warehouse","watch","waveform","waves","weave","webcam","webhook","wifi","wifi-low","wifi-off","wind","window","windows","wine","wrench","x","x-circle","x-square","yen","zoom-in","zoom-out"] as const
|
|
3
|
+
export type IconName = (typeof names)[number]
|
|
4
|
+
export type IconVariant = 'solid' | 'action'
|
|
5
|
+
export const actions = ["trash","bell","check","refresh","copy"] as const
|
|
6
|
+
export type IconActionName = (typeof actions)[number]
|
package/src/icon.tsx
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// `<Icon name>`. Literal names compile to the icon itself; what reaches this file is a name the build could only bound,
|
|
2
|
+
// or not bound at all, and @weft-js/warp-icons/names holds exactly those.
|
|
3
|
+
import { resolve, type IconActionName, type IconName, type IconVariant } from '@weft-js/warp-icons/names'
|
|
4
|
+
import { untrack } from '@weft-js/core'
|
|
5
|
+
import type { IconTrigger } from './action'
|
|
6
|
+
import type { IconComponent, IconProps } from './svg'
|
|
7
|
+
|
|
8
|
+
export type { IconName, IconVariant }
|
|
9
|
+
/** An icon by name, or the icon itself. */
|
|
10
|
+
export type IconRef = IconName | IconComponent
|
|
11
|
+
/** `<Icon>`'s props: a name, and `solid` or `action` with a name that has one. */
|
|
12
|
+
export type IconByNameProps = ({ name: IconRef; solid?: boolean; action?: undefined } | { name: IconActionName; action: IconTrigger | boolean; solid?: undefined }) & IconProps
|
|
13
|
+
|
|
14
|
+
/** An icon by name. A literal name compiles to the icon itself; anything else is looked up at runtime. */
|
|
15
|
+
export function Icon({ name, solid, ...rest }: IconByNameProps) {
|
|
16
|
+
const Draw = untrack(() => (typeof name === 'function' ? name : resolve(name, rest.action ? 'action' : solid ? 'solid' : undefined)))
|
|
17
|
+
return Draw ? <Draw {...rest} /> : null
|
|
18
|
+
}
|
package/src/icons.css
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* Every @weft-js/warp-icons svg, with or without warp. warp's own base rule says the same; this one lets the set stand alone. */
|
|
2
|
+
/* warp's layer order, first: a layer ranks by when the page first names it, and this file can load before warp.css. */
|
|
3
|
+
@layer warp.tokens, warp.base, warp.signature, warp.layout, warp.components, warp.inputs, warp.inputs-rich, warp.data, warp.app, warp.flows, warp.marketing, warp.canvas, warp.flow;
|
|
4
|
+
@layer warp.components {
|
|
5
|
+
:where(svg[data-icon]) { inline-size: var(--warp-icon-md, 1em); block-size: var(--warp-icon-md, 1em); flex: none; fill: none; stroke: currentColor; stroke-width: var(--warp-icon-stroke, 1.5); stroke-linecap: round; stroke-linejoin: round; }
|
|
6
|
+
/* weights: each tier has its own, on the path, so they hold when a build compiles the tier switch out */
|
|
7
|
+
:where(svg[data-icon] [data-qs]) { stroke-width: 1; }
|
|
8
|
+
:where(svg[data-icon] [data-bs]) { stroke-width: 2; }
|
|
9
|
+
:where(svg[data-icon] [data-bw]) { stroke-width: 2.5; }
|
|
10
|
+
/* the weft, the stroke that says what the icon means: colour it with --warp-icon-weft-color */
|
|
11
|
+
:where(svg[data-icon] [data-w]) { stroke: var(--warp-icon-weft-color, currentColor); }
|
|
12
|
+
/* pixels: a 16 grid in the 24 box. In forced colours currentColor is the system's, so these follow it. */
|
|
13
|
+
:where(svg[data-icon] [data-p], svg[data-icon] [data-c]) { stroke: none; transform: scale(1.5); shape-rendering: crispEdges; }
|
|
14
|
+
:where(svg[data-icon] [data-p]) { fill: currentColor; }
|
|
15
|
+
:where(svg[data-icon] [data-p][data-w]) { fill: var(--warp-icon-weft-color, currentColor); }
|
|
16
|
+
/* An icon picks its form from its own size (10 §6). Dither is depth, and at 16 px it reads as noise, so it needs 20. */
|
|
17
|
+
:where(svg[data-icon]) { container-type: size; }
|
|
18
|
+
:where(svg[data-icon] [data-d]) { opacity: 0; }
|
|
19
|
+
@container (min-width: 20px) { :where(svg[data-icon] [data-d]) { opacity: 1; } }
|
|
20
|
+
/* solid: a filled body, its offset shade, and what's cut from it */
|
|
21
|
+
:where(svg[data-icon] [data-s]) { fill: currentColor; stroke: none; }
|
|
22
|
+
:where(svg[data-icon] [data-sh]) { stroke: currentColor; stroke-width: 1.5; }
|
|
23
|
+
:where(svg[data-icon] [data-so]) { fill: currentColor; fill-opacity: .3; stroke: none; }
|
|
24
|
+
:where(svg[data-icon] [data-k]) { stroke: var(--warp-icon-knock, Canvas); stroke-width: 2.5; }
|
|
25
|
+
:where(svg[data-icon] [data-k][data-qs]) { stroke-width: 1.5; }
|
|
26
|
+
:where(svg[data-icon] [data-hi]) { stroke-width: 1.25; }
|
|
27
|
+
:where(svg[data-icon] [data-c]) { fill: var(--warp-icon-knock, Canvas); }
|
|
28
|
+
@media (forced-colors: active) { :where(svg[data-icon] [data-so]) { display: none; } }
|
|
29
|
+
}
|
package/src/late.tsx
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// A name only known at runtime: the icon holds its box while it loads, on the server too, and fills in on the client. If it
|
|
2
|
+
// can't load, the box stays, marked data-failed, and the page carries on.
|
|
3
|
+
import { state } from '@weft-js/core'
|
|
4
|
+
import type { IconComponent, IconProps } from './svg'
|
|
5
|
+
import { icon, Svg } from './svg'
|
|
6
|
+
|
|
7
|
+
type Lookup = (name: string, variant?: string) => IconComponent | undefined | Promise<IconComponent | undefined>
|
|
8
|
+
const held = new Map<string, IconComponent>()
|
|
9
|
+
|
|
10
|
+
function deferred(key: string, find: () => Promise<IconComponent | undefined>): IconComponent {
|
|
11
|
+
let made = held.get(key)
|
|
12
|
+
if (made) return made
|
|
13
|
+
let got: Promise<IconComponent | undefined> | undefined
|
|
14
|
+
made = (props: IconProps) => {
|
|
15
|
+
const ready = state<IconComponent | null>(null), failed = state(false)
|
|
16
|
+
if (typeof window !== 'undefined') (got ??= find()).then(i => { if (i) ready.value = i; else failed.value = true }, () => { got = undefined; failed.value = true })
|
|
17
|
+
return <>{ready.value ? ready.value(props) : <Svg {...props} data-loading={failed.value ? undefined : ''} data-failed={failed.value ? '' : undefined} html="" />}</>
|
|
18
|
+
}
|
|
19
|
+
held.set(key, made)
|
|
20
|
+
return made
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let loaded: Promise<Lookup> | undefined
|
|
24
|
+
/** From the pack, a chunk of its own: the first use on a page fetches it. */
|
|
25
|
+
export const late = (name: string, variant: string | undefined, pack: () => Promise<Lookup>) => deferred(`${name}:${variant ?? ''}`, async () => (await (loaded ??= pack()))(name, variant))
|
|
26
|
+
|
|
27
|
+
// Only paths go in, with their d and data attributes: what a remote host sends is data, never markup that can run.
|
|
28
|
+
const clean = (svg: string) => (svg.match(/<path(?:\s+data-[a-z]+(?:="[^"<>]*")?|\s+d="[^"<>]*"|\s+transform="translate\([\d. ]+\)")*\s*\/>/g) ?? []).join('')
|
|
29
|
+
|
|
30
|
+
/** From a host of static files (`weft icons publish`): one small fetch per icon. */
|
|
31
|
+
export const remote = (source: string) => (name: string, variant?: string) => deferred(`${name}:${variant ?? ''}`, async () => {
|
|
32
|
+
const response = await fetch(`${source}${name}${variant ? `.${variant}` : ''}.svg`)
|
|
33
|
+
if (!response.ok) return undefined
|
|
34
|
+
const html = clean(await response.text())
|
|
35
|
+
return html ? icon(html) : undefined
|
|
36
|
+
})
|
package/src/lookup.tsx
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// An icon by name. Apart from all.tsx so that names.tsx can use it without going through `Icon`, which imports names.tsx.
|
|
2
|
+
import * as all from '@weft-js/warp-icons'
|
|
3
|
+
import type { IconComponent } from '@weft-js/warp-icons'
|
|
4
|
+
|
|
5
|
+
const table = all as unknown as Record<string, IconComponent>
|
|
6
|
+
const title = (name: string) => name.replace(/(^|-)(.)/g, (_, __, c) => c.toUpperCase())
|
|
7
|
+
|
|
8
|
+
/** The icon of a kebab-case name, in `solid` or `action` if asked: undefined for a name nobody drew. */
|
|
9
|
+
export const lookup = (name: string, variant?: string): IconComponent | undefined => table[`${title(name)}Icon${variant ? variant[0]!.toUpperCase() + variant.slice(1) : ''}`]
|
package/src/names.tsx
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// A name to its icon. This file is what dev and tests see, with every icon in. A build swaps it for a module holding only
|
|
2
|
+
// the names the app uses (compile.mjs), so nothing here reaches production unless a name is truly dynamic.
|
|
3
|
+
import * as all from '@weft-js/warp-icons'
|
|
4
|
+
import type { IconComponent } from '@weft-js/warp-icons'
|
|
5
|
+
import type { IconActionName, IconName, IconVariant } from './gen/names'
|
|
6
|
+
import { lookup } from './lookup'
|
|
7
|
+
|
|
8
|
+
export type { IconActionName, IconName, IconVariant }
|
|
9
|
+
/** The icon of a name, or undefined. A build's version knows only the names the app uses. */
|
|
10
|
+
export const resolve = lookup
|
|
11
|
+
/** `Icons[id]` for an id only known at runtime. The build's version does the same over what it kept. */
|
|
12
|
+
export const pack = (id: string) => (all as unknown as Record<string, IconComponent>)[id]
|
package/src/set.tsx
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// `<Icon name>` over the icons you list: a name is a lookup, so only a list can stay tree-shakable. `@weft-js/warp-icons/all` is
|
|
2
|
+
// the same thing over every icon, for an app that takes names from data. An icon itself passes straight through.
|
|
3
|
+
import { untrack } from '@weft-js/core'
|
|
4
|
+
import type { IconComponent, IconProps } from './svg'
|
|
5
|
+
|
|
6
|
+
/** An `<Icon>` over these icons only: names stay typed, and whatever isn't listed tree-shakes away. */
|
|
7
|
+
export function iconSet<const S extends Record<string, IconComponent>>(icons: S) {
|
|
8
|
+
return function Icon({ name, ...rest }: { name: (keyof S & string) | IconComponent } & IconProps) {
|
|
9
|
+
const icon = untrack(() => name)
|
|
10
|
+
const Draw: IconComponent = typeof icon === 'function' ? icon : icons[icon]!
|
|
11
|
+
return <Draw {...rest} />
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/svg.tsx
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// The one <svg> every icon renders. An icon is its drawing as a string, so it costs that string and nothing per icon.
|
|
2
|
+
// Decorative unless it is given a `label`.
|
|
3
|
+
import type { JSX } from '@weft-js/core/jsx-runtime'
|
|
4
|
+
import './icons.css'
|
|
5
|
+
|
|
6
|
+
/** What every icon takes. Anything else lands on the `<svg>`. */
|
|
7
|
+
export type IconProps = {
|
|
8
|
+
/** An accessible name: the icon becomes role="img". Without one it is aria-hidden. */
|
|
9
|
+
label?: string | undefined
|
|
10
|
+
/** Width and height, in px or any CSS length. warp's own sizes apply otherwise. */
|
|
11
|
+
size?: number | string | undefined
|
|
12
|
+
/** Show this tier whatever the page's expression is. */
|
|
13
|
+
tier?: 'quiet' | 'balanced' | 'vivid' | undefined
|
|
14
|
+
/** Inline CSS. `size` goes in front of it. */
|
|
15
|
+
style?: string | undefined
|
|
16
|
+
[attr: string]: unknown
|
|
17
|
+
}
|
|
18
|
+
/** One icon. Each is a plain component, so an unused one tree-shakes. */
|
|
19
|
+
export type IconComponent = (props: IconProps) => JSX.Element
|
|
20
|
+
|
|
21
|
+
const length = (size: number | string) => (typeof size === 'number' ? `${size}px` : size)
|
|
22
|
+
|
|
23
|
+
export function Svg({ label, size, tier, html, style, ...rest }: IconProps & { html: string }) {
|
|
24
|
+
// oxlint-disable-next-line weft/no-html-sink -- `html` is this package's generated art: `icon` and `acting` set it after the props spread, so no caller reaches it
|
|
25
|
+
return <svg data-icon data-expression={tier} viewBox="0 0 24 24" role={label ? 'img' : undefined} aria-label={label} aria-hidden={label ? undefined : 'true'} focusable="false" style={size === undefined ? style : `width:${length(size)};height:${length(size)}${style ? `;${style}` : ''}`} innerHTML={html} {...rest} />
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/*#__NO_SIDE_EFFECTS__*/
|
|
29
|
+
export function icon(line: string, pixels = ''): IconComponent {
|
|
30
|
+
return p => <Svg {...p} html={line + pixels} />
|
|
31
|
+
}
|
package/src/tiers.css
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* The runtime tier switch. warp's expression stops set these four properties, so the nearest data-expression wins with
|
|
2
|
+
no script and no reflow; a page without warp shows balanced. */
|
|
3
|
+
/* warp's layer order, first: a layer ranks by when the page first names it, and this file can load before warp.css. */
|
|
4
|
+
@layer warp.tokens, warp.base, warp.signature, warp.layout, warp.components, warp.inputs, warp.inputs-rich, warp.data, warp.app, warp.flows, warp.marketing, warp.canvas, warp.flow;
|
|
5
|
+
@layer warp.components {
|
|
6
|
+
:where(svg[data-icon] [data-t="o"]) { display: var(--warp-icon-o, inline); }
|
|
7
|
+
:where(svg[data-icon] [data-t="q"]) { display: var(--warp-icon-q, none); }
|
|
8
|
+
:where(svg[data-icon] [data-t="b"]) { display: var(--warp-icon-b, inline); }
|
|
9
|
+
:where(svg[data-icon] [data-t="v"]) { display: var(--warp-icon-v, none); }
|
|
10
|
+
/* Pixels need whole cells: under 16 px they lose rows, so the pixel tier shows the balanced line there (--warp-icon-fine). */
|
|
11
|
+
@container (max-width: 15.5px) {
|
|
12
|
+
:where(svg[data-icon] [data-t="b"]) { display: var(--warp-icon-fine, var(--warp-icon-b, inline)); }
|
|
13
|
+
:where(svg[data-icon] [data-t="v"]) { display: none; }
|
|
14
|
+
}
|
|
15
|
+
}
|