@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hogoshi/ui",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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%-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))]",
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>
@@ -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
+ }
@@ -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.5rem)] pb-2 pl-[var(--safe-left)] transition-[width] duration-[var(--motion-duration)] ease-[var(--motion-ease)] md:flex",
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
- <ChromeVeil side="bottom" fixed className="md:hidden" />
158
- <nav
159
- 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
- className,
162
- )}
163
- {...props}
164
- >
165
- <div className="relative flex items-stretch justify-around">
166
- <Glide selector={ACTIVE} className="bg-accent-muted/60 rounded-[var(--radius-md)]" />
167
- {children}
168
- </div>
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: "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
@@ -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 { ChromeVeil } from "./components/chrome-veil";
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: 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%;
@@ -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
- }