Skip to content

Commit 6d83304

Browse files
authored
Merge pull request #36 from cloudmix-dev/bugfix/app-shell-layout-scroll
AppShell layout scroll
2 parents dcd0ca0 + 77f870a commit 6d83304

7 files changed

Lines changed: 97 additions & 118 deletions

File tree

.changeset/thin-items-double.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudmix-dev/react": patch
3+
---
4+
5+
Fix AppShell scroll mechanics for better mobile support

packages/react/src/components/app-shell.tsx

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Dialog, Transition } from "@headlessui/react";
22
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/solid";
3-
import React, { Fragment, useState } from "react";
3+
import React, { Fragment, forwardRef, useState } from "react";
44

55
import { cn } from "../utils";
66
import { Button } from "./button";
7+
import { Slot } from "./slot";
78

89
export interface AppShellProps extends React.PropsWithChildren {
910
renderActions?: React.ReactNode;
@@ -26,7 +27,7 @@ function AppShell({
2627
const hasBar = Boolean(renderBar || renderActions);
2728

2829
return (
29-
<div className="relative h-full w-full overflow-hidden">
30+
<div className="relative h-full w-full">
3031
{hasNav && (
3132
<Transition.Root show={sidebarOpen} as={Fragment}>
3233
<Dialog
@@ -97,7 +98,7 @@ function AppShell({
9798
</Transition.Root>
9899
)}
99100
{hasNav && (
100-
<div className="hidden lg:absolute lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col">
101+
<div className="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-72 lg:flex-col">
101102
<div className="flex flex-col gap-y-5 h-full overflow-y-auto bg-neutral-100 p-4 dark:bg-neutral-900">
102103
{renderLogo && (
103104
<div className="flex-shrink-0 py-4">{renderLogo}</div>
@@ -111,8 +112,10 @@ function AppShell({
111112
</div>
112113
</div>
113114
)}
114-
<div className={cn("relative h-full max-h-full", hasNav && "lg:ml-72")}>
115-
<div className="absolute top-0 left-0 w-full z-40 flex h-16 items-center gap-x-4 border-b border-neutral-200 bg-neutral-50 text-neutral-950 px-4 dark:bg-neutral-950 dark:text-neutral-50 dark:border-neutral-800">
115+
<div
116+
className={cn("fixed top-0 left-0 w-full z-40", hasNav && "lg:pl-72")}
117+
>
118+
<div className="flex h-16 items-center gap-x-4 border-b border-neutral-200 bg-neutral-50 text-neutral-950 px-4 dark:bg-neutral-950 dark:text-neutral-50 dark:border-neutral-800">
116119
{hasNav && (
117120
<button
118121
type="button"
@@ -136,11 +139,16 @@ function AppShell({
136139
</div>
137140
)}
138141
</div>
139-
<main className="h-full max-h-full pt-16 overflow-hidden">
140-
<div className="h-full max-h-full p-4 overflow-scroll">
141-
{children}
142+
</div>
143+
<div className={cn("flex flex-col h-full", hasNav && "lg:pl-72")}>
144+
<main className="flex-grow pt-16">{children}</main>
145+
<footer>
146+
<div className="container m-auto px-4">
147+
<div className="flex justify-center items-center h-16 text-sm">
148+
<p>&copy; {new Date().getFullYear()} Cloudmix</p>
149+
</div>
142150
</div>
143-
</main>
151+
</footer>
144152
</div>
145153
</div>
146154
);
@@ -150,36 +158,43 @@ AppShell.displayName = "AppShell";
150158

151159
export interface AppShellNavigationLinkProps extends React.PropsWithChildren {
152160
active?: boolean;
153-
as?: React.ElementType;
161+
asChild?: boolean;
154162
className?: string;
155163
href: string;
156164
hrefProp?: "href" | "to";
157165
}
158166

159-
AppShell.NavigationLink = function NavigationLink({
160-
active = false,
161-
as = "a",
162-
children,
163-
href,
164-
hrefProp = "href",
165-
className,
166-
}: AppShellNavigationLinkProps) {
167-
const Component = as;
167+
const AppShellNavigationLink = forwardRef<
168+
HTMLAnchorElement,
169+
AppShellNavigationLinkProps
170+
>(
171+
(
172+
{ active = false, asChild, children, className, ...props },
173+
forwardedRef,
174+
) => {
175+
const Component = asChild ? Slot : "a";
168176

169-
return (
170-
<Component
171-
className={cn(
172-
"group flex gap-x-3 rounded-md px-4 py-2 text-sm leading-6 font-semibold focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-brand-500 focus-visible:ring-offset-neutral-100 dark:focus-visible:ring-offset-neutral-900",
173-
active
174-
? "bg-neutral-200 text-brand-600 dark:bg-neutral-800 dark:text-brand-500"
175-
: "text-neutral-500 hover:text-neutral-950 hover:bg-neutral-200 dark:text-neutral-400 dark:hover:text-neutral-50 dark:hover:bg-neutral-800",
176-
className,
177-
)}
178-
{...{ [hrefProp]: href }}
179-
>
180-
{children}
181-
</Component>
182-
);
183-
};
177+
return (
178+
<Component
179+
// biome-ignore lint/suspicious/noExplicitAny: the forwardedRef can be for any React component
180+
ref={forwardedRef as any}
181+
className={cn(
182+
"group flex gap-x-3 rounded-md px-4 py-2 text-sm leading-6 font-semibold focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-brand-500 focus-visible:ring-offset-neutral-100 dark:focus-visible:ring-offset-neutral-900",
183+
active
184+
? "bg-neutral-200 text-brand-600 dark:bg-neutral-800 dark:text-brand-500"
185+
: "text-neutral-500 hover:text-neutral-950 hover:bg-neutral-200 dark:text-neutral-400 dark:hover:text-neutral-50 dark:hover:bg-neutral-800",
186+
className,
187+
)}
188+
{...props}
189+
>
190+
{children}
191+
</Component>
192+
);
193+
},
194+
);
195+
196+
AppShell.NavigationLink = AppShellNavigationLink;
197+
198+
AppShell.NavigationLink.displayName = "AppShell.NavigationLink";
184199

185200
export { AppShell };

packages/react/src/components/button.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,17 @@ export interface ButtonProps
4747

4848
const Button = forwardRef<HTMLElement, ButtonProps>(
4949
({ asChild, children, className, variant, size, ...props }, forwardedRef) => {
50-
const Comp = asChild ? Slot : BaseButton;
50+
const Component = asChild ? Slot : BaseButton;
5151

5252
return (
53-
<Comp
54-
{...props}
53+
<Component
5554
// biome-ignore lint/suspicious/noExplicitAny: the forwardedRef can be for any React component
5655
ref={forwardedRef as any}
5756
className={cn(buttonVariants({ variant, size, className }))}
57+
{...props}
5858
>
5959
{children as ReactNode}
60-
</Comp>
60+
</Component>
6161
);
6262
},
6363
);

packages/react/src/components/excalidraw-diagram.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ function ExcalidrawDiagram({
5050
(async () => {
5151
const newElements = await prepareDiagram(mermaid);
5252

53-
console.log("ELEMENTS", newElements);
54-
5553
setElements(newElements);
5654
setLoading(false);
5755
})();

www/storybook/.storybook/preview-head.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,12 @@
99
height: 100%;
1010
width: 100%;
1111
}
12-
</style>
12+
13+
.docs-story>div {
14+
padding: 0 !important
15+
}
16+
17+
.sbdocs.sbdocs-preview.sb-unstyled .docs-story .innerZoomElementWrapper>* {
18+
border: none !important;
19+
}
20+
</style>

www/storybook/src/components/layout.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
// @ts-ignore
2-
export function StoryLayout({ children }: React.PropsWithChildren) {
2+
export function StoryLayout({
3+
children,
4+
padding = true,
5+
}: React.PropsWithChildren<{ padding?: boolean }>) {
36
return (
4-
<div className="flex justify-center items-center h-full w-full p-8 bg-neutral-50 dark:bg-neutral-950">
7+
<div
8+
className={`flex justify-center items-center h-full w-full bg-neutral-50 dark:bg-neutral-950${
9+
padding ? " p-8" : ""
10+
}`}
11+
>
512
{children}
613
</div>
714
);

www/storybook/src/stories/app-shell.stories.tsx

Lines changed: 21 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -10,79 +10,25 @@ import { type Meta, type StoryObj } from "@storybook/react";
1010

1111
import { StoryLayout } from "../components/layout";
1212

13-
function Content() {
13+
function Content({ paragraphs = 5 }: { paragraphs?: number }) {
1414
return (
15-
<Prose className="m-auto">
16-
<p>
17-
Pellentesque habitant morbi tristique senectus et netus et malesuada
18-
fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae,
19-
ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam
20-
egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend
21-
leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum
22-
erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean
23-
fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci,
24-
sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar
25-
facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor
26-
neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat
27-
volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis
28-
luctus, metus
29-
</p>
30-
<p>
31-
Pellentesque habitant morbi tristique senectus et netus et malesuada
32-
fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae,
33-
ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam
34-
egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend
35-
leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum
36-
erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean
37-
fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci,
38-
sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar
39-
facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor
40-
neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat
41-
volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis
42-
luctus, metus
43-
</p>
44-
<p>
45-
Pellentesque habitant morbi tristique senectus et netus et malesuada
46-
fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae,
47-
ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam
48-
egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend
49-
leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum
50-
erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean
51-
fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci,
52-
sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar
53-
facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor
54-
neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat
55-
volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis
56-
luctus, metus
57-
</p>
58-
<p>
59-
Pellentesque habitant morbi tristique senectus et netus et malesuada
60-
fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae,
61-
ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam
62-
egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend
63-
leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum
64-
erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean
65-
fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci,
66-
sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar
67-
facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor
68-
neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat
69-
volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis
70-
luctus, metus
71-
</p>
72-
<p>
73-
Pellentesque habitant morbi tristique senectus et netus et malesuada
74-
fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae,
75-
ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam
76-
egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend
77-
leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum
78-
erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean
79-
fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci,
80-
sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar
81-
facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor
82-
neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat
83-
volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis
84-
luctus, metus
85-
</p>
15+
<Prose className="m-auto p-4">
16+
{new Array(paragraphs).fill(null).map((_, i) => (
17+
<p key={`paragraph_${i}`}>
18+
Pellentesque habitant morbi tristique senectus et netus et malesuada
19+
fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae,
20+
ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam
21+
egestas semper. Aenean ultricies mi vitae est. Mauris placerat
22+
eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra.
23+
Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet,
24+
wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum
25+
rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in
26+
turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus
27+
faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat.
28+
Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor,
29+
facilisis luctus, metus
30+
</p>
31+
))}
8632
</Prose>
8733
);
8834
}
@@ -92,7 +38,7 @@ const meta: Meta<typeof AppShell> = {
9238
component: AppShell,
9339
decorators: [
9440
(Story: React.ComponentType) => (
95-
<StoryLayout>
41+
<StoryLayout padding={false}>
9642
<Story />
9743
</StoryLayout>
9844
),
@@ -106,7 +52,7 @@ type AppShellStory = StoryObj<typeof AppShell>;
10652

10753
export const Default: AppShellStory = {
10854
render: (props) => (
109-
<div className="h-[50rem] w-full">
55+
<div className="h-[50rem] w-full overflow-scroll">
11056
<AppShell
11157
{...props}
11258
renderLogo={
@@ -149,7 +95,7 @@ export const Default: AppShellStory = {
14995
</>
15096
}
15197
>
152-
<Content />
98+
<Content paragraphs={1} />
15399
</AppShell>
154100
</div>
155101
),

0 commit comments

Comments
 (0)