@hogoshi/ui 0.1.13 → 0.1.14

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": "@hogoshi/ui",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -37,8 +37,8 @@ export function TopBar({ contained = false, children, className }: { contained?:
37
37
  className={cn(
38
38
  "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
39
  compact
40
- ? "border-border bg-bg-elevated w-[min(40rem,calc(100%-max(0.75rem,var(--safe-left))-max(0.75rem,var(--safe-right))))] rounded-full px-2 shadow-[var(--shadow-md)]"
41
- : "w-full border-transparent bg-transparent pl-[max(0.75rem,var(--safe-left))] pr-[max(0.75rem,var(--safe-right))]",
40
+ ? "border-border bg-bg-elevated w-[min(40rem,calc(100%-var(--edge-left)-var(--edge-right)))] rounded-full px-2 shadow-[var(--shadow-md)]"
41
+ : "w-full border-transparent bg-transparent pr-[var(--edge-right)] pl-[var(--edge-left)]",
42
42
  )}
43
43
  >
44
44
  <Navbar className="relative h-full w-full bg-transparent px-1 shadow-none backdrop-blur-none">{children}</Navbar>
@@ -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-[calc(var(--safe-top)+0.75rem)]",
22
- right: "right-[max(0.75rem,var(--safe-right))]",
23
- bottom: "bottom-2",
24
- left: "left-[max(0.75rem,var(--safe-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
+ }
@@ -42,7 +42,7 @@ export function Sidebar({ className, ...props }: ComponentProps<"aside">) {
42
42
  <aside
43
43
  data-collapsed={collapsed || undefined}
44
44
  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.5rem)] pb-2 pl-[var(--safe-left)] transition-[width] duration-[var(--motion-duration)] ease-[var(--motion-ease)] md:flex",
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.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
46
  collapsed && "w-[calc(3.5rem+var(--safe-left))]",
47
47
  className,
48
48
  )}
@@ -156,8 +156,9 @@ export function BottomBar({ className, children, ...props }: ComponentProps<"nav
156
156
  <>
157
157
  <ChromeVeil side="bottom" fixed className="md:hidden" />
158
158
  <nav
159
+ data-bottom-bar
159
160
  className={cn(
160
- "bg-bg-elevated/70 border-border fixed right-[max(0.75rem,var(--safe-right))] bottom-[var(--bottombar-offset)] left-[max(0.75rem,var(--safe-left))] z-[var(--z-sticky)] rounded-[var(--radius-lg)] border p-1 shadow-[var(--shadow-md)] backdrop-blur-xl md:hidden",
161
+ "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",
161
162
  className,
162
163
  )}
163
164
  {...props}
@@ -13,13 +13,13 @@ export function Toaster() {
13
13
  position="bottom-right"
14
14
  closeButton
15
15
  offset={{
16
- bottom: "1.5rem",
17
- right: "1.5rem",
16
+ bottom: "var(--toast-bottom)",
17
+ right: "max(1.5rem, var(--safe-right))",
18
18
  }}
19
19
  mobileOffset={{
20
- bottom: "5rem",
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
@@ -43,6 +43,7 @@ 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
45
  export { ChromeVeil } from "./components/chrome-veil";
46
+ export { AppShell, ShellAction, ShellBrand, ShellLogo, ShellMenu, ShellSearch, ShellTitle, shellGlass } from "./components/shell";
46
47
  export { Glide, glideActive } from "./components/glide";
47
48
  export type { GlideProps } from "./components/glide";
48
49
  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: max(0.5rem, var(--safe-bottom));
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%;