Compare commits
9
Commits
2d746c0d33
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dc64c7c13 | ||
|
|
efc2238802 | ||
|
|
292fd70e2a | ||
|
|
b6fcc7ae00 | ||
|
|
e27059bfd9 | ||
|
|
aaafe073a9 | ||
|
|
42ddfd0527 | ||
|
|
cf06ec07ca | ||
|
|
1db797b4db |
@@ -6,7 +6,13 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-design:
|
build-and-design:
|
||||||
runs-on: ubuntu-latest
|
# `host`, not `ubuntu-latest`. A containerized job on this act_runner dies
|
||||||
|
# during setup with `mkdirat var/run/act: path escapes from parent` — the
|
||||||
|
# runner extracts its payload to /var/run/act, and /var/run is a symlink to
|
||||||
|
# /run in the Debian/Ubuntu job images, which Docker's archive extractor
|
||||||
|
# refuses to write through. See #6. ui-kit ships no image and deploys
|
||||||
|
# nothing, so there is nothing to isolate; the job just needs Node + repo.
|
||||||
|
runs-on: host
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
|
|||||||
Generated
+2
-2
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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%;
|
||||||
|
|||||||
Reference in New Issue
Block a user