Skip to content

Commit

Permalink
feat: add magic back button
Browse files Browse the repository at this point in the history
  • Loading branch information
hsuanyi-chou committed Nov 21, 2024
1 parent 38e0ca2 commit 8dd790f
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Just copy and paste to your project and customize to your needs. The code is you
- [x] [Heading With Anchor](components/ui/heading-with-anchor.tsx)
- [x] [Infinite Scroll](components/ui/infinite-scroll.tsx)
- [x] [Loading Button](components/ui/loading-button.tsx)
- [x] [Magic Back Button](components/ui/magic-back-button.tsx)
- [x] [Multiple Selector](components/ui/multiple-selector.tsx)
- [x] [Datetime picker](components/ui/datetime-picker.tsx)
- [x] [Spinner](components/ui/spinner.tsx)
Expand Down
25 changes: 25 additions & 0 deletions app/(docs)/docs/magic-back-button/magic-back-button-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';
import React from 'react';
import { MagicBackButton } from '@/components/ui/magic-back-button';
import { P } from '@/components/ui/heading-with-anchor';
import { InlineCode } from '@/components/ui/inline-code';

const MagicBackButtonDemo = () => {
return (
<div className="flex max-h-[300px] w-full flex-col items-center justify-center gap-3">
<div>
<P className="text-muted-foreground">
Try opening this page link in a new tab, <br />
and you’ll notice the button will navigate directly to the homepage. (
<InlineCode>{`router.push('/')`}</InlineCode>)<br />
However, if you navigate to this page from another component, <br />
the button will simply go back to the previous page. (
<InlineCode>{`router.back()`}</InlineCode>)
</P>
</div>
<MagicBackButton />
</div>
);
};

export default MagicBackButtonDemo;
107 changes: 107 additions & 0 deletions app/(docs)/docs/magic-back-button/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from 'react';
import { PageSubTitle, PageTemplate } from '@/app/(docs)/docs/components/page-template';
import PreviewCodeCard from '@/app/(docs)/docs/components/preview-code-card';
import { Steppers } from '@/components/ui/steppers';
import { Metadata } from 'next';
import { baseMetadata } from '@/app/(docs)/layout-parts/base-metadata';
import MagicBackButtonDemo from '@/app/(docs)/docs/magic-back-button/magic-back-button-demo';
import { P } from '@/components/ui/heading-with-anchor';
import { InlineCode } from '@/components/ui/inline-code';
import { PropLink } from '@/app/(docs)/docs/components/props-table/prop-link';
import Usage from '@/app/(docs)/docs/components/usage';
import MagicBackButtonBackLink from '@/app/(docs)/docs/magic-back-button/usage/magic-back-button-back-link';
import { Reference, ReferenceBorder } from '@/app/(docs)/docs/components/reference';
import CodeHighlight from '@/app/(docs)/docs/components/code-card/parts/code-highlight';

export const metadata: Metadata = baseMetadata({
title: 'Infinite Scroll',
description:
'Redirects first-time visitors to the homepage; otherwise, respects browser history.',
});

