Skip to content

Commit

Permalink
Fine tuning scrollBar
Browse files Browse the repository at this point in the history
  • Loading branch information
Duncid committed Jan 3, 2025
1 parent 5245448 commit 82b7692
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 38 deletions.
3 changes: 2 additions & 1 deletion sparkle/src/components/Container.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";

import { ScrollArea } from "@sparkle/components";
import { ScrollArea, ScrollBar } from "@sparkle/components";
import { cn } from "@sparkle/lib";

interface ContainerProps extends React.HTMLAttributes<HTMLDivElement> {
Expand All @@ -24,6 +24,7 @@ export const Container = React.forwardRef<HTMLDivElement, ContainerProps>(
>
{children}
</div>
<ScrollBar size="classic" orientation="vertical" />
</ScrollArea>
</div>
);
Expand Down
129 changes: 98 additions & 31 deletions sparkle/src/components/ScrollArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,108 @@ export interface ScrollAreaProps
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
ScrollAreaProps
>(({ className, children, hideScrollBar = false, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("s-relative s-z-20 s-overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="s-h-full s-w-full s-rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
{!hideScrollBar && <ScrollBar />}
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
));
>(({ className, children, hideScrollBar = false, ...props }, ref) => {
const hasCustomScrollBar = React.Children.toArray(children).some(
(child) =>
React.isValidElement(child) &&
(child.type as typeof ScrollBar).displayName === ScrollBar.displayName
);

const shouldHideDefaultScrollBar = hideScrollBar || hasCustomScrollBar;

return (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("s-relative s-z-20 s-overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="s-h-full s-w-full s-rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
{!shouldHideDefaultScrollBar && <ScrollBar />}
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
);
});

ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;

const scrollBarSizes = {
compact: {
bar: {
vertical: "s-w-5",
horizontal: "s-h-5",
},
padding: {
vertical: "s-pr-1 s-pl-2.5 s-py-2 hover:s-pl-2",
horizontal: "s-pb-1 s-pt-2.5 s-px-2",
},
thumb: "s-bg-muted-foreground/40 hover:s-bg-muted-foreground/70",
},
classic: {
bar: {
vertical: "s-w-5",
horizontal: "s-h-5",
},
padding: {
vertical: "s-pl-2 s-pr-1 s-py-1",
horizontal: "s-py-0.5 s-px-1",
},
thumb: "s-bg-muted-foreground/70 hover:s-bg-muted-foreground/80",
},
} as const;

type ScrollBarSize = keyof typeof scrollBarSizes;

interface ScrollBarProps
extends React.ComponentPropsWithoutRef<
typeof ScrollAreaPrimitive.ScrollAreaScrollbar
> {
size?: ScrollBarSize;
}

const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"s-flex s-touch-none s-select-none s-transition-colors",
orientation === "vertical" &&
"s-h-full s-w-3 s-border-l s-border-l-transparent s-px-[3px] s-py-3",
orientation === "horizontal" &&
"s-h-3 s-flex-col s-border-t s-border-t-transparent s-px-3 s-py-[3px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="s-relative s-flex-1 s-rounded-full s-bg-muted-foreground/40" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
));
ScrollBarProps
>(
(
{ className, orientation = "vertical", size = "compact", ...props },
ref
) => {
const sizeConfig = scrollBarSizes[size];

return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"s-flex s-touch-none s-select-none hover:s-cursor-pointer",
orientation === "vertical" && [
"s-h-full s-border-l s-border-l-transparent",
sizeConfig.bar.vertical,
sizeConfig.padding.vertical,
],
orientation === "horizontal" && [
"s-flex-col s-border-t s-border-t-transparent",
sizeConfig.bar.horizontal,
sizeConfig.padding.horizontal,
],
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb
className={cn(
"s-relative s-flex-1 s-rounded-full s-transition-colors s-duration-200",
sizeConfig.thumb
)}
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
);
}
);

ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;

export { ScrollArea, ScrollBar };
export type { ScrollBarSize };
26 changes: 20 additions & 6 deletions sparkle/src/stories/ScrollArea.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,34 @@ const tags = Array.from({ length: 50 }).map(

export function ScrollAreaDemo() {
return (
<div className="s-h-[400px]">
<ScrollArea className="s-h-full s-w-[200px] s-border-b s-border-t s-border-border">
<div className="s-p-4">
<div className="s-flex s-flex-row s-gap-6 s-bg-muted s-p-8">
<div className="s-h-[400px]">
<ScrollArea className="s-h-full s-w-[200px] s-border-b s-border-t s-border-border s-bg-white">
<h4 className="s-mb-4 s-text-sm s-font-medium s-leading-none">
Tags
Mini ScrollBar
</h4>
{tags.map((tag) => (
<React.Fragment key={tag}>
<div className="s-text-sm">{tag}</div>
<Separator className="s-my-2" />
</React.Fragment>
))}
</div>
</ScrollArea>
</ScrollArea>
</div>
<div className="s-h-[400px]">
<ScrollArea className="s-h-full s-w-[200px] s-border-b s-border-t s-border-border s-bg-white">
<h4 className="s-mb-4 s-text-sm s-font-medium s-leading-none">
Classic ScrollBar
</h4>
{tags.map((tag) => (
<React.Fragment key={tag}>
<div className="s-text-sm">{tag}</div>
<Separator className="s-my-2" />
</React.Fragment>
))}
<ScrollBar orientation="vertical" size="classic" />
</ScrollArea>
</div>
</div>
);
}
Expand Down

0 comments on commit 82b7692

Please sign in to comment.