Skip to content

Commit

Permalink
[sparkle] - enh: introduce LinkWrapper (#8288)
Browse files Browse the repository at this point in the history
* [sparkle] - feature: introduce LinkWrapper component for context-aware links

 - Created a new LinkWrapper component that takes SparkleContext to render links conditionally based on the provided href
 - Exposed LinkWrapperProps and LinkWrapper to be accessible from the sparkle package components index

* [sparkle] - fix: correct aria-label attribute in context link component

 - Fixed a typo in the `aria-label` attribute of the context link component to ensure proper accessibility standards are met

* [sparkle] - feature: enhance NewDropdownMenuItem with link capabilities

 - NewDropdownMenuItem refactored to include LinkWrapper for optional link behavior
 - Added props for link handling such as href, target, and rel to NewDropdownMenuItem component

* [sparkle] - feature: enhance NavigationListItem with LinkWrapper functionality

 - Integrated LinkWrapper component to NavigationListItem to support hyperlinking capabilities
 - Added pass-through props from LinkWrapper to NavigationListItem excluding 'children' and 'className' for extended functionality

* [sparkle] - feature: enhance TabsContent to support routing links

 - TabsContent now accepts LinkWrapperProps to enable client-side routing for tabbed interfaces
 - Wrapped TabsContent in LinkWrapper to facilitate navigation when tabs are clicked

* [sparkle] - refactor: improve TabsTrigger component with LinkWrapperProps

 - Extended TabsTrigger props to include Omit<LinkWrapperProps, "children" | "className"> for better link management
 - Wrapped TabsTrigger content with LinkWrapper component to provide link capabilities
 - Removed redundant displayName setting for TabsTrigger and moved it to TabsContent component

* [sparkle] - fix: adjust conditional rendering and wrapper elements in components

 - Make `asChild` prop conditionally apply when either `href` or `asChild` is true in `NewDropdownMenuItem`
 - Wrap `Button` with a `div` in `TabsTrigger` to ensure proper rendering
 - Remove `s-ring-ring` class duplication in `TabsContent` styles
 - Apply `asChild` prop directly to `NewDropdownMenuTrigger` in storybook examples for consistent behavior

* [sparkle] - feature: bump package version to 0.2.283

 - Incremented the package version for a new release
 - Updated package-lock.json to keep dependencies in sync with the new version

---------

Co-authored-by: Jules <[email protected]>
  • Loading branch information
JulesBelveze and Jules authored Oct 29, 2024
1 parent 6e5460f commit c13504b
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 95 deletions.
4 changes: 2 additions & 2 deletions sparkle/package-lock.json

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

2 changes: 1 addition & 1 deletion sparkle/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dust-tt/sparkle",
"version": "0.2.282",
"version": "0.2.283",
"scripts": {
"build": "rm -rf dist && npm run tailwind && npm run build:esm && npm run build:cjs",
"tailwind": "tailwindcss -i ./src/styles/tailwind.css -o dist/sparkle.css",
Expand Down
42 changes: 42 additions & 0 deletions sparkle/src/components/LinkWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from "react";

import { SparkleContext } from "@sparkle/context";

export interface LinkWrapperProps {
href?: string;
target?: string;
rel?: string;
replace?: boolean;
shallow?: boolean;
className?: string;
children: React.ReactNode;
}

export function LinkWrapper({
href,
target,
rel,
replace,
shallow,
className,
children,
}: LinkWrapperProps) {
const { components } = React.useContext(SparkleContext);

if (href) {
return (
<components.link
href={href}
target={target}
rel={rel}
replace={replace}
shallow={shallow}
className={className}
>
{children}
</components.link>
);
}

return <>{children}</>;
}
129 changes: 83 additions & 46 deletions sparkle/src/components/NavigationList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";

import { Button, Icon } from "@sparkle/components/";
import {
Button,
Icon,
LinkWrapper,
LinkWrapperProps,
} from "@sparkle/components/";
import { MoreIcon } from "@sparkle/icons";
import { cn } from "@sparkle/lib/utils";

Expand Down Expand Up @@ -43,62 +48,94 @@ const NavigationList = React.forwardRef<
));
NavigationList.displayName = "NavigationList";

interface NavigationListItemProps extends React.HTMLAttributes<HTMLDivElement> {
interface NavigationListItemProps
extends React.HTMLAttributes<HTMLDivElement>,
Omit<LinkWrapperProps, "children" | "className"> {
selected?: boolean;
label?: string;
icon?: React.ComponentType;
showMoreIcon?: boolean;
}

const NavigationListItem = React.forwardRef<
HTMLDivElement,
NavigationListItemProps
>(({ className, selected, label, icon, ...props }, ref) => {
const [isHovered, setIsHovered] = React.useState(false);
const [isPressed, setIsPressed] = React.useState(false);
>(
(
{
className,
selected,
label,
icon,
href,
target,
rel,
replace,
shallow,
...props
},
ref
) => {
const [isHovered, setIsHovered] = React.useState(false);
const [isPressed, setIsPressed] = React.useState(false);

const handleMouseDown = (event: React.MouseEvent) => {
if (!(event.target as HTMLElement).closest(".new-button-class")) {
setIsPressed(true);
}
};
const handleMouseDown = (event: React.MouseEvent) => {
if (!(event.target as HTMLElement).closest(".new-button-class")) {
setIsPressed(true);
}
};

return (
<div className={className} ref={ref} {...props}>
<div
className={listStyles({
layout: "item",
state: selected ? "selected" : isPressed ? "active" : "unselected",
})}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => {
setIsHovered(false);
setIsPressed(false);
}}
onMouseDown={handleMouseDown}
onMouseUp={() => setIsPressed(false)}
>
{icon && <Icon visual={icon} size="sm" />}
{label && (
<span className="s-grow s-overflow-hidden s-text-ellipsis s-whitespace-nowrap">
{label}
</span>
)}
{isHovered && (
<div className="-s-mr-2 s-flex s-h-4 s-items-center">
<Button
variant="ghost"
icon={MoreIcon}
size="xs"
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onMouseUp={(e) => e.stopPropagation()}
/>
</div>
)}
const content = (
<div className={className} ref={ref} {...props}>
<div
className={listStyles({
layout: "item",
state: selected ? "selected" : isPressed ? "active" : "unselected",
})}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => {
setIsHovered(false);
setIsPressed(false);
}}
onMouseDown={handleMouseDown}
onMouseUp={() => setIsPressed(false)}
>
{icon && <Icon visual={icon} size="sm" />}
{label && (
<span className="s-grow s-overflow-hidden s-text-ellipsis s-whitespace-nowrap">
{label}
</span>
)}
{isHovered && (
<div className="-s-mr-2 s-flex s-h-4 s-items-center">
<Button
variant="ghost"
icon={MoreIcon}
size="xs"
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onMouseUp={(e) => e.stopPropagation()}
/>
</div>
)}
</div>
</div>
</div>
);
});
);

return (
<LinkWrapper
href={href}
target={target}
rel={rel}
replace={replace}
shallow={shallow}
className={className}
>
{content}
</LinkWrapper>
);
}
);
NavigationListItem.displayName = "NavigationListItem";

