@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.
Files changed (38) hide show
  1. package/dist/components/plugin-download-consent.d.ts +8 -0
  2. package/dist/components/plugin-download-consent.js +2 -0
  3. package/dist/components/plugin-download-consent.js.map +1 -1
  4. package/dist/components/popover.d.ts +10 -1
  5. package/dist/components/popover.js +9 -0
  6. package/dist/components/popover.js.map +1 -1
  7. package/dist/components/preview-card.d.ts +14 -0
  8. package/dist/components/preview-card.js +102 -0
  9. package/dist/components/preview-card.js.map +1 -0
  10. package/dist/components/special-tag-surface.d.ts +36 -0
  11. package/dist/components/special-tag-surface.js +533 -0
  12. package/dist/components/special-tag-surface.js.map +1 -0
  13. package/dist/components/tag-chip.d.ts +87 -0
  14. package/dist/components/tag-chip.js +713 -0
  15. package/dist/components/tag-chip.js.map +1 -0
  16. package/dist/lib/colors.d.ts +33 -0
  17. package/dist/lib/colors.js +65 -0
  18. package/dist/lib/colors.js.map +1 -0
  19. package/dist/lib/tag-surface.d.ts +22 -0
  20. package/dist/lib/tag-surface.js +615 -0
  21. package/dist/lib/tag-surface.js.map +1 -0
  22. package/package.json +2 -2
  23. package/src/components/plugin-download-consent.test.tsx +13 -0
  24. package/src/components/plugin-download-consent.tsx +10 -0
  25. package/src/components/popover.tsx +21 -0
  26. package/src/components/preview-card.tsx +100 -0
  27. package/src/components/special/gold.tsx +73 -0
  28. package/src/components/special/kintsugi.tsx +143 -0
  29. package/src/components/special/oilslick.tsx +138 -0
  30. package/src/components/special/rainbow.tsx +63 -0
  31. package/src/components/special/silver.tsx +73 -0
  32. package/src/components/special/types.ts +21 -0
  33. package/src/components/special-tag-surface.tsx +53 -0
  34. package/src/components/tag-chip.test.tsx +180 -0
  35. package/src/components/tag-chip.tsx +221 -0
  36. package/src/lib/colors.test.ts +94 -0
  37. package/src/lib/colors.ts +96 -0
  38. 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
+ }