@hoardodile/ui 0.1.2 → 0.1.4
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/dist/components/plugin-download-consent.d.ts +8 -0
- package/dist/components/plugin-download-consent.js +2 -0
- package/dist/components/plugin-download-consent.js.map +1 -1
- package/dist/components/popover.d.ts +10 -1
- package/dist/components/popover.js +9 -0
- package/dist/components/popover.js.map +1 -1
- package/dist/components/preview-card.d.ts +14 -0
- package/dist/components/preview-card.js +102 -0
- package/dist/components/preview-card.js.map +1 -0
- package/dist/components/special-tag-surface.d.ts +36 -0
- package/dist/components/special-tag-surface.js +533 -0
- package/dist/components/special-tag-surface.js.map +1 -0
- package/dist/components/tag-chip.d.ts +87 -0
- package/dist/components/tag-chip.js +713 -0
- package/dist/components/tag-chip.js.map +1 -0
- package/dist/lib/colors.d.ts +33 -0
- package/dist/lib/colors.js +65 -0
- package/dist/lib/colors.js.map +1 -0
- package/dist/lib/tag-surface.d.ts +22 -0
- package/dist/lib/tag-surface.js +615 -0
- package/dist/lib/tag-surface.js.map +1 -0
- package/package.json +2 -2
- package/src/components/plugin-download-consent.test.tsx +13 -0
- package/src/components/plugin-download-consent.tsx +10 -0
- package/src/components/popover.tsx +21 -0
- package/src/components/preview-card.tsx +100 -0
- package/src/components/special/gold.tsx +73 -0
- package/src/components/special/kintsugi.tsx +143 -0
- package/src/components/special/oilslick.tsx +138 -0
- package/src/components/special/rainbow.tsx +63 -0
- package/src/components/special/silver.tsx +73 -0
- package/src/components/special/types.ts +21 -0
- package/src/components/special-tag-surface.tsx +53 -0
- package/src/components/tag-chip.test.tsx +180 -0
- package/src/components/tag-chip.tsx +221 -0
- package/src/lib/colors.test.ts +94 -0
- package/src/lib/colors.ts +96 -0
- package/src/lib/tag-surface.ts +71 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment node
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from "vitest"
|
|
6
|
+
import {
|
|
7
|
+
computeTagChipColors,
|
|
8
|
+
isBlackHex,
|
|
9
|
+
isSpecialTagStyle,
|
|
10
|
+
isWhiteHex,
|
|
11
|
+
TAG_SPECIAL_STYLES,
|
|
12
|
+
} from "./colors"
|
|
13
|
+
|
|
14
|
+
describe("isWhiteHex", () => {
|
|
15
|
+
it("matches common white representations", () => {
|
|
16
|
+
expect(isWhiteHex("#fff")).toBe(true)
|
|
17
|
+
expect(isWhiteHex("#FFFFFF")).toBe(true)
|
|
18
|
+
expect(isWhiteHex("white")).toBe(true)
|
|
19
|
+
expect(isWhiteHex("rgb(255,255,255)")).toBe(true)
|
|
20
|
+
expect(isWhiteHex("rgba( 255 , 255 , 255 , 0.5)")).toBe(true)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("rejects empty and non-white colors", () => {
|
|
24
|
+
expect(isWhiteHex("")).toBe(false)
|
|
25
|
+
expect(isWhiteHex("#000")).toBe(false)
|
|
26
|
+
expect(isWhiteHex("#abcdef")).toBe(false)
|
|
27
|
+
expect(isWhiteHex("rgb(254,255,255)")).toBe(false)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe("isBlackHex", () => {
|
|
32
|
+
it("matches common black representations", () => {
|
|
33
|
+
expect(isBlackHex("#000")).toBe(true)
|
|
34
|
+
expect(isBlackHex("#000000")).toBe(true)
|
|
35
|
+
expect(isBlackHex("BLACK")).toBe(true)
|
|
36
|
+
expect(isBlackHex("rgb(0, 0, 0)")).toBe(true)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("rejects empty and non-black colors", () => {
|
|
40
|
+
expect(isBlackHex("")).toBe(false)
|
|
41
|
+
expect(isBlackHex("#fff")).toBe(false)
|
|
42
|
+
expect(isBlackHex("rgb(0,0,1)")).toBe(false)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe("computeTagChipColors", () => {
|
|
47
|
+
it("falls back to muted/accent vars for empty color", () => {
|
|
48
|
+
const colors = computeTagChipColors("")
|
|
49
|
+
expect(colors.baseBg).toContain("var(--color-muted)")
|
|
50
|
+
expect(colors.hoverBg).toContain("var(--color-accent)")
|
|
51
|
+
expect(colors.fg).toContain("var(--color-foreground)")
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("returns explicit white treatment for white", () => {
|
|
55
|
+
const colors = computeTagChipColors("#ffffff")
|
|
56
|
+
expect(colors.baseBg).toBe("#ffffff")
|
|
57
|
+
expect(colors.fg).toBe("#0a0a0a")
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it("returns explicit black treatment for black", () => {
|
|
61
|
+
const colors = computeTagChipColors("black")
|
|
62
|
+
expect(colors.baseBg).toBe("#0a0a0a")
|
|
63
|
+
expect(colors.fg).toBe("#ffffff")
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it("uses color-mix blends for arbitrary colors", () => {
|
|
67
|
+
const colors = computeTagChipColors("#3366ff")
|
|
68
|
+
expect(colors.baseBg).toContain("color-mix")
|
|
69
|
+
expect(colors.baseBg).toContain("6%")
|
|
70
|
+
expect(colors.hoverBg).toContain("20%")
|
|
71
|
+
expect(colors.fg).toBe("#3366ff")
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it("treats special style names as ordinary colors", () => {
|
|
75
|
+
const colors = computeTagChipColors("rainbow")
|
|
76
|
+
expect(colors.baseBg).toContain("color-mix")
|
|
77
|
+
expect(colors.fg).toBe("rainbow")
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe("isSpecialTagStyle", () => {
|
|
82
|
+
it("matches all known special styles", () => {
|
|
83
|
+
for (const style of TAG_SPECIAL_STYLES) {
|
|
84
|
+
expect(isSpecialTagStyle(style)).toBe(true)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it("rejects regular colors and empty", () => {
|
|
89
|
+
expect(isSpecialTagStyle("")).toBe(false)
|
|
90
|
+
expect(isSpecialTagStyle("#ff0000")).toBe(false)
|
|
91
|
+
expect(isSpecialTagStyle("red")).toBe(false)
|
|
92
|
+
expect(isSpecialTagStyle("rainbowish")).toBe(false)
|
|
93
|
+
})
|
|
94
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure color utilities used by tag/category chip rendering.
|
|
3
|
+
*
|
|
4
|
+
* Centralized here so chip components, color pickers, and tests share
|
|
5
|
+
* one definition for "looks white"/"looks black" and the color-mix
|
|
6
|
+
* blending used to keep chips subtle on cards and sidebars.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const TAG_SPECIAL_STYLES = [
|
|
10
|
+
"silver",
|
|
11
|
+
"gold",
|
|
12
|
+
"rainbow",
|
|
13
|
+
"oilslick",
|
|
14
|
+
"kintsugi",
|
|
15
|
+
] as const
|
|
16
|
+
|
|
17
|
+
/** Default color presets shared by every color picker in the app. */
|
|
18
|
+
export const DEFAULT_COLOR_PRESETS = [
|
|
19
|
+
"#9D9D9D",
|
|
20
|
+
"#000000",
|
|
21
|
+
"#FFFFFF",
|
|
22
|
+
"#27AE60",
|
|
23
|
+
"#0070DD",
|
|
24
|
+
"#00BCD4",
|
|
25
|
+
"#8E44AD",
|
|
26
|
+
"#FF8000",
|
|
27
|
+
"#F1C40F",
|
|
28
|
+
"#E74C3C",
|
|
29
|
+
] as const
|
|
30
|
+
|
|
31
|
+
export type TagSpecialStyle = (typeof TAG_SPECIAL_STYLES)[number]
|
|
32
|
+
|
|
33
|
+
export function isSpecialTagStyle(color: string): color is TagSpecialStyle {
|
|
34
|
+
return TAG_SPECIAL_STYLES.includes(color as TagSpecialStyle)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type TagChipColors = {
|
|
38
|
+
readonly baseBg: string
|
|
39
|
+
readonly hoverBg: string
|
|
40
|
+
readonly fg: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const WHITE_COLOR_PATTERN =
|
|
44
|
+
/^(?:#fff(?:fff)?|white|rgba?\(\s*255\s*,\s*255\s*,\s*255\b)/i
|
|
45
|
+
const BLACK_COLOR_PATTERN =
|
|
46
|
+
/^(?:#000(?:000)?|black|rgba?\(\s*0\s*,\s*0\s*,\s*0\b)/i
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Tests whether a CSS color string visually resolves to white in any
|
|
50
|
+
* common short form (`#fff`, `#ffffff`, `white`, `rgb(255,255,255,…)`).
|
|
51
|
+
*/
|
|
52
|
+
export function isWhiteHex(color: string): boolean {
|
|
53
|
+
if (color === "") return false
|
|
54
|
+
return WHITE_COLOR_PATTERN.test(color.trim())
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Mirror of {@link isWhiteHex} for black. */
|
|
58
|
+
export function isBlackHex(color: string): boolean {
|
|
59
|
+
if (color === "") return false
|
|
60
|
+
return BLACK_COLOR_PATTERN.test(color.trim())
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a chip's background/hover/foreground triple for the given
|
|
65
|
+
* `color`. White and black are special-cased so they remain visibly
|
|
66
|
+
* black/white in both light and dark themes instead of vanishing into
|
|
67
|
+
* the chip background.
|
|
68
|
+
*/
|
|
69
|
+
export function computeTagChipColors(color: string): TagChipColors {
|
|
70
|
+
if (color === "") {
|
|
71
|
+
return {
|
|
72
|
+
baseBg: "var(--color-muted)",
|
|
73
|
+
hoverBg: "var(--color-accent)",
|
|
74
|
+
fg: "var(--color-foreground)",
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (isWhiteHex(color)) {
|
|
78
|
+
return {
|
|
79
|
+
baseBg: "#ffffff",
|
|
80
|
+
hoverBg: "#f1f5f9",
|
|
81
|
+
fg: "#0a0a0a",
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (isBlackHex(color)) {
|
|
85
|
+
return {
|
|
86
|
+
baseBg: "#0a0a0a",
|
|
87
|
+
hoverBg: "#262626",
|
|
88
|
+
fg: "#ffffff",
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
baseBg: `color-mix(in srgb, ${color} 6%, var(--color-card))`,
|
|
93
|
+
hoverBg: `color-mix(in srgb, ${color} 20%, var(--color-card))`,
|
|
94
|
+
fg: color,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getSpecialTagStyleConfig } from "@hoardodile/ui/components/special-tag-surface"
|
|
2
|
+
import {
|
|
3
|
+
computeTagChipColors,
|
|
4
|
+
isSpecialTagStyle,
|
|
5
|
+
type TagSpecialStyle,
|
|
6
|
+
} from "@hoardodile/ui/lib/colors"
|
|
7
|
+
import { cn } from "@hoardodile/ui/lib/utils"
|
|
8
|
+
import type { CSSProperties } from "react"
|
|
9
|
+
|
|
10
|
+
export type ResolvedTagChipSurface = {
|
|
11
|
+
readonly className: string
|
|
12
|
+
readonly style: CSSProperties | undefined
|
|
13
|
+
/** The special SVG texture to layer under the chip's content, or null. */
|
|
14
|
+
readonly texture: TagSpecialStyle | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolves a chip's visual surface for a user-assigned color — the single
|
|
19
|
+
* source of chip coloring shared by every tag/category pill in the app.
|
|
20
|
+
*
|
|
21
|
+
* A plain color tints the whole chip (a 6% wash as fill, the color as ink,
|
|
22
|
+
* deepening to the hover tint when `active`); the five special styles
|
|
23
|
+
* (silver, gold, rainbow, oilslick, kintsugi) render their SVG texture;
|
|
24
|
+
* an empty color falls back to the default muted chip, taking the primary
|
|
25
|
+
* fill when `active`.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveTagChipSurface(
|
|
28
|
+
color: string,
|
|
29
|
+
active: boolean,
|
|
30
|
+
): ResolvedTagChipSurface {
|
|
31
|
+
if (isSpecialTagStyle(color)) {
|
|
32
|
+
const config = getSpecialTagStyleConfig(color)
|
|
33
|
+
return {
|
|
34
|
+
className: cn(
|
|
35
|
+
"relative isolate group",
|
|
36
|
+
config.default.className,
|
|
37
|
+
active ? config.active?.className : undefined,
|
|
38
|
+
),
|
|
39
|
+
style: {
|
|
40
|
+
...config.default.style,
|
|
41
|
+
...(active ? config.active?.style : {}),
|
|
42
|
+
},
|
|
43
|
+
texture: color,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (color === "") {
|
|
48
|
+
return active
|
|
49
|
+
? {
|
|
50
|
+
className: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
51
|
+
style: undefined,
|
|
52
|
+
texture: null,
|
|
53
|
+
}
|
|
54
|
+
: {
|
|
55
|
+
className: "bg-muted text-foreground hover:bg-accent",
|
|
56
|
+
style: undefined,
|
|
57
|
+
texture: null,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const chipColors = computeTagChipColors(color)
|
|
62
|
+
return {
|
|
63
|
+
className: "bg-(--chip-bg) hover:bg-(--chip-hover-bg)",
|
|
64
|
+
style: {
|
|
65
|
+
["--chip-bg" as string]: active ? chipColors.hoverBg : chipColors.baseBg,
|
|
66
|
+
["--chip-hover-bg" as string]: chipColors.hoverBg,
|
|
67
|
+
color: chipColors.fg,
|
|
68
|
+
},
|
|
69
|
+
texture: null,
|
|
70
|
+
}
|
|
71
|
+
}
|