const variantStyles = cva("", {
Expand Down
86 changes: 59 additions & 27 deletions sparkle/src/components/NewDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { cva } from "class-variance-authority";
import * as React from "react";

import { Icon } from "@sparkle/components";
import { Icon, LinkWrapper, LinkWrapperProps } from "@sparkle/components";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "@sparkle/icons";
import { cn } from "@sparkle/lib/utils";

Expand Down Expand Up @@ -170,38 +170,70 @@ const NewDropdownMenuContent = React.forwardRef<
));
NewDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;

type NewDropdownMenuItemProps = MutuallyExclusiveProps<
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: ItemVariantType;
} & Omit<LinkWrapperProps, "children" | "className">,
LabelAndIconProps & { description?: string }
>;

const NewDropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
MutuallyExclusiveProps<
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: ItemVariantType;
},
LabelAndIconProps & { description?: string }
>
NewDropdownMenuItemProps
>(
(
{ children, variant, description, className, inset, icon, label, ...props },
{
children,
variant,
description,
className,
inset,
icon,
label,
href,
target,
rel,
asChild,
replace,
shallow,
...props
},
ref
) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
menuStyleClasses.item({ variant }),
inset ? menuStyleClasses.inset : "",
className
)}
{...props}
>
<ItemWithLabelIconAndDescription
label={label}
icon={icon}
description={description}
) => {
const content = (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
menuStyleClasses.item({ variant }),
inset ? menuStyleClasses.inset : "",
className
)}
{...props}
asChild={!!href || asChild}
>
<ItemWithLabelIconAndDescription
label={label}
icon={icon}
description={description}
>
{children}
</ItemWithLabelIconAndDescription>
</DropdownMenuPrimitive.Item>
);

return (
<LinkWrapper
href={href}
target={target}
rel={rel}
replace={replace}
shallow={shallow}
>
{children}
</ItemWithLabelIconAndDescription>
</DropdownMenuPrimitive.Item>
)
{content}
</LinkWrapper>
);
}
);
NewDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;

Expand Down
52 changes: 37 additions & 15 deletions sparkle/src/components/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as TabsPrimitive from "@radix-ui/react-tabs";
import * as React from "react";

import { Button } from "@sparkle/components";
import { Button, LinkWrapper, LinkWrapperProps } from "@sparkle/components";
import { cn } from "@sparkle/lib/utils";

const Tabs = TabsPrimitive.Root;
Expand All @@ -27,20 +27,42 @@ const TabsTrigger = React.forwardRef<
label?: string;
icon?: React.ComponentType;
isLoading?: boolean;
} & Omit<LinkWrapperProps, "children" | "className">
>(
(
{ className, label, icon, href, target, rel, replace, shallow, ...props },
ref
) => {
const content = (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"s-border-0 s-border-b-2 s-border-primary-800/0 s-pb-1 disabled:s-pointer-events-none data-[state=active]:s-border-primary-800",
className
)}
asChild
{...props}
>
<div>
<Button variant="ghost" size="sm" label={label} icon={icon} />
</div>
</TabsPrimitive.Trigger>
);

return (
<LinkWrapper
href={href}
target={target}
rel={rel}
replace={replace}
shallow={shallow}
className={className}
>
{content}
</LinkWrapper>
);
}
>(({ className, label, icon, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"s-border-0 s-border-b-2 s-border-primary-800/0 s-pb-1 disabled:s-pointer-events-none data-[state=active]:s-border-primary-800",
className
)}
{...props}
>
<Button variant="ghost" size="sm" label={label} icon={icon} />
</TabsPrimitive.Trigger>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
);

const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
Expand All @@ -49,7 +71,7 @@ const TabsContent = React.forwardRef<
<TabsPrimitive.Content
ref={ref}
className={cn(
"focus-visible:s-ring-ring s-contents s-ring-offset-background focus-visible:s-outline-none focus-visible:s-ring-2 focus-visible:s-ring-offset-2",
"s-contents s-ring-offset-background focus-visible:s-outline-none focus-visible:s-ring-2 focus-visible:s-ring-offset-2",
className
)}
{...props}
Expand Down
Loading

0 comments on commit c13504b

Please sign in to comment.