10 Commits
Author SHA1 Message Date
GROKandClaude Opus 5 efc2238802 chore: release v0.5.1
ci / build-and-design (push) Failing after 2s
Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-24 22:39:54 -05:00
GROK 292fd70e2a pin light danger to Terracotta #A42D20 (#5)
ci / build-and-design (push) Failing after 2s
Light danger pin; regen CSS. Dark and foregrounds unchanged.
2026-08-24 14:55:20 -05:00
GROK b6fcc7ae00 pin light danger to Terracotta #A42D20 (src/styles/theme.generated.css)
ci / build-and-design (pull_request) Failing after 2s
2026-08-24 14:46:29 -05:00
GROK e27059bfd9 pin light danger to Terracotta #A42D20 (src/lib/tokens.json) 2026-08-24 14:46:28 -05:00
GROK aaafe073a9 Merge pull request 'Scope CommandPalette uppercase to the group heading' (#4) from fix/cmdk-heading-uppercase into main
ci / build-and-design (push) Failing after 1s
2026-08-20 17:18:53 -05:00
GROK 42ddfd0527 Merge pull request 'Add immediate opt-out for FadeIn and AnimatedNumber' (#3) from fix/motion-immediate into main
ci / build-and-design (push) Failing after 2s
2026-08-20 17:18:48 -05:00
GROK cf06ec07ca Scope CommandPalette uppercase to the group heading
ci / build-and-design (pull_request) Failing after 1s
Fixes #2. uppercase on Command.Group was inherited by items, so labels rendered as ALL CAPS.
2026-08-20 16:59:20 -05:00
GROK 1db797b4db Add immediate opt-out so FadeIn and AnimatedNumber can run on mount
ci / build-and-design (pull_request) Failing after 21s
Fixes #1. Dashboards and wall displays never scroll, so whileInView left FadeIn at opacity 0 and AnimatedNumber stuck at 0. Default is unchanged.
2026-08-20 16:59:19 -05:00
GROKandClaude Opus 5 2d746c0d33 chore: gitignore output/ and tmp/ demo-PDF artifacts
ci / build-and-design (push) Failing after 2s
Both are generated by scripts/build_demo_pdf.py — output/ holds the
rendered demo.pdf, tmp/ the intermediate page PNGs it rasterizes on the
way there (~2 MB of them).

Same category as the existing dist-* entries, so grouped with them.

scripts/build_demo_pdf.py itself is deliberately left untracked rather
than ignored — it is source, not output, and whether it belongs in the
repo is a separate call.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-07 15:14:10 -05:00
GROKandClaude Opus 5 10a29ded6a chore: gitignore .npmrc so a registry token can't be committed
ci / build-and-design (push) Failing after 3s
Publishing to the Gitea npm registry needs an _authToken, and the natural
place to put it is a .npmrc in the repo root — which was not ignored. The
committed .npmrc.example invites exactly that mistake.

The pattern matches the exact filename, so .npmrc.example is unaffected.

Nothing was leaked: no .npmrc has ever been committed here. This is
preventative.

Better still, publish with `npm publish --userconfig <path outside the
repo>` and keep the token out of the working tree entirely — that is how
v0.5.0 was published.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-07 15:00:35 -05:00
9 changed files with 44 additions and 11 deletions
+12
View File
@@ -4,6 +4,11 @@ dist-showcase/
dist-demo/ dist-demo/
*.log *.log
.DS_Store .DS_Store
# Generated by scripts/build_demo_pdf.py — the rendered demo PDF and the
# intermediate page PNGs it rasterizes on the way there (~2 MB).
output/
tmp/
# impeccable-ignore-start # impeccable-ignore-start
.impeccable/config.local.json .impeccable/config.local.json
.impeccable/hook.cache.json .impeccable/hook.cache.json
@@ -14,3 +19,10 @@ dist-demo/
# tsup temp (mount cannot unlink) # tsup temp (mount cannot unlink)
tsup.config.bundled_*.mjs tsup.config.bundled_*.mjs
# A real .npmrc carries the Gitea registry _authToken — never commit it.
# This pattern matches the exact filename only, so .npmrc.example (which is
# meant to be committed) is unaffected.
# Publishing from a clone: prefer `npm publish --userconfig <path outside the
# repo>` over writing a token into the working tree at all.
.npmrc
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@jason/ui-kit", "name": "@jason/ui-kit",
"version": "0.5.0", "version": "0.5.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@jason/ui-kit", "name": "@jason/ui-kit",
"version": "0.5.0", "version": "0.5.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fontsource-variable/jetbrains-mono": "^5.3.0", "@fontsource-variable/jetbrains-mono": "^5.3.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@jason/ui-kit", "name": "@jason/ui-kit",
"version": "0.5.0", "version": "0.5.1",
"description": "Shared design foundation — branded tokens, Tailwind preset, shadcn-style components, motion, app shell + marketing blocks. Guarded by Impeccable.", "description": "Shared design foundation — branded tokens, Tailwind preset, shadcn-style components, motion, app shell + marketing blocks. Guarded by Impeccable.",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
+6 -2
View File
@@ -8,10 +8,13 @@ export function AnimatedNumber({
value, value,
format = defaultFormat, format = defaultFormat,
className, className,
immediate = false,
}: { }: {
value: number; value: number;
format?: (n: number) => string; format?: (n: number) => string;
className?: string; className?: string;
/** Count up on mount instead of waiting to scroll into view. Dashboards. */
immediate?: boolean;
}) { }) {
const ref = React.useRef<HTMLSpanElement>(null); const ref = React.useRef<HTMLSpanElement>(null);
const inView = useInView(ref, { once: true, margin: "-20% 0px" }); const inView = useInView(ref, { once: true, margin: "-20% 0px" });
@@ -19,11 +22,12 @@ export function AnimatedNumber({
const mv = useMotionValue(0); const mv = useMotionValue(0);
const spring = useSpring(mv, { stiffness: 90, damping: 20 }); const spring = useSpring(mv, { stiffness: 90, damping: 20 });
const [display, setDisplay] = React.useState(format(0)); const [display, setDisplay] = React.useState(format(0));
const active = immediate || inView;
React.useEffect(() => { React.useEffect(() => {
if (reduce) { setDisplay(format(value)); return; } if (reduce) { setDisplay(format(value)); return; }
if (inView) mv.set(value); if (active) mv.set(value);
}, [inView, value, reduce, mv, format]); }, [active, value, reduce, mv, format]);
React.useEffect(() => spring.on("change", (v) => setDisplay(format(v))), [spring, format]); React.useEffect(() => spring.on("change", (v) => setDisplay(format(v))), [spring, format]);
+10 -1
View File
@@ -59,7 +59,16 @@ export function CommandPalette({
<Command.List className="max-h-80 overflow-auto p-2"> <Command.List className="max-h-80 overflow-auto p-2">
<Command.Empty className="py-6 text-center text-sm text-muted">No results.</Command.Empty> <Command.Empty className="py-6 text-center text-sm text-muted">No results.</Command.Empty>
{groups.map(([group, items]) => ( {groups.map(([group, items]) => (
<Command.Group key={group} heading={group} className="px-1 py-1 text-xs font-semibold uppercase tracking-wide text-muted [&_[cmdk-group-items]]:mt-1"> <Command.Group
key={group}
heading={group}
className={cn(
"px-1 py-1 [&_[cmdk-group-items]]:mt-1",
"[&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-semibold",
"[&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-wide",
"[&_[cmdk-group-heading]]:text-muted"
)}
>
{items.map((a) => ( {items.map((a) => (
<Command.Item <Command.Item
key={a.id} key={a.id}
+4 -1
View File
@@ -24,6 +24,8 @@ export interface StatCardProps {
intent?: StatIntent; intent?: StatIntent;
icon?: React.ReactNode; icon?: React.ReactNode;
className?: string; className?: string;
/** Count up on mount. Pass through to AnimatedNumber. */
immediate?: boolean;
} }
/** Dashboard metric tile. Pass `animate` for a count-up, or `value` for static. */ /** Dashboard metric tile. Pass `animate` for a count-up, or `value` for static. */
@@ -36,6 +38,7 @@ export function StatCard({
intent = "default", intent = "default",
icon, icon,
className, className,
immediate,
}: StatCardProps) { }: StatCardProps) {
return ( return (
<div className={cn("rounded-lg border border-border bg-surface p-4 shadow-sm", className)}> <div className={cn("rounded-lg border border-border bg-surface p-4 shadow-sm", className)}>
@@ -44,7 +47,7 @@ export function StatCard({
{icon && <span className="text-muted [&_svg]:size-4">{icon}</span>} {icon && <span className="text-muted [&_svg]:size-4">{icon}</span>}
</div> </div>
<p className={cn("mt-1 font-display text-2xl font-bold", INTENT[intent])}> <p className={cn("mt-1 font-display text-2xl font-bold", INTENT[intent])}>
{animate != null ? <AnimatedNumber value={animate} format={format} /> : value} {animate != null ? <AnimatedNumber value={animate} format={format} immediate={immediate} /> : value}
</p> </p>
{sub && <p className="mt-0.5 text-xs text-muted">{sub}</p>} {sub && <p className="mt-0.5 text-xs text-muted">{sub}</p>}
</div> </div>
+1 -1
View File
@@ -35,7 +35,7 @@
"successForeground": "#FFFFFF", "successForeground": "#FFFFFF",
"warning": "#7A5B00", "warning": "#7A5B00",
"warningForeground": "#FFFFFF", "warningForeground": "#FFFFFF",
"danger": "#9B3D22", "danger": "#A42D20",
"dangerForeground": "#FFFFFF", "dangerForeground": "#FFFFFF",
"info": "#366B99", "info": "#366B99",
"infoForeground": "#FFFFFF" "infoForeground": "#FFFFFF"
+7 -2
View File
@@ -9,19 +9,24 @@ export function FadeIn({
delay = 0, delay = 0,
y = 8, y = 8,
className, className,
immediate = false,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
delay?: number; delay?: number;
y?: number; y?: number;
className?: string; className?: string;
/** Animate on mount instead of waiting to scroll into view. Dashboards. */
immediate?: boolean;
}) { }) {
const reduce = useReducedMotion(); const reduce = useReducedMotion();
const visible = { opacity: 1, y: 0 };
return ( return (
<motion.div <motion.div
className={className} className={className}
initial={reduce ? false : { opacity: 0, y }} initial={reduce ? false : { opacity: 0, y }}
whileInView={{ opacity: 1, y: 0 }} {...(immediate
viewport={{ once: true, margin: "-10% 0px" }} ? { animate: visible }
: { whileInView: visible, viewport: { once: true, margin: "-10% 0px" as const } })}
transition={reduce ? { duration: 0 } : { duration: 0.4, ease: easeOut, delay }} transition={reduce ? { duration: 0 } : { duration: 0.4, ease: easeOut, delay }}
> >
{children} {children}
+1 -1
View File
@@ -35,7 +35,7 @@
--success-foreground: 0 0% 100%; --success-foreground: 0 0% 100%;
--warning: 45 100% 24%; --warning: 45 100% 24%;
--warning-foreground: 0 0% 100%; --warning-foreground: 0 0% 100%;
--danger: 13 64% 37%; --danger: 6 67% 38%;
--danger-foreground: 0 0% 100%; --danger-foreground: 0 0% 100%;
--info: 208 48% 41%; --info: 208 48% 41%;
--info-foreground: 0 0% 100%; --info-foreground: 0 0% 100%;