@olwiba/ui 0.2.15 → 0.2.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olwiba/ui",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+
3
+ import type { ReactNode } from 'react';
4
+ import type { LucideIcon } from 'lucide-react';
5
+ import { cn } from '../lib/utils';
6
+
7
+ export interface AppPageHeroProps {
8
+ eyebrow?: ReactNode;
9
+ title: ReactNode;
10
+ description?: ReactNode;
11
+ icon?: LucideIcon;
12
+ action?: ReactNode;
13
+ children?: ReactNode;
14
+ compact?: boolean;
15
+ className?: string;
16
+ }
17
+
18
+ /**
19
+ * Rich page header for signed-in product/admin pages.
20
+ *
21
+ * Use this when a route needs more visual weight than a simple `PageHeader`,
22
+ * but still keep product-specific stats, actions, and copy in the consumer.
23
+ */
24
+ export function AppPageHero({
25
+ eyebrow,
26
+ title,
27
+ description,
28
+ icon: Icon,
29
+ action,
30
+ children,
31
+ compact = false,
32
+ className,
33
+ }: AppPageHeroProps) {
34
+ return (
35
+ <section
36
+ className={cn(
37
+ 'relative overflow-hidden rounded-3xl border bg-card/80 p-5 shadow-sm sm:p-6',
38
+ 'before:absolute before:-right-16 before:-top-16 before:size-44 before:rounded-full before:bg-primary/15 before:blur-3xl',
39
+ 'after:absolute after:-bottom-20 after:left-10 after:size-52 after:rounded-full after:bg-primary/10 after:blur-3xl',
40
+ className,
41
+ )}
42
+ >
43
+ <div className="relative flex flex-col gap-5 sm:flex-row sm:items-start sm:justify-between">
44
+ <div className="flex min-w-0 gap-4">
45
+ {Icon && (
46
+ <div className="flex size-12 shrink-0 items-center justify-center rounded-2xl border bg-background/70 text-primary shadow-sm">
47
+ <Icon className="size-5" />
48
+ </div>
49
+ )}
50
+ <div className="min-w-0 space-y-2">
51
+ {eyebrow && (
52
+ <p className="text-xs font-semibold uppercase tracking-[0.24em] text-primary/80">
53
+ {eyebrow}
54
+ </p>
55
+ )}
56
+ <h1
57
+ className={cn(
58
+ 'font-semibold tracking-tight',
59
+ compact ? 'text-2xl' : 'text-3xl sm:text-4xl',
60
+ )}
61
+ >
62
+ {title}
63
+ </h1>
64
+ {description && (
65
+ <p className="max-w-2xl text-sm leading-6 text-muted-foreground">{description}</p>
66
+ )}
67
+ </div>
68
+ </div>
69
+ {action && <div className="relative shrink-0 sm:pt-1">{action}</div>}
70
+ </div>
71
+ {children && <div className="relative mt-5">{children}</div>}
72
+ </section>
73
+ );
74
+ }
package/src/index.ts CHANGED
@@ -29,6 +29,8 @@ export {
29
29
  type AppNavItem,
30
30
  } from './app/AppShell';
31
31
 
32
+ export { AppPageHero, type AppPageHeroProps } from './app/AppPageHero';
33
+
32
34
  export {
33
35
  AuthSection,
34
36
  type AuthSectionProps,
@@ -39,7 +39,11 @@ export function Footer({
39
39
  children: (
40
40
  <>
41
41
  {brand.logo}
42
- <span className="text-lg font-semibold text-foreground">{brand.name}</span>
42
+ {/* Same trim as Navbar's lockup, and more visible here: text-lg
43
+ scales the unused ascender space up with the font size. */}
44
+ <span className="text-lg font-semibold leading-none text-foreground [text-box-edge:ex_alphabetic] [text-box-trim:trim-both]">
45
+ {brand.name}
46
+ </span>
43
47
  </>
44
48
  ),
45
49
  })}
@@ -42,7 +42,24 @@ export function Navbar({
42
42
  children: (
43
43
  <span className="flex items-center gap-2 font-semibold">
44
44
  {brand.logo}
45
- <span>{brand.name}</span>
45
+ {/* `items-center` centres the text's *box*, not its glyphs, and
46
+ the box reserves room for ascenders and descenders the name may
47
+ not use — so a lowercase wordmark sits low with dead air above.
48
+ `leading-none` only removes half-leading; the ascender space is
49
+ still inside the box, which is why it wasn't enough on its own.
50
+
51
+ text-box-trim/-edge trims the box to the glyphs themselves.
52
+ `ex alphabetic` = x-height to baseline, chosen over `cap
53
+ alphabetic` because these wordmarks are lowercase (nestrrr).
54
+ A product with a capitalised name wants `cap` instead —
55
+ otherwise its capitals overflow the trimmed box and read high.
56
+
57
+ leading-none stays as the fallback: text-box-trim is not
58
+ Baseline yet (no Firefox), and there it degrades to the old
59
+ behaviour rather than breaking. */}
60
+ <span className="leading-none [text-box-edge:ex_alphabetic] [text-box-trim:trim-both]">
61
+ {brand.name}
62
+ </span>
46
63
  </span>
47
64
  ),
48
65
  })}
@@ -104,7 +121,10 @@ export function Navbar({
104
121
  children: (
105
122
  <span className="flex items-center gap-2 font-semibold">
106
123
  {brand.logo}
107
- <span>{brand.name}</span>
124
+ {/* Same fix as the desktop lockup above. */}
125
+ <span className="leading-none [text-box-edge:ex_alphabetic] [text-box-trim:trim-both]">
126
+ {brand.name}
127
+ </span>
108
128
  </span>
109
129
  ),
110
130
  })}