@hogoshi/ui 0.1.13 → 0.1.15
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 +1 -1
- package/src/components/navbar.tsx +2 -4
- package/src/components/sheet.tsx +4 -4
- package/src/components/shell.tsx +173 -0
- package/src/components/sidebar.tsx +14 -17
- package/src/components/toaster.tsx +5 -5
- package/src/index.ts +1 -1
- package/src/styles.css +12 -1
- package/src/components/chrome-veil.tsx +0 -20
package/package.json
CHANGED
|
@@ -5,7 +5,6 @@ import { NavigationMenu } from "@base-ui/react/navigation-menu";
|
|
|
5
5
|
import { ArrowDown01Icon } from "@hugeicons-pro/core-stroke-rounded";
|
|
6
6
|
import { useEffect, useState, type ComponentProps, type ReactNode } from "react";
|
|
7
7
|
import { cn } from "../lib/cn";
|
|
8
|
-
import { ChromeVeil } from "./chrome-veil";
|
|
9
8
|
import { Glide, glideActive } from "./glide";
|
|
10
9
|
import type { AppIcon } from "./icon";
|
|
11
10
|
import { Icon } from "./icon";
|
|
@@ -32,13 +31,12 @@ export function TopBar({ contained = false, children, className }: { contained?:
|
|
|
32
31
|
|
|
33
32
|
return (
|
|
34
33
|
<header className={cn("pointer-events-none inset-x-0 top-0 z-[var(--z-sticky)]", contained ? "absolute" : "fixed", className)}>
|
|
35
|
-
<ChromeVeil side="top" />
|
|
36
34
|
<div
|
|
37
35
|
className={cn(
|
|
38
36
|
"pointer-events-auto relative mx-auto flex h-12 translate-y-[var(--topbar-offset)] items-center border transition-[width,border-radius,background-color,border-color,box-shadow] duration-[calc(var(--motion-duration)*4)] ease-[var(--motion-ease)]",
|
|
39
37
|
compact
|
|
40
|
-
? "border-border bg-bg-elevated w-[min(40rem,calc(100%-
|
|
41
|
-
: "w-full border-transparent bg-transparent
|
|
38
|
+
? "border-border bg-bg-elevated w-[min(40rem,calc(100%-var(--edge-left)-var(--edge-right)))] rounded-full px-2 shadow-[var(--shadow-md)]"
|
|
39
|
+
: "w-full border-transparent bg-transparent pr-[var(--edge-right)] pl-[var(--edge-left)]",
|
|
42
40
|
)}
|
|
43
41
|
>
|
|
44
42
|
<Navbar className="relative h-full w-full bg-transparent px-1 shadow-none backdrop-blur-none">{children}</Navbar>
|
package/src/components/sheet.tsx
CHANGED
|
@@ -18,10 +18,10 @@ const SideContext = createContext<SheetSide>("right");
|
|
|
18
18
|
const direction = { top: "up", right: "right", bottom: "down", left: "left" } as const;
|
|
19
19
|
|
|
20
20
|
const edge = {
|
|
21
|
-
top: "top-[
|
|
22
|
-
right: "right-[
|
|
23
|
-
bottom: "bottom-
|
|
24
|
-
left: "left-[
|
|
21
|
+
top: "top-[var(--topbar-offset)]",
|
|
22
|
+
right: "right-[var(--edge-right)]",
|
|
23
|
+
bottom: "bottom-[var(--bottombar-offset)]",
|
|
24
|
+
left: "left-[var(--edge-left)]",
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
const frame = {
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Search01Icon } from "@hugeicons-pro/core-stroke-rounded";
|
|
4
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
5
|
+
import { cn } from "../lib/cn";
|
|
6
|
+
import { Button } from "./button";
|
|
7
|
+
import { Icon } from "./icon";
|
|
8
|
+
import { Kbd } from "./kbd";
|
|
9
|
+
import { Logo } from "./logo";
|
|
10
|
+
import { Sheet, SheetContent, SheetTitle } from "./sheet";
|
|
11
|
+
import { SidebarHeader, SidebarInset, SidebarProvider, SidebarTrigger, sidebarLabel, useSidebar } from "./sidebar";
|
|
12
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
|
|
13
|
+
|
|
14
|
+
export const shellGlass = "border-border bg-bg-elevated/70 hover:bg-bg-elevated border backdrop-blur-xl";
|
|
15
|
+
|
|
16
|
+
const focusRing = "rounded-[var(--radius-sm)] outline-none focus-visible:ring-2 focus-visible:ring-ring";
|
|
17
|
+
|
|
18
|
+
export function AppShell({
|
|
19
|
+
sidebar,
|
|
20
|
+
topBar,
|
|
21
|
+
bottomBar,
|
|
22
|
+
defaultCollapsed,
|
|
23
|
+
className,
|
|
24
|
+
contentClassName,
|
|
25
|
+
children,
|
|
26
|
+
}: {
|
|
27
|
+
sidebar?: ReactNode;
|
|
28
|
+
topBar?: ReactNode;
|
|
29
|
+
bottomBar?: ReactNode;
|
|
30
|
+
defaultCollapsed?: boolean;
|
|
31
|
+
className?: string;
|
|
32
|
+
contentClassName?: string;
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
}) {
|
|
35
|
+
return (
|
|
36
|
+
<SidebarProvider defaultCollapsed={defaultCollapsed}>
|
|
37
|
+
<div data-app-shell className={cn("bg-bg fixed inset-0 flex h-app overflow-hidden", className)}>
|
|
38
|
+
{sidebar}
|
|
39
|
+
<SidebarInset className="relative flex min-h-0 min-w-0 flex-1 flex-col">
|
|
40
|
+
{topBar}
|
|
41
|
+
<div
|
|
42
|
+
data-scroll-root
|
|
43
|
+
className={cn(
|
|
44
|
+
"min-h-0 flex-1 touch-pan-y overflow-x-hidden overflow-y-scroll overscroll-y-contain pb-[var(--safe-bottom)]",
|
|
45
|
+
topBar && "pt-[var(--topbar-space)]",
|
|
46
|
+
bottomBar && "pb-[var(--bottombar-space)] md:pb-[var(--safe-bottom)]",
|
|
47
|
+
contentClassName,
|
|
48
|
+
)}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</div>
|
|
52
|
+
</SidebarInset>
|
|
53
|
+
</div>
|
|
54
|
+
{bottomBar}
|
|
55
|
+
</SidebarProvider>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function ShellBrand({ href, label, className, ...props }: Omit<ComponentProps<"a">, "children"> & { label: string }) {
|
|
60
|
+
const { collapsed } = useSidebar();
|
|
61
|
+
return (
|
|
62
|
+
<SidebarHeader className={cn("group/header relative overflow-hidden px-2", className)}>
|
|
63
|
+
<a
|
|
64
|
+
href={href}
|
|
65
|
+
aria-label={label}
|
|
66
|
+
className={cn(
|
|
67
|
+
"flex min-w-0 items-center gap-2 px-1 whitespace-nowrap",
|
|
68
|
+
focusRing,
|
|
69
|
+
collapsed &&
|
|
70
|
+
"group-hover/header:pointer-events-none group-hover/header:opacity-0 [@media(hover:none)]:pointer-events-none [@media(hover:none)]:opacity-0",
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
<Logo size="sm" className="shrink-0" />
|
|
75
|
+
<span className={cn("text-sm font-semibold tracking-tight", sidebarLabel, collapsed && "opacity-0")}>{label}</span>
|
|
76
|
+
</a>
|
|
77
|
+
<SidebarTrigger
|
|
78
|
+
className={
|
|
79
|
+
collapsed
|
|
80
|
+
? "pointer-events-none absolute top-3 left-3 opacity-0 transition-opacity group-hover/header:pointer-events-auto group-hover/header:opacity-100 focus-visible:pointer-events-auto focus-visible:opacity-100 [@media(hover:none)]:pointer-events-auto [@media(hover:none)]:opacity-100"
|
|
81
|
+
: "shrink-0"
|
|
82
|
+
}
|
|
83
|
+
/>
|
|
84
|
+
</SidebarHeader>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function ShellLogo({ href, label, className, children, ...props }: ComponentProps<"a"> & { label: string }) {
|
|
89
|
+
return (
|
|
90
|
+
<a href={href} aria-label={label} className={cn("flex shrink-0 items-center gap-2", focusRing, className)} {...props}>
|
|
91
|
+
<Logo size="sm" />
|
|
92
|
+
{children}
|
|
93
|
+
</a>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function ShellTitle({ className, children }: { className?: string; children: ReactNode }) {
|
|
98
|
+
return (
|
|
99
|
+
<p className={cn("pointer-events-none absolute top-1/2 left-1/2 max-w-[70%] -translate-x-1/2 -translate-y-1/2", className)}>
|
|
100
|
+
<span
|
|
101
|
+
aria-hidden
|
|
102
|
+
className="absolute top-1/2 left-1/2 -z-10 h-[2.4em] w-[260%] -translate-x-1/2 -translate-y-1/2 bg-[radial-gradient(ellipse_at_center,var(--bg)_0%,color-mix(in_oklch,var(--bg)_82%,transparent)_36%,transparent_70%)]"
|
|
103
|
+
/>
|
|
104
|
+
<span className="relative block truncate text-sm font-semibold">{children}</span>
|
|
105
|
+
</p>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function ShellAction({ className, ...props }: ComponentProps<typeof Button>) {
|
|
110
|
+
return <Button variant="secondary" size="icon" className={cn(shellGlass, "rounded-full", className)} {...props} />;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function ShellSearch({
|
|
114
|
+
onOpen,
|
|
115
|
+
shortcut = "⌘K",
|
|
116
|
+
label = "Search",
|
|
117
|
+
className,
|
|
118
|
+
}: {
|
|
119
|
+
onOpen: () => void;
|
|
120
|
+
shortcut?: string;
|
|
121
|
+
label?: string;
|
|
122
|
+
className?: string;
|
|
123
|
+
}) {
|
|
124
|
+
const { collapsed } = useSidebar();
|
|
125
|
+
const fade = cn(sidebarLabel, collapsed && "opacity-0");
|
|
126
|
+
const button = (
|
|
127
|
+
<Button
|
|
128
|
+
variant="outline"
|
|
129
|
+
aria-label={label}
|
|
130
|
+
className={cn(shellGlass, "w-full justify-between overflow-hidden px-3 font-normal whitespace-nowrap [&>span]:w-full [&>span>span]:w-full", className)}
|
|
131
|
+
onClick={onOpen}
|
|
132
|
+
>
|
|
133
|
+
<span className="flex w-full items-center justify-between gap-2">
|
|
134
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
135
|
+
<Icon icon={Search01Icon} variant="stroke" size={16} className="shrink-0" />
|
|
136
|
+
<span className={fade}>{label}</span>
|
|
137
|
+
</span>
|
|
138
|
+
<Kbd className={cn("shrink-0", fade)}>{shortcut}</Kbd>
|
|
139
|
+
</span>
|
|
140
|
+
</Button>
|
|
141
|
+
);
|
|
142
|
+
return (
|
|
143
|
+
<div className="shrink-0 px-2 pb-2">
|
|
144
|
+
<Tooltip disabled={!collapsed}>
|
|
145
|
+
<TooltipTrigger render={button} />
|
|
146
|
+
<TooltipContent side="right">{label}</TooltipContent>
|
|
147
|
+
</Tooltip>
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function ShellMenu({
|
|
153
|
+
open,
|
|
154
|
+
onOpenChange,
|
|
155
|
+
brand,
|
|
156
|
+
children,
|
|
157
|
+
}: {
|
|
158
|
+
open: boolean;
|
|
159
|
+
onOpenChange: (open: boolean) => void;
|
|
160
|
+
brand: ReactNode;
|
|
161
|
+
children: ReactNode;
|
|
162
|
+
}) {
|
|
163
|
+
return (
|
|
164
|
+
<Sheet side="left" open={open} onOpenChange={onOpenChange}>
|
|
165
|
+
<SheetContent className="right-auto px-2">
|
|
166
|
+
<SheetTitle className="px-3 pt-1 text-sm font-semibold">{brand}</SheetTitle>
|
|
167
|
+
<SidebarProvider>
|
|
168
|
+
<div className="flex min-h-0 flex-1 flex-col">{children}</div>
|
|
169
|
+
</SidebarProvider>
|
|
170
|
+
</SheetContent>
|
|
171
|
+
</Sheet>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
@@ -4,7 +4,6 @@ import { Button as BaseButton } from "@base-ui/react/button";
|
|
|
4
4
|
import { SidebarLeftIcon } from "@hugeicons-pro/core-stroke-rounded";
|
|
5
5
|
import { createContext, useContext, useEffect, useMemo, useState, type ComponentProps, type ReactNode } from "react";
|
|
6
6
|
import { cn } from "../lib/cn";
|
|
7
|
-
import { ChromeVeil } from "./chrome-veil";
|
|
8
7
|
import type { AppIcon, IconVariant } from "./icon";
|
|
9
8
|
import { Glide, glideActive } from "./glide";
|
|
10
9
|
import { Icon } from "./icon";
|
|
@@ -42,7 +41,7 @@ export function Sidebar({ className, ...props }: ComponentProps<"aside">) {
|
|
|
42
41
|
<aside
|
|
43
42
|
data-collapsed={collapsed || undefined}
|
|
44
43
|
className={cn(
|
|
45
|
-
"bg-bg-elevated border-border hidden h-full min-h-0 w-[calc(15rem+var(--safe-left))] shrink-0 flex-col overflow-hidden border-r pt-[calc(var(--safe-top)+0.
|
|
44
|
+
"bg-bg-elevated border-border hidden h-full min-h-0 w-[calc(15rem+var(--safe-left))] shrink-0 flex-col overflow-hidden border-r pt-[calc(var(--safe-top)+0.75rem)] pb-[calc(var(--safe-bottom)+0.75rem)] pl-[var(--safe-left)] transition-[width] duration-[var(--motion-duration)] ease-[var(--motion-ease)] md:flex",
|
|
46
45
|
collapsed && "w-[calc(3.5rem+var(--safe-left))]",
|
|
47
46
|
className,
|
|
48
47
|
)}
|
|
@@ -153,21 +152,19 @@ export function SidebarItem({
|
|
|
153
152
|
|
|
154
153
|
export function BottomBar({ className, children, ...props }: ComponentProps<"nav">) {
|
|
155
154
|
return (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
>
|
|
165
|
-
<
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
</nav>
|
|
170
|
-
</>
|
|
155
|
+
<nav
|
|
156
|
+
data-bottom-bar
|
|
157
|
+
className={cn(
|
|
158
|
+
"bg-bg-elevated/70 border-border fixed right-[var(--edge-right)] bottom-[var(--bottombar-offset)] left-[var(--edge-left)] z-[var(--z-sticky)] rounded-[var(--radius-lg)] border p-1 shadow-[var(--shadow-md)] backdrop-blur-xl md:hidden",
|
|
159
|
+
className,
|
|
160
|
+
)}
|
|
161
|
+
{...props}
|
|
162
|
+
>
|
|
163
|
+
<div className="relative flex items-stretch justify-around">
|
|
164
|
+
<Glide selector={ACTIVE} className="bg-accent-muted/60 rounded-[var(--radius-md)]" />
|
|
165
|
+
{children}
|
|
166
|
+
</div>
|
|
167
|
+
</nav>
|
|
171
168
|
);
|
|
172
169
|
}
|
|
173
170
|
|
|
@@ -13,13 +13,13 @@ export function Toaster() {
|
|
|
13
13
|
position="bottom-right"
|
|
14
14
|
closeButton
|
|
15
15
|
offset={{
|
|
16
|
-
bottom: "
|
|
17
|
-
right: "1.5rem",
|
|
16
|
+
bottom: "var(--toast-bottom)",
|
|
17
|
+
right: "max(1.5rem, var(--safe-right))",
|
|
18
18
|
}}
|
|
19
19
|
mobileOffset={{
|
|
20
|
-
bottom: "
|
|
21
|
-
left: "1rem",
|
|
22
|
-
right: "1rem",
|
|
20
|
+
bottom: "var(--toast-bottom)",
|
|
21
|
+
left: "max(1rem, var(--safe-left))",
|
|
22
|
+
right: "max(1rem, var(--safe-right))",
|
|
23
23
|
}}
|
|
24
24
|
style={{ zIndex: "var(--z-toast)" }}
|
|
25
25
|
toastOptions={{
|
package/src/index.ts
CHANGED
|
@@ -42,7 +42,7 @@ export { Progress } from "./components/progress";
|
|
|
42
42
|
export { Logo } from "./components/logo";
|
|
43
43
|
export { LOGO_PATH, LOGO_VIEWBOX, logoSvg } from "./components/logo-mark";
|
|
44
44
|
export { Navbar, NavbarItem, NavbarMenu, NavbarMenuLink, NavbarMenus, TopBar } from "./components/navbar";
|
|
45
|
-
export {
|
|
45
|
+
export { AppShell, ShellAction, ShellBrand, ShellLogo, ShellMenu, ShellSearch, ShellTitle, shellGlass } from "./components/shell";
|
|
46
46
|
export { Glide, glideActive } from "./components/glide";
|
|
47
47
|
export type { GlideProps } from "./components/glide";
|
|
48
48
|
export { Calendar } from "./components/calendar";
|
package/src/styles.css
CHANGED
|
@@ -78,10 +78,14 @@ body {
|
|
|
78
78
|
--safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
79
79
|
--safe-left: env(safe-area-inset-left, 0px);
|
|
80
80
|
--app-height: 100dvh;
|
|
81
|
+
--edge-x: 0.75rem;
|
|
82
|
+
--edge-left: max(var(--edge-x), var(--safe-left));
|
|
83
|
+
--edge-right: max(var(--edge-x), var(--safe-right));
|
|
81
84
|
--topbar-offset: calc(var(--safe-top) + 0.75rem);
|
|
82
85
|
--topbar-space: calc(var(--topbar-offset) + 3.75rem);
|
|
83
|
-
--bottombar-offset:
|
|
86
|
+
--bottombar-offset: calc(var(--safe-bottom) + 0.75rem);
|
|
84
87
|
--bottombar-space: calc(var(--bottombar-offset) + 4.25rem);
|
|
88
|
+
--toast-bottom: calc(var(--safe-bottom) + 1.5rem);
|
|
85
89
|
}
|
|
86
90
|
|
|
87
91
|
@media (display-mode: standalone) {
|
|
@@ -90,6 +94,12 @@ body {
|
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
96
|
|
|
97
|
+
@media (max-width: 47.99rem) {
|
|
98
|
+
html:has([data-bottom-bar]) {
|
|
99
|
+
--toast-bottom: var(--bottombar-space);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
93
103
|
html,
|
|
94
104
|
body {
|
|
95
105
|
min-height: var(--app-height);
|
|
@@ -109,6 +119,7 @@ body {
|
|
|
109
119
|
|
|
110
120
|
html {
|
|
111
121
|
scroll-behavior: auto;
|
|
122
|
+
touch-action: pan-x pan-y;
|
|
112
123
|
-webkit-tap-highlight-color: transparent;
|
|
113
124
|
-webkit-text-size-adjust: 100%;
|
|
114
125
|
text-size-adjust: 100%;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { cn } from "../lib/cn";
|
|
2
|
-
|
|
3
|
-
const sides = {
|
|
4
|
-
top: "top-0 h-[calc(var(--safe-top)+2rem)] bg-gradient-to-b [mask-image:linear-gradient(to_bottom,black_30%,transparent)]",
|
|
5
|
-
bottom: "bottom-0 h-[var(--bottombar-space)] bg-gradient-to-t [mask-image:linear-gradient(to_top,black_30%,transparent)]",
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export function ChromeVeil({ side, fixed = false, className }: { side: "top" | "bottom"; fixed?: boolean; className?: string }) {
|
|
9
|
-
return (
|
|
10
|
-
<div
|
|
11
|
-
aria-hidden
|
|
12
|
-
className={cn(
|
|
13
|
-
"from-bg pointer-events-none inset-x-0 to-transparent backdrop-blur-xl",
|
|
14
|
-
fixed ? "fixed z-[calc(var(--z-sticky)-1)]" : "absolute",
|
|
15
|
-
sides[side],
|
|
16
|
-
className,
|
|
17
|
-
)}
|
|
18
|
-
/>
|
|
19
|
-
);
|
|
20
|
-
}
|