diff --git a/src/App.tsx b/src/App.tsx index dc3fb29..ecd2488 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,6 @@ import { GlobalStyle } from "./styles/global-style"; import { lightTheme, darkTheme } from "./styles/themes"; import Button from "./lib/button"; import Tabs from "./lib/pagination/tabs"; -import Card from "./lib/container/card"; import Buttons from "./examples/buttons"; import Pagination from "./examples/pagination"; import Containers from "./examples/containers"; @@ -19,6 +18,7 @@ import TimelineProgress from "./examples/timeline"; import Input from "./examples/input"; import Tooltips from "./examples/tooltip"; import Copiable from "./examples/copiable"; +import clsx from "clsx"; const StyledDiv = styled.div` position: fixed; @@ -38,29 +38,9 @@ const StyledDiv = styled.div` ${(props) => props.theme.klerosUIComponentsTransitionSpeed}; `; -const StyledCard = styled(Card)` - height: 500px; - width: 1000px; - display: flex; - justify-content: space-around; - align-items: center; - gap: 24px; - overflow: auto; - flex-wrap: wrap; - padding: 36px 36px; - background: ${(props) => props.theme.klerosUIComponentsLightBackground}; - transition: background ease - ${(props) => props.theme.klerosUIComponentsTransitionSpeed}; -`; - -const StyledTabs = styled(Tabs)` - width: 995px; -`; - const App = () => { const [theme, setTheme] = useState(lightTheme); const [tailwindTheme, setTailwindTheme] = useState("light"); - const [example, setExample] = useState("buttons"); // temporary const changeTheme = () => { @@ -80,40 +60,97 @@ const App = () => { - , + }, + { + text: "Pagination", + value: "pagination", + id: "pagination", + content: , + }, + { + text: "Containers", + value: "containers", + id: "containers", + content: , + }, + { + text: "Accordion", + value: "accordion", + id: "accordion", + content: , + }, + { + text: "Form", + value: "form", + id: "content", + content:
, + }, + { + text: "Dropdowns", + value: "dropdowns", + id: "dropdowns", + content: , + }, + { + text: "Displays", + value: "displays", + id: "displays", + content: , + }, + { + text: "Messages", + value: "messages", + id: "messages", + content: , + }, + { + text: "Timeline", + value: "timeline", + id: "timeline", + content: , + }, + { + text: "Progress", + value: "progress", + id: "progress", + content: , + }, + { + text: "Input", + value: "input", + id: "input", + content: , + }, + { + text: "Tooltip", + value: "tooltip", + id: "tooltip", + content: , + }, + { + text: "Copiable", + value: "copiable", + id: "copiable", + content: , + }, ]} - callback={setExample} - currentValue={example} /> - - {example === "buttons" && } - {example === "pagination" && } - {example === "containers" && } - {example === "accordion" && } - {example === "form" && } - {example === "dropdowns" && } - {example === "displays" && } - {example === "messages" && } - {example === "timeline" && } - {example === "progress" && } - {example === "input" && } - {example === "tooltip" && } - {example === "copiable" && } - + ); +}; interface CompactPaginationProps { currentPage: number; numPages: number; callback: (newPage: number) => void; + /** Callback function called when end of pages has been reached */ onCloseOnLastPage?: () => void; label?: ReactNode; className?: string; } -const CompactPagination: React.FC = ({ +function CompactPagination({ currentPage, numPages, callback, onCloseOnLastPage, label, className, -}) => { +}: Readonly) { const [{ incrementPage, decrementPage, minPageReached, maxPageReached }] = usePagination(currentPage, numPages, callback, onCloseOnLastPage); return ( - - {label} - - - +
+ + {label} + + + + {currentPage === numPages && onCloseOnLastPage ? ( - - - + + + ) : ( - - - + + + )} - +
); -}; +} export default CompactPagination; diff --git a/src/lib/pagination/standard.tsx b/src/lib/pagination/standard.tsx index 59d9440..efcc67d 100644 --- a/src/lib/pagination/standard.tsx +++ b/src/lib/pagination/standard.tsx @@ -1,112 +1,58 @@ import React from "react"; -import styled from "styled-components"; import usePagination from "../../hooks/pagination/use-pagination"; import Arrow from "../../assets/svgs/arrows/light-left.svg"; -import { borderBox, button, svg } from "../../styles/common-style"; - -const Wrapper = styled.div` - ${borderBox} - display: flex; - align-items: center; - justify-content: center; -`; - -const PageButton = styled.button<{ selected?: boolean }>` - ${button} - height: 32px; - width: 32px; - margin: 4px; - background: ${(props) => - props.selected - ? props.theme.klerosUIComponentsLightBlue - : props.theme.klerosUIComponentsWhiteBackground}; - border: 1px solid - ${(props) => - props.selected - ? props.theme.klerosUIComponentsPrimaryBlue - : props.theme.klerosUIComponentsStroke}; - border-radius: 3px; - display: flex; - align-items: center; - justify-content: center; - - font-size: 14px; - color: ${(props) => - props.selected - ? props.theme.klerosUIComponentsPrimaryBlue - : props.theme.klerosUIComponentsPrimaryText}; - - :hover:enabled { - background: ${(props) => - props.selected - ? props.theme.klerosUIComponentsWhiteBackground - : props.theme.klerosUIComponentsLightBlue}; - border: 1px solid - ${(props) => - props.selected - ? props.theme.klerosUIComponentsPrimaryBlue - : props.theme.klerosUIComponentsSecondaryBlue}; - color: ${(props) => - props.selected - ? props.theme.klerosUIComponentsPrimaryBlue - : props.theme.klerosUIComponentsSecondaryBlue}; - } - - :hover:disabled { - cursor: default; - } -`; - -const StyledArrow = styled.svg``; - -const ArrowButton = styled(PageButton)` - & ${StyledArrow} { - ${svg} - fill: ${(props) => - props.disabled - ? props.theme.klerosUIComponentsStroke - : props.theme.klerosUIComponentsPrimaryText}; - transition: fill ease - ${({ theme }) => theme.klerosUIComponentsTransitionSpeed}; - } - - :hover:enabled { - & ${StyledArrow} { - fill: ${({ theme }) => theme.klerosUIComponentsSecondaryBlue}; - } - } -`; - -const LeftArrow = styled(ArrowButton)` - & ${StyledArrow} { - padding-right: 1px; - } -`; - -const RightArrow = styled(ArrowButton)` - & ${StyledArrow} { - padding-left: 1px; - transform: rotate(180deg); - } -`; +import { cn } from "../../utils"; +import { Button, type ButtonProps } from "react-aria-components"; +import clsx from "clsx"; +const PageButton: React.FC = ({ + children, + selected, + className, + ...props +}) => ( + +); interface StandardPaginationProps { currentPage: number; numPages: number; callback: (newPage: number) => void; className?: string; + /** Disables the number buttons, allowing only use of arrows */ disableNumbers?: boolean; + /** Hides the number buttons */ hideNumbers?: boolean; } -const StandardPagination: React.FC = ({ +function StandardPagination({ currentPage, numPages, callback, disableNumbers, hideNumbers, className, -}) => { +}: Readonly) { const [ { incrementPage, @@ -119,26 +65,50 @@ const StandardPagination: React.FC = ({ ] = usePagination(currentPage, numPages, callback); return ( - - - - +
+ + + {!hideNumbers && getPageRange().map((i) => ( goToPage(i)} - disabled={disableNumbers} + onPress={() => goToPage(i)} + isDisabled={disableNumbers} > {i} ))} - - - - + + + +
); -}; +} export default StandardPagination; diff --git a/src/lib/pagination/tabs.tsx b/src/lib/pagination/tabs.tsx index 6f125c6..8e5ba52 100644 --- a/src/lib/pagination/tabs.tsx +++ b/src/lib/pagination/tabs.tsx @@ -1,98 +1,147 @@ -import React from "react"; -import styled from "styled-components"; -import { - borderBox, - button, - hoverShortTransitionTiming, - hoverLongTransitionTiming, - svg, -} from "../../styles/common-style"; - -const Wrapper = styled.div` - ${borderBox} - height: fit-content; - width: 500px; - display: flex; -`; - -const StyledSVG = styled.svg``; - -const StyledTab = styled.button<{ selected?: boolean }>` - ${button} - ${hoverShortTransitionTiming} - flex-grow: 1; - height: 45px; - background: none; - border-bottom: 3px solid - ${(props) => - props.selected - ? props.theme.klerosUIComponentsPrimaryBlue - : props.theme.klerosUIComponentsStroke}; - display: flex; - align-items: center; - justify-content: center; - - color: ${(props) => { - if (props.selected) return props.theme.klerosUIComponentsPrimaryBlue; - else if (props.disabled) return props.theme.klerosUIComponentsStroke; - else return props.theme.klerosUIComponentsPrimaryText; - }}; - - ${(props) => - !props.disabled && !props.selected - ? `:hover { - ${hoverLongTransitionTiming} - border-bottom: 3px solid - ${props.theme.klerosUIComponentsSecondaryBlue}; - }` - : ""} - - & ${StyledSVG} { - ${svg} - ${hoverShortTransitionTiming} - height: 16px; - width: 16px; - margin-right: 16px; +import React, { ReactNode, useCallback, useState } from "react"; - fill: ${(props) => { - if (props.selected) return props.theme.klerosUIComponentsPrimaryBlue; - else if (props.disabled) return props.theme.klerosUIComponentsStroke; - else return props.theme.klerosUIComponentsPrimaryText; - }}; - } -`; +import { cn } from "../../utils"; +import { + Tabs as AriaTabs, + Collection, + Tab, + TabList, + TabPanel, + type TabsProps as AriaTabsProps, + type Key, + type TabListProps, + type TabPanelProps, + type TabProps, +} from "react-aria-components"; interface TabsItem { + /** Unique id for each tab panel */ + id: Key; text: string; + /** Value associated with each tab. Passed as an arg to callback function. */ value: any; Icon?: React.FC>; icon?: React.ReactNode; disabled?: boolean; + /** Content to display when this tab is selected. */ + content: ReactNode; + /** Props for Tab + * [See TabProps](https://react-spectrum.adobe.com/react-aria/Tabs.html#tab) + */ + tabProps?: TabProps; + /** + * Can be used to provide separate styling for a TabPanel, apart from one passed in panelClassName parent props. + * [See TabPanelProps](https://react-spectrum.adobe.com/react-aria/Tabs.html#tabpanel) + */ + tabPanelProps?: TabPanelProps; } -interface TabsProps { - currentValue: any; +interface TabsProps extends Omit { items: TabsItem[]; // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type - callback: Function; + callback?: Function; + className?: string; + /** ClassName to provide a common style for all TabPanels */ + panelClassName?: string; + /** + * Can be used to override default style. + * [See TablistProps](https://react-spectrum.adobe.com/react-aria/Tabs.html#tablist) + */ + tabListProps?: TabListProps; } -const Tabs: React.FC = ({ items, ...props }) => { +/** Tabs organize content into multiple sections and allow users to navigate between them. */ +function Tabs({ + items, + className, + tabListProps, + panelClassName, + callback, + defaultSelectedKey, + ...props +}: Readonly) { + const [selectedKey, setSelectedKey] = useState( + defaultSelectedKey, + ); + + const handleSelection = useCallback( + (key: Key) => { + setSelectedKey(key); + const selectedItem = items.find((item) => item.text === key); + if (selectedItem && callback) callback(key, selectedItem.value); + }, + [items, callback], + ); + return ( - - {items.map(({ Icon, icon, text, value, disabled }) => ( - props.callback(value)} - > - {icon ?? (Icon && )} - {text} - - ))} - + + + {items.map(({ id, Icon, icon, text, disabled }) => ( + + {icon ?? + (Icon && ( + + ))} + + {text} + + + ))} + + + + {(item) => ( + + {item.content} + + )} + + ); -}; +} export default Tabs; diff --git a/src/stories/compactPagination.stories.tsx b/src/stories/compactPagination.stories.tsx new file mode 100644 index 0000000..bceb284 --- /dev/null +++ b/src/stories/compactPagination.stories.tsx @@ -0,0 +1,74 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { IPreviewArgs } from "./utils"; + +import Pagination from "../lib/pagination/compact"; +import React, { useState } from "react"; + +const meta = { + component: Pagination, + title: "Pagination/Compact Pagination", + tags: ["autodocs"], + argTypes: { + numPages: { + control: "number", + }, + currentPage: { + control: "number", + }, + className: { + control: "text", + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj & IPreviewArgs; + +export const CompactPagination: Story = { + args: { + themeUI: "light", + backgroundUI: "light", + numPages: 6, + currentPage: 0, + callback: () => {}, + className: "w-full", + label: "Label:", + }, + render: function Render(args) { + const [currentPage, setCurrentPage] = useState(1); + + return ( + + ); + }, +}; + +export const CompactPaginationWithCloseCallback: Story = { + args: { + themeUI: "light", + backgroundUI: "light", + numPages: 6, + currentPage: 0, + callback: () => {}, + className: "w-full", + label: "Shows close button in end.", + }, + render: function Render(args) { + const [currentPage, setCurrentPage] = useState(1); + + return ( + {}} + /> + ); + }, +}; diff --git a/src/stories/standardPagination.stories.tsx b/src/stories/standardPagination.stories.tsx new file mode 100644 index 0000000..c91f1da --- /dev/null +++ b/src/stories/standardPagination.stories.tsx @@ -0,0 +1,57 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { IPreviewArgs } from "./utils"; + +import Pagination from "../lib/pagination/standard"; +import React, { useState } from "react"; + +const meta = { + component: Pagination, + title: "Pagination/Standard Pagination", + tags: ["autodocs"], + argTypes: { + numPages: { + control: "number", + }, + currentPage: { + control: "number", + }, + className: { + control: "text", + }, + disableNumbers: { + control: "boolean", + }, + hideNumbers: { + control: "boolean", + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj & IPreviewArgs; + +export const StandardPagination: Story = { + args: { + themeUI: "light", + backgroundUI: "light", + numPages: 6, + currentPage: 0, + callback: () => {}, + className: "w-full", + disableNumbers: false, + hideNumbers: false, + }, + render: function Render(args) { + const [currentPage, setCurrentPage] = useState(1); + + return ( + + ); + }, +}; diff --git a/src/stories/tabs.stories.tsx b/src/stories/tabs.stories.tsx new file mode 100644 index 0000000..3661310 --- /dev/null +++ b/src/stories/tabs.stories.tsx @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import React from "react"; +import { IPreviewArgs } from "./utils"; + +import TabsComponent from "../lib/pagination/tabs"; +import Telegram from "../assets/svgs/telegram.svg"; + +const meta = { + component: TabsComponent, + title: "Pagination/Tabs", + tags: ["autodocs"], + argTypes: {}, +} satisfies Meta; + +export default meta; + +type Story = StoryObj & IPreviewArgs; + +export const Tabs: Story = { + args: { + themeUI: "light", + backgroundUI: "light", + className: "w-[500px]", + defaultSelectedKey: "discord", + panelClassName: "bg-klerosUIComponentsLightBlue p-4", + items: [ + { text: "Discord", value: 0, id: "discord", content:

Discord

}, + { + text: "Telegram", + value: 1, + Icon: Telegram, + id: "telegram", + content:

Telegram

, + }, + { + text: "Disabled", + value: 2, + disabled: true, + id: "disabled", + content:

Disabled

, + }, + ], + }, +};