const MagicBackButtonPage = () => {
return (
<PageTemplate
title="Magic Back Button"
description="Redirects first-time visitors to the homepage; otherwise, respects browser history."
>
<ReferenceBorder>
<Reference href="https://ui.shadcn.com/docs/components/button" />
<Reference href="https://react-page-tracker.typeart.cc/" text="React Page Tracker" />
</ReferenceBorder>

<PageSubTitle>About</PageSubTitle>
<P className="text-muted-foreground">
The Magic Back button is designed to prevent users from unintentionally leaving your site.
When a user directly visits a page with a back button (i.e., their first visit), clicking
the button will redirect them to the homepage. However, if they navigate to the page from
another part of the site, the button respects the browser's history and behaves like a
normal back button, without disrupting the browser's history records.
<br />
<br />
This component is built on top of{' '}
<PropLink href="https://react-page-tracker.typeart.cc/">
<InlineCode>React Page Tracker</InlineCode>
</PropLink>
. before using this component, please following the{' '}
<PropLink href="https://react-page-tracker.typeart.cc/docs/nextjs">
<InlineCode>installation</InlineCode>
</PropLink>
.
</P>
<PreviewCodeCard path="app/(docs)/docs/magic-back-button/magic-back-button-demo.tsx">
<MagicBackButtonDemo />
</PreviewCodeCard>

<PageSubTitle>Installation</PageSubTitle>
<Steppers
withInstall
installScript="npx shadcn@latest add button"
codePath="components/ui/magic-back-button.tsx"
steps={[
{
title: 'npm i react-page-tracker',
children: (
<div className="text-xl text-primary">
Following the
<PropLink href="https://react-page-tracker.typeart.cc/docs/nextjs">
<InlineCode className="text-xl">installation</InlineCode>
</PropLink>{' '}
to use the package. <br />
<br />
Simply import <InlineCode className="text-xl">PageTracker</InlineCode> in your root
layout.
<CodeHighlight
code={`
import { PageTracker } from 'react-page-tracker';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
+ <PageTracker />
{children}
</body>
</html>)
}`}
withExpand
/>
</div>
),
},
]}
withEnd
/>

<PageSubTitle>Usage</PageSubTitle>
<Usage
title="Custom your back link"
path="app/(docs)/docs/magic-back-button/usage/magic-back-button-back-link.tsx"
>
<MagicBackButtonBackLink />
</Usage>
</PageTemplate>
);
};

export default MagicBackButtonPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use client';
import React from 'react';
import { MagicBackButton } from '@/components/ui/magic-back-button';
import { P } from '@/components/ui/heading-with-anchor';
import { InlineCode } from '@/components/ui/inline-code';

const MagicBackButtonBackLink = () => {
return (
<div className="flex max-h-[300px] w-full flex-col items-center justify-center gap-3">
<P className="text-muted-foreground">
back to <InlineCode>/docs</InlineCode> when you first visit this page.
</P>
<MagicBackButton backLink="/docs" />
</div>
);
};

export default MagicBackButtonBackLink;
5 changes: 5 additions & 0 deletions app/(docs)/layout-parts/documentation.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ export const DOCS: Documentation[] = [
value: 'loading-button',
url: '/docs/loading-button',
},
{
label: 'Magic Back Button',
value: 'magic-back-button',
url: '/docs/magic-back-button',
},
{
label: 'Progress With Value',
value: 'progress-with-value',
Expand Down
2 changes: 2 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ThemeProvider } from '@/components/ui/theme-provider';
import './globals.css';
import Navbar from '@/app/(docs)/layout-parts/navbar/navbar';
import Script from 'next/script';
import { PageTracker } from 'react-page-tracker';

const fontSans = FontSans({
subsets: ['latin'],
Expand Down Expand Up @@ -51,6 +52,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
className={cn('min-h-screen bg-background font-sans antialiased', fontSans.variable)}
suppressHydrationWarning
>
<PageTracker />
<ThemeProvider
attribute="class"
defaultTheme="system"
Expand Down
34 changes: 34 additions & 0 deletions components/ui/magic-back-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
import { Button, ButtonProps } from '@/components/ui/button';
import { useRouter } from 'next/navigation';
import { usePageTrackerStore } from 'react-page-tracker';
import { ChevronLeft } from 'lucide-react';

export const MagicBackButton = React.forwardRef<
HTMLButtonElement,
ButtonProps & { backLink?: string }
>(({ className, onClick, children, backLink = '/', ...props }, ref) => {
const router = useRouter();
const isFirstPage = usePageTrackerStore((state) => state.isFirstPage);
return (
<Button
className={cn('rounded-full', className)}
variant="outline"
size="icon"
ref={ref}
onClick={(e) => {
if (isFirstPage) {
router.push(backLink);
} else {
router.back();
}
onClick?.(e);
}}
{...props}
>
{children ?? <ChevronLeft />}
</Button>
);
});
MagicBackButton.displayName = 'MagicBackButton';
18 changes: 16 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "shadcn-ui-expensions",
"version": "0.8.0",
"version": "0.9.0",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down Expand Up @@ -33,6 +33,7 @@
"react-dom": "^18",
"react-highlight": "^0.15.0",
"react-hook-form": "^7.51.5",
"react-page-tracker": "^0.3.0",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
Expand Down

0 comments on commit 8dd790f

Please sign in to comment.