Skip to content

Commit

Permalink
feat(ui): add frontend pagination (#267)
Browse files Browse the repository at this point in the history
* feat(ui): improve Button component styling

* feat(ui): add frontend pagination on main page
  • Loading branch information
LucasMrqes authored May 20, 2024
1 parent d3411ce commit c894d97
Show file tree
Hide file tree
Showing 3 changed files with 305 additions and 68 deletions.
149 changes: 105 additions & 44 deletions ui/src/components/core/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,74 +5,130 @@ import LoaderIcon from "@/assets/icons/LoaderIcon";

export interface ButtonProps {
className?: string;
theme?: "light" | "dark";
variant?: "primary" | "secondary" | "tertiary";
children?: React.ReactNode;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
isLoading?: boolean;
disabled?: boolean;
label?: string;
onClick?: () => void;
}

const Button: React.FC<ButtonProps> = ({
className,
variant = "primary",
theme = "light",
children,
leftIcon,
rightIcon,
isLoading,
disabled,
label,
onClick,
}) => {
const styles = {
base: {
primary: `bg-nuances-black
text-nuances-white
hover:bg-nuances-400
active:bg-nuances-400
focus-visible:outline
focus-visible:outline-1
focus-visible:outline-offset-[3px]
focus-visible:outline-nuances-black
fill-nuances-white`,
light: {
base: {
primary: `bg-nuances-black
text-nuances-white
hover:bg-nuances-400
active:bg-nuances-400
focus-visible:outline
focus-visible:outline-1
focus-visible:outline-offset-[3px]
focus-visible:outline-nuances-black
fill-nuances-white`,

secondary: `bg-nuances-white
text-nuances-black
border
border-nuances-black
hover:bg-nuances-50
active:bg-nuances-50
focus-visible:outline
focus-visible:outline-1
focus-visible:outline-offset-[3px]
focus-visible:outline-nuances-white
fill-nuances-black`,
secondary: `bg-nuances-white
text-nuances-black
border
border-nuances-black
hover:bg-nuances-50
active:bg-nuances-50
focus-visible:outline
focus-visible:outline-1
focus-visible:outline-offset-[3px]
focus-visible:outline-nuances-white
fill-nuances-black`,

tertiary: `bg-nuances-white
text-primary-600
underline
hover:text-primary-400
hover:fill-primary-400
active:text-primary-400
active:fill-primary-400
focus-visible:outline-none
fill-primary-600`,
},
tertiary: `bg-nuances-white
text-primary-600
underline
hover:text-primary-400
hover:fill-primary-400
active:text-primary-400
active:fill-primary-400
focus-visible:outline-none
fill-primary-600`,
},

disabled: {
primary: `bg-nuances-50
text-nuances-300
fill-nuances-300`,
disabled: {
primary: `bg-nuances-50
text-nuances-300
fill-nuances-300`,

secondary: `bg-nuances-50
text-nuances-300
fill-nuances-300`,
secondary: `bg-nuances-50
text-nuances-300
fill-nuances-300`,

tertiary: `bg-nuances-white
text-nuances-300
underline
fill-nuances-300`,
tertiary: `bg-nuances-white
text-nuances-300
underline
fill-nuances-300`,
},
},
dark: {
base: {
primary: `bg-nuances-black
text-nuances-white
hover:bg-nuances-400
active:bg-nuances-400
focus-visible:outline
focus-visible:outline-1
focus-visible:outline-offset-[3px]
focus-visible:outline-nuances-black
fill-nuances-white`,

secondary: `bg-nuances-white
text-nuances-black
border
border-nuances-black
hover:bg-nuances-50
active:bg-nuances-50
focus-visible:outline
focus-visible:outline-1
focus-visible:outline-offset-[3px]
focus-visible:outline-nuances-white
fill-nuances-black`,

tertiary: `bg-nuances-black
text-primary-600
underline
hover:text-primary-400
hover:fill-primary-400
active:text-primary-400
active:fill-primary-400
focus-visible:outline-none
fill-primary-600`,
},

disabled: {
primary: `bg-nuances-50
text-nuances-300
fill-nuances-300`,

secondary: `bg-nuances-50
text-nuances-300
fill-nuances-300`,

tertiary: `bg-nuances-black
text-nuances-300
underline
fill-nuances-300`,
},
}
};

return (
Expand All @@ -82,8 +138,8 @@ const Button: React.FC<ButtonProps> = ({
px-4
py-2
rounded-md
${styles.base[variant]}`,
disabled && styles.disabled[variant],
${styles[theme].base[variant]}`,
disabled && styles[theme].disabled[variant],
className
)}
tabIndex={0}
Expand Down Expand Up @@ -114,6 +170,11 @@ const Button: React.FC<ButtonProps> = ({
{rightIcon && (
<span className={`${isLoading && "invisible"}`}>{rightIcon}</span>
)}
{label && (
<span className={`font-semibold text-base ${isLoading && "invisible"}`}>
{label}
</span>
)}
</div>
</button>
);
Expand Down
152 changes: 152 additions & 0 deletions ui/src/components/dropdowns/PaginationDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import React, { useState, useRef } from "react";
import { twMerge } from "tailwind-merge";
import {
useFloating,
useClick,
useDismiss,
useRole,
useListNavigation,
useTypeahead,
useInteractions,
FloatingFocusManager,
offset,
flip,
size,
autoUpdate,
FloatingPortal,
} from "@floating-ui/react";

import Dropdown from "@/components/core/Dropdown";

export interface PaginationDropdownProps {
className?: string;
variant?: "light" | "dark";
disabled?: boolean;
selectedPagination: number;
setSelectedPagination: (pagination: number) => void;
}

const options: Array<{ value: number; label: string }> = [
{ value: 5, label: "5" },
{ value: 10, label: "10" },
{ value: 25, label: "25" },
{ value: 50, label: "50" },
];

const PaginationDropdown: React.FC<PaginationDropdownProps> = ({
className,
variant = "light",
disabled,
selectedPagination,
setSelectedPagination,
}) => {
const [isOpen, setIsOpen] = useState(false);
const [activeIndex, setActiveIndex] = useState<number | null>(null);

const listElementsRef = useRef<Array<HTMLElement | null>>([]);
const listContentRef = useRef<Array<string | null>>([]);
const isTypingRef = useRef(false);

const { refs, floatingStyles, context } = useFloating<HTMLElement>({
placement: "bottom-start",
open: isOpen,
onOpenChange: setIsOpen,
whileElementsMounted: autoUpdate,
middleware: [
offset(8),
flip(),
size({
apply({ availableHeight, elements }) {
elements.floating.style.maxHeight = `${availableHeight}px`;
},
padding: 8,
}),
],
});

const click = useClick(context, {
enabled: !disabled,
event: "mousedown",
});
const listNavigation = useListNavigation(context, {
enabled: !disabled,
listRef: listElementsRef,
activeIndex: activeIndex,
onNavigate: setActiveIndex,
});
const typeahead = useTypeahead(context, {
enabled: !disabled,
listRef: listContentRef,
activeIndex: activeIndex,
onMatch: setActiveIndex,
onTypingChange(isTyping) {
isTypingRef.current = isTyping;
},
});
const dismiss = useDismiss(context);
const role = useRole(context, { role: "select" });

const { getReferenceProps, getFloatingProps } = useInteractions(
[click, listNavigation, typeahead, dismiss, role]
);


const styles = {
light: `bg-nuances-white
text-primary-600
shadow-light`,
dark: `bg-nuances-black
text-nuances-300
shadow-dark`,
};

return (
<>
<Dropdown
className={className}
label={selectedPagination.toString()}
filled={true}
disabled={disabled}
variant={variant}
ref={refs.setReference}
{...getReferenceProps()}
/>
{isOpen && (
<FloatingPortal>
<FloatingFocusManager context={context} modal={false}>
<div
ref={refs.setFloating}
style={floatingStyles}
className={twMerge(
`flex
flex-col
rounded-lg
outline-none
p-2`,
styles[variant]
)}
{...getFloatingProps()}
>
<div className="flex flex-col gap-1 overflow-auto">
{options.map(({ value, label }) => (
<button
key={value}
className="outline-none"
onClick={() => {
setSelectedPagination(value)
setIsOpen(false)
}}
>
{label}
</button>
))}
</div>
</div>
</FloatingFocusManager>
</FloatingPortal>
)}
</>
);
};

export default PaginationDropdown;
Loading

0 comments on commit c894d97

Please sign in to comment.