Update AppSegmented.tsx

This commit is contained in:
Nate Kelley 2025-04-01 15:16:53 -06:00
parent 969becb306
commit f915e4caea
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
3 changed files with 140 additions and 29 deletions

View File

@ -44,7 +44,6 @@ export const PreventNavigation: React.FC<PreventNavigationProps> = React.memo(
* @param e The triggered event.
*/
const handleClick = useMemoizedFn((event: MouseEvent) => {
console.log('handleClick');
let target = event.target as HTMLElement;
let href: string | null = null;
@ -61,10 +60,11 @@ export const PreventNavigation: React.FC<PreventNavigationProps> = React.memo(
event.preventDefault();
event.stopPropagation();
console.log(href);
console.log(target);
confirmationFn.current = () => {
if (href) router.push(href);
target.click();
// if (href) router.push(href);
};
setLeavingPage(true);

View File

@ -1,6 +1,32 @@
import type { Meta, StoryObj } from '@storybook/react';
import { AppSegmented } from './AppSegmented';
import { BottleChampagne, Grid, HouseModern, PaintRoller } from '../icons';
import { PreventNavigation } from '../layouts/PreventNavigation';
import { useRouter } from 'next/navigation';
import { usePathname, useSearchParams } from 'next/navigation';
import { useState } from 'react';
import { Checkbox } from '../checkbox';
// Mock the Next.js router
const MockNextRouter = ({ children }: { children: React.ReactNode }) => {
const mockRouter = {
back: () => {},
forward: () => {},
push: () => {},
replace: () => {},
refresh: () => {},
prefetch: () => Promise.resolve()
};
// @ts-ignore - we're mocking the router
useRouter.mockImplementation(() => mockRouter);
// @ts-ignore - we're mocking the pathname
usePathname.mockImplementation(() => '/');
// @ts-ignore - we're mocking the search params
useSearchParams.mockImplementation(() => new URLSearchParams());
return <>{children}</>;
};
const meta: Meta<typeof AppSegmented> = {
title: 'UI/Segmented/AppSegmented',
@ -9,6 +35,13 @@ const meta: Meta<typeof AppSegmented> = {
layout: 'centered'
},
tags: ['autodocs'],
decorators: [
(Story) => (
<MockNextRouter>
<Story />
</MockNextRouter>
)
],
argTypes: {
size: {
control: 'radio',
@ -120,3 +153,62 @@ export const WithOnlyIcons: Story = {
]
}
};
export const WithPreventDefault: Story = {
args: {
options: [
{
value: 'tab1',
icon: <HouseModern />,
link: 'https://www.google.com',
label: 'Tab 1',
tooltip: 'Tooltip 1'
},
{
value: 'tab2',
icon: <Grid />,
link: 'https://www.google.com',
label: 'Tab 2',
tooltip: 'Tooltip 2'
},
{
value: 'tab3',
icon: <BottleChampagne />,
link: 'https://www.google.com',
label: 'Tab 3',
tooltip: 'Tooltip 3'
}
]
},
render: (args) => {
const [isDirty, setIsDirty] = useState(true);
return (
<div className="flex w-full min-w-[500px] flex-col items-center justify-center gap-4">
<AppSegmented {...args} />
<div className="flex items-center gap-2">
<Checkbox
checked={isDirty}
onCheckedChange={(checked) => setIsDirty(checked === 'indeterminate' ? true : checked)}
/>
<p>{isDirty ? 'Dirty' : 'Clean'}</p>
</div>
<PreventNavigation
isDirty={isDirty}
title="Title"
description="Description"
onOk={() => {
alert('ok');
return Promise.resolve();
}}
onCancel={() => {
alert('cancel');
return Promise.resolve();
}}
/>
</div>
);
}
};

View File

@ -6,9 +6,17 @@ import { motion } from 'framer-motion';
import { cn } from '@/lib/classMerge';
import { useEffect, useState, useLayoutEffect, useTransition } from 'react';
import { cva } from 'class-variance-authority';
import { useMemoizedFn, useMergedRefs, useSize, useThrottleFn, useWhyDidYouUpdate } from '@/hooks';
import {
useMemoizedFn,
useMergedRefs,
useMount,
useSize,
useThrottleFn,
useWhyDidYouUpdate
} from '@/hooks';
import { Tooltip } from '../tooltip/Tooltip';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
export interface SegmentedItem<T extends string | number = string> {
value: T;
@ -126,8 +134,8 @@ export const AppSegmented: AppSegmentedComponent = React.memo(
console.log('handleTabClick called with:', value);
const item = options.find((item) => item.value === value);
if (item && !item.disabled && value !== selectedValue) {
setSelectedValue(item.value);
startTransition(() => {
setSelectedValue(item.value);
onChange?.(item);
});
}
@ -160,13 +168,12 @@ export const AppSegmented: AppSegmentedComponent = React.memo(
setSelectedValue(value);
});
}
}, [value, selectedValue]);
}, [value]);
return (
<Tabs.Root
ref={rootRef}
value={selectedValue as string}
onValueChange={handleTabClick}
className={cn(segmentedVariants({ block, type }), heightVariants({ size }), className)}>
{isMeasured && (
<motion.div
@ -197,6 +204,7 @@ export const AppSegmented: AppSegmentedComponent = React.memo(
size={size}
block={block}
tabRefs={tabRefs}
handleTabClick={handleTabClick}
/>
))}
</Tabs.List>
@ -213,37 +221,48 @@ interface SegmentedTriggerProps<T extends string = string> {
size: AppSegmentedProps<T>['size'];
block: AppSegmentedProps<T>['block'];
tabRefs: React.MutableRefObject<Map<string, HTMLButtonElement>>;
handleTabClick: (value: string) => void;
}
function SegmentedTriggerComponent<T extends string = string>(props: SegmentedTriggerProps<T>) {
const { item, selectedValue, size, block, tabRefs } = props;
const { item, selectedValue, size, block, tabRefs, handleTabClick } = props;
const { tooltip, label, icon, disabled, value, link } = item;
const router = useRouter();
const LinkDiv = link ? Link : 'div';
useMount(() => {
if (link) {
router.prefetch(link);
}
});
return (
<Tooltip title={tooltip || ''} sideOffset={10} delayDuration={150}>
<Tabs.Trigger
key={value}
value={value}
disabled={disabled}
asChild
ref={(el) => {
if (el) tabRefs.current.set(value, el);
}}
className={cn(
triggerVariants({
size,
block,
disabled,
selected: selectedValue === value
})
)}>
<LinkDiv href={link || ''}>
{icon && <span className={cn('flex items-center text-sm')}>{icon}</span>}
{label && <span className={cn('text-sm')}>{label}</span>}
</LinkDiv>
</Tabs.Trigger>
<LinkDiv href={link || ''}>
<Tabs.Trigger
key={value}
value={value}
disabled={disabled}
asChild
onClick={() => handleTabClick(value)}
ref={(el) => {
if (el) tabRefs.current.set(value, el);
}}
className={cn(
triggerVariants({
size,
block,
disabled,
selected: selectedValue === value
})
)}>
<div className="">
{icon && <span className={cn('flex items-center text-sm')}>{icon}</span>}
{label && <span className={cn('text-sm')}>{label}</span>}
</div>
</Tabs.Trigger>
</LinkDiv>
</Tooltip>
);
}