@weft-js/warp-icons 0.1.0 → 0.1.2
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 +11 -4
- package/compile.mjs +267 -399
- package/package.json +12 -3
- package/plugin.mjs +33 -7
- package/src/action.tsx +7 -1
- package/src/all.tsx +4 -1
- package/src/compile-catalog.mjs +96 -0
- package/src/compile-closest.mjs +19 -0
- package/src/compile-names.mjs +33 -0
- package/src/compile-pascal.mjs +4 -0
- package/src/compile-read.mjs +227 -0
- package/src/compile-resolver.mjs +169 -0
- package/src/compile-rewrite-pack.mjs +39 -0
- package/src/compile-unwrap.mjs +19 -0
- package/src/gen/actions/bell.tsx +1 -1
- package/src/gen/actions/check.tsx +1 -1
- package/src/gen/actions/copy.tsx +1 -1
- package/src/gen/actions/refresh.tsx +1 -1
- package/src/gen/actions/trash.tsx +1 -1
- package/src/gen/all-accessibility-bus.tsx +274 -0
- package/src/gen/all-cake-cursor.tsx +284 -0
- package/src/gen/all-database-funnel.tsx +276 -0
- package/src/gen/all-gallery-logout.tsx +252 -0
- package/src/gen/all-magnet-puzzle.tsx +285 -0
- package/src/gen/all-qr-syringe.tsx +328 -0
- package/src/gen/all-tab-zoom.tsx +280 -0
- package/src/gen/all.tsx +8 -1959
- package/src/icon.tsx +16 -3
- package/src/icons.css +16 -3
- package/src/late.tsx +46 -12
- package/src/lookup.tsx +2 -1
- package/src/set.tsx +4 -1
- package/src/svg.tsx +23 -4
- package/src/tiers.css +1 -0
package/src/icon.tsx
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// `<Icon name>`. Literal names compile to the icon itself; what reaches this file is a name the build could only bound,
|
|
2
2
|
// or not bound at all, and @weft-js/warp-icons/names holds exactly those.
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
resolve,
|
|
5
|
+
type IconActionName,
|
|
6
|
+
type IconName,
|
|
7
|
+
type IconVariant,
|
|
8
|
+
} from '@weft-js/warp-icons/names'
|
|
4
9
|
import { untrack } from '@weft-js/core'
|
|
5
10
|
import type { IconTrigger } from './action'
|
|
6
11
|
import type { IconComponent, IconProps } from './svg'
|
|
@@ -9,10 +14,18 @@ export type { IconName, IconVariant }
|
|
|
9
14
|
/** An icon by name, or the icon itself. */
|
|
10
15
|
export type IconRef = IconName | IconComponent
|
|
11
16
|
/** `<Icon>`'s props: a name, and `solid` or `action` with a name that has one. */
|
|
12
|
-
export type IconByNameProps = (
|
|
17
|
+
export type IconByNameProps = (
|
|
18
|
+
| { name: IconRef; solid?: boolean; action?: undefined }
|
|
19
|
+
| { name: IconActionName; action: IconTrigger | boolean; solid?: undefined }
|
|
20
|
+
) &
|
|
21
|
+
IconProps
|
|
13
22
|
|
|
14
23
|
/** An icon by name. A literal name compiles to the icon itself; anything else is looked up at runtime. */
|
|
15
24
|
export function Icon({ name, solid, ...rest }: IconByNameProps) {
|
|
16
|
-
const Draw = untrack(() =>
|
|
25
|
+
const Draw = untrack(() =>
|
|
26
|
+
typeof name === 'function'
|
|
27
|
+
? name
|
|
28
|
+
: resolve(name, rest.action ? 'action' : solid ? 'solid' : undefined),
|
|
29
|
+
)
|
|
17
30
|
return Draw ? <Draw {...rest} /> : null
|
|
18
31
|
}
|
package/src/icons.css
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
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
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, app, theme, base, components, utilities;
|
|
3
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;
|
|
4
5
|
@layer warp.components {
|
|
5
6
|
: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; }
|
|
@@ -7,23 +8,35 @@
|
|
|
7
8
|
:where(svg[data-icon] [data-qs]) { stroke-width: 1; }
|
|
8
9
|
:where(svg[data-icon] [data-bs]) { stroke-width: 2; }
|
|
9
10
|
:where(svg[data-icon] [data-bw]) { stroke-width: 2.5; }
|
|
11
|
+
/* hollow's inside mark: lighter than the body round it */
|
|
12
|
+
:where(svg[data-icon] [data-bi]) { stroke-width: 1.5; }
|
|
10
13
|
/* the weft, the stroke that says what the icon means: colour it with --warp-icon-weft-color */
|
|
11
14
|
:where(svg[data-icon] [data-w]) { stroke: var(--warp-icon-weft-color, currentColor); }
|
|
12
15
|
/* pixels: a 16 grid in the 24 box. In forced colours currentColor is the system's, so these follow it. */
|
|
13
16
|
:where(svg[data-icon] [data-p], svg[data-icon] [data-c]) { stroke: none; transform: scale(1.5); shape-rendering: crispEdges; }
|
|
14
17
|
:where(svg[data-icon] [data-p]) { fill: currentColor; }
|
|
15
18
|
: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
|
-
|
|
19
|
+
/* 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.
|
|
20
|
+
Only an icon that has dither measures itself: a size container is a layout of its own, and a list of 200 rows with
|
|
21
|
+
an icon each spent 70 ms (4x CPU) laying them out. */
|
|
22
|
+
:where(svg[data-icon][data-dither]) { container-type: size; }
|
|
23
|
+
/* A hollow mark measures itself too: under 20 px its gap widens (forms.mjs), or it would close up and read as solid. */
|
|
24
|
+
:where(svg[data-icon]:has(> mask)) { container-type: size; }
|
|
25
|
+
:where(svg[data-icon] [data-m]) { display: none; }
|
|
26
|
+
@container (max-width: 19.5px) { :where(svg[data-icon] [data-m]) { display: inline; } }
|
|
18
27
|
:where(svg[data-icon] [data-d]) { opacity: 0; }
|
|
19
28
|
@container (min-width: 20px) { :where(svg[data-icon] [data-d]) { opacity: 1; } }
|
|
20
29
|
/* solid: a filled body, its offset shade, and what's cut from it */
|
|
21
30
|
:where(svg[data-icon] [data-s]) { fill: currentColor; stroke: none; }
|
|
22
31
|
:where(svg[data-icon] [data-sh]) { stroke: currentColor; stroke-width: 1.5; }
|
|
32
|
+
/* an open mark's closed shapes, filled in solid */
|
|
33
|
+
:where(svg[data-icon] [data-f]) { fill: currentColor; }
|
|
34
|
+
:where(svg[data-icon] [data-f][data-w]) { fill: var(--warp-icon-weft-color, currentColor); }
|
|
23
35
|
:where(svg[data-icon] [data-so]) { fill: currentColor; fill-opacity: .3; stroke: none; }
|
|
24
36
|
:where(svg[data-icon] [data-k]) { stroke: var(--warp-icon-knock, Canvas); stroke-width: 2.5; }
|
|
25
37
|
:where(svg[data-icon] [data-k][data-qs]) { stroke-width: 1.5; }
|
|
26
38
|
:where(svg[data-icon] [data-hi]) { stroke-width: 1.25; }
|
|
27
39
|
:where(svg[data-icon] [data-c]) { fill: var(--warp-icon-knock, Canvas); }
|
|
28
|
-
|
|
40
|
+
:where(svg[data-icon] [data-sx]) { stroke-opacity: .3; }
|
|
41
|
+
@media (forced-colors: active) { :where(svg[data-icon] [data-so], svg[data-icon] [data-sx]) { display: none; } }
|
|
29
42
|
}
|
package/src/late.tsx
CHANGED
|
@@ -4,7 +4,10 @@ import { state } from '@weft-js/core'
|
|
|
4
4
|
import type { IconComponent, IconProps } from './svg'
|
|
5
5
|
import { icon, Svg } from './svg'
|
|
6
6
|
|
|
7
|
-
type Lookup = (
|
|
7
|
+
type Lookup = (
|
|
8
|
+
name: string,
|
|
9
|
+
variant?: string,
|
|
10
|
+
) => IconComponent | undefined | Promise<IconComponent | undefined>
|
|
8
11
|
const held = new Map<string, IconComponent>()
|
|
9
12
|
|
|
10
13
|
function deferred(key: string, find: () => Promise<IconComponent | undefined>): IconComponent {
|
|
@@ -12,9 +15,33 @@ function deferred(key: string, find: () => Promise<IconComponent | undefined>):
|
|
|
12
15
|
if (made) return made
|
|
13
16
|
let got: Promise<IconComponent | undefined> | undefined
|
|
14
17
|
made = (props: IconProps) => {
|
|
15
|
-
const ready = state<IconComponent | null>(null),
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
const ready = state<IconComponent | null>(null),
|
|
19
|
+
failed = state(false)
|
|
20
|
+
if (typeof window !== 'undefined')
|
|
21
|
+
(got ??= find()).then(
|
|
22
|
+
(i) => {
|
|
23
|
+
if (i) ready.value = i
|
|
24
|
+
else failed.value = true
|
|
25
|
+
},
|
|
26
|
+
() => {
|
|
27
|
+
got = undefined
|
|
28
|
+
failed.value = true
|
|
29
|
+
},
|
|
30
|
+
)
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
{ready.value ? (
|
|
34
|
+
ready.value(props)
|
|
35
|
+
) : (
|
|
36
|
+
<Svg
|
|
37
|
+
{...props}
|
|
38
|
+
data-loading={failed.value ? undefined : ''}
|
|
39
|
+
data-failed={failed.value ? '' : undefined}
|
|
40
|
+
html=""
|
|
41
|
+
/>
|
|
42
|
+
)}
|
|
43
|
+
</>
|
|
44
|
+
)
|
|
18
45
|
}
|
|
19
46
|
held.set(key, made)
|
|
20
47
|
return made
|
|
@@ -22,15 +49,22 @@ function deferred(key: string, find: () => Promise<IconComponent | undefined>):
|
|
|
22
49
|
|
|
23
50
|
let loaded: Promise<Lookup> | undefined
|
|
24
51
|
/** 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>) =>
|
|
52
|
+
export const late = (name: string, variant: string | undefined, pack: () => Promise<Lookup>) =>
|
|
53
|
+
deferred(`${name}:${variant ?? ''}`, async () => (await (loaded ??= pack()))(name, variant))
|
|
26
54
|
|
|
27
55
|
// 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) =>
|
|
56
|
+
const clean = (svg: string) =>
|
|
57
|
+
(
|
|
58
|
+
svg.match(
|
|
59
|
+
/<path(?:\s+data-[a-z]+(?:="[^"<>]*")?|\s+d="[^"<>]*"|\s+transform="translate\([\d. ]+\)")*\s*\/>/g,
|
|
60
|
+
) ?? []
|
|
61
|
+
).join('')
|
|
29
62
|
|
|
30
63
|
/** From a host of static files (`weft icons publish`): one small fetch per icon. */
|
|
31
|
-
export const remote = (source: string) => (name: string, variant?: string) =>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
64
|
+
export const remote = (source: string) => (name: string, variant?: string) =>
|
|
65
|
+
deferred(`${name}:${variant ?? ''}`, async () => {
|
|
66
|
+
const response = await fetch(`${source}${name}${variant ? `.${variant}` : ''}.svg`)
|
|
67
|
+
if (!response.ok) return undefined
|
|
68
|
+
const html = clean(await response.text())
|
|
69
|
+
return html ? icon(html) : undefined
|
|
70
|
+
})
|
package/src/lookup.tsx
CHANGED
|
@@ -6,4 +6,5 @@ const table = all as unknown as Record<string, IconComponent>
|
|
|
6
6
|
const title = (name: string) => name.replace(/(^|-)(.)/g, (_, __, c) => c.toUpperCase())
|
|
7
7
|
|
|
8
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 =>
|
|
9
|
+
export const lookup = (name: string, variant?: string): IconComponent | undefined =>
|
|
10
|
+
table[`${title(name)}Icon${variant ? variant[0]!.toUpperCase() + variant.slice(1) : ''}`]
|
package/src/set.tsx
CHANGED
|
@@ -5,7 +5,10 @@ import type { IconComponent, IconProps } from './svg'
|
|
|
5
5
|
|
|
6
6
|
/** An `<Icon>` over these icons only: names stay typed, and whatever isn't listed tree-shakes away. */
|
|
7
7
|
export function iconSet<const S extends Record<string, IconComponent>>(icons: S) {
|
|
8
|
-
return function Icon({
|
|
8
|
+
return function Icon({
|
|
9
|
+
name,
|
|
10
|
+
...rest
|
|
11
|
+
}: { name: (keyof S & string) | IconComponent } & IconProps) {
|
|
9
12
|
const icon = untrack(() => name)
|
|
10
13
|
const Draw: IconComponent = typeof icon === 'function' ? icon : icons[icon]!
|
|
11
14
|
return <Draw {...rest} />
|
package/src/svg.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import './icons.css'
|
|
|
7
7
|
export type IconProps = {
|
|
8
8
|
/** An accessible name: the icon becomes role="img". Without one it is aria-hidden. */
|
|
9
9
|
label?: string | undefined
|
|
10
|
-
/** Width and height, in px or any CSS length.
|
|
10
|
+
/** Width and height, in px or any CSS length. Warp's own sizes apply otherwise. */
|
|
11
11
|
size?: number | string | undefined
|
|
12
12
|
/** Show this tier whatever the page's expression is. */
|
|
13
13
|
tier?: 'quiet' | 'balanced' | 'vivid' | undefined
|
|
@@ -21,11 +21,30 @@ export type IconComponent = (props: IconProps) => JSX.Element
|
|
|
21
21
|
const length = (size: number | string) => (typeof size === 'number' ? `${size}px` : size)
|
|
22
22
|
|
|
23
23
|
export function Svg({ label, size, tier, html, style, ...rest }: IconProps & { html: string }) {
|
|
24
|
-
// oxlint-disable
|
|
25
|
-
return
|
|
24
|
+
// oxlint-disable 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 (
|
|
26
|
+
<svg
|
|
27
|
+
data-icon
|
|
28
|
+
data-expression={tier}
|
|
29
|
+
viewBox="0 0 24 24"
|
|
30
|
+
role={label ? 'img' : undefined}
|
|
31
|
+
aria-label={label}
|
|
32
|
+
aria-hidden={label ? undefined : 'true'}
|
|
33
|
+
focusable="false"
|
|
34
|
+
style={
|
|
35
|
+
size === undefined
|
|
36
|
+
? style
|
|
37
|
+
: `width:${length(size)};height:${length(size)}${style ? `;${style}` : ''}`
|
|
38
|
+
}
|
|
39
|
+
innerHTML={html} /* oxlint-enable weft/no-html-sink */
|
|
40
|
+
{...rest}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
/*#__NO_SIDE_EFFECTS__*/
|
|
29
46
|
export function icon(line: string, pixels = ''): IconComponent {
|
|
30
|
-
|
|
47
|
+
// Dither shows from 20 px, which the icon learns by measuring itself (icons.css): only one that has it asks.
|
|
48
|
+
const dither = /\sdata-d=/.test(pixels) || /\sdata-d=/.test(line) || undefined
|
|
49
|
+
return (p) => <Svg data-dither={dither} {...p} html={line + pixels} />
|
|
31
50
|
}
|
package/src/tiers.css
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* The runtime tier switch. warp's expression stops set these four properties, so the nearest data-expression wins with
|
|
2
2
|
no script and no reflow; a page without warp shows balanced. */
|
|
3
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, app, theme, base, components, utilities;
|
|
4
5
|
@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
6
|
@layer warp.components {
|
|
6
7
|
:where(svg[data-icon] [data-t="o"]) { display: var(--warp-icon-o, inline); }
|