diff --git a/.gitignore b/.gitignore index 7fa505c..3c7c7db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules storybook-static -dist \ No newline at end of file +dist +*.tgz \ No newline at end of file diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 6110f61..8e1a728 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -1,4 +1,4 @@ -import { attributes } from "../../utils/attributes"; +import { attributes } from "../utils/attributes"; import "./menu.css"; import { LogoInverse } from "../Logo/LogoInverse"; import { ComponentType, useEffect } from "react"; diff --git a/src/components/NetworkIndicator/NetworkIndicator.tsx b/src/components/NetworkIndicator/NetworkIndicator.tsx index 8e270fd..100eb36 100644 --- a/src/components/NetworkIndicator/NetworkIndicator.tsx +++ b/src/components/NetworkIndicator/NetworkIndicator.tsx @@ -1,4 +1,4 @@ -import { classnames } from "../../utils/classnames"; +import { classnames } from "../utils/classnames"; import "./networkIndicator.css"; type Props = { diff --git a/src/components/Tabs/Tabs.tsx b/src/components/Tabs/Tabs.tsx new file mode 100644 index 0000000..5e72299 --- /dev/null +++ b/src/components/Tabs/Tabs.tsx @@ -0,0 +1,32 @@ +import { ComponentType } from "react"; +import "./tabs.css"; +import { classnames } from "../utils/classnames"; + +type Props = { + tabs: { + label: string; + Icon?: ComponentType<{ size?: string }>; + }[]; + onTabChange: (index: number) => void | Promise; + tabIndex: number; +}; + +export function Tabs({ tabs, onTabChange, tabIndex }: Props) { + return ( +
+ {tabs.map((tab, index) => ( +
onTabChange(index)} + > + {tab.Icon && } + {tab.label} +
+ ))} +
+ ); +} diff --git a/src/components/Tabs/tabs.css b/src/components/Tabs/tabs.css new file mode 100644 index 0000000..b054e23 --- /dev/null +++ b/src/components/Tabs/tabs.css @@ -0,0 +1,45 @@ +.tabs { + display: flex; + margin-top: 1rem; + gap: 1rem; + position: relative; +} + +.tabs-tab { + display: flex; + align-items: center; + gap: 0.25rem; + padding-bottom: 1rem; + cursor: pointer; + transition: 0.35s opacity; + z-index: 1; +} + +.tabs-tab::after { + width: 100%; + background-color: var(--codex-background-light); + content: " "; + position: absolute; + height: 2px; + top: 11px; + top: 31px; +} + +.tabs-tab:not(.files-headerTab--active) { + opacity: 0.7; +} + +.tabs-tab:hover { + opacity: 0.85; +} + +.tabs-tab--active { + border-bottom: 2px solid var(--codex-color-contrast); +} + +.tabs-icon { + width: 1rem; + height: 1rem; + display: flex; + place-items: center; +} diff --git a/src/index.ts b/src/index.ts index bc2bc04..95742f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,3 +33,4 @@ export { Tooltip } from "./components/Tooltip/Tooltip"; export { Collapse } from "./components/Collapse/Collapse"; export { Placeholder } from "./components/Placeholder/Placeholder"; export { Sheets } from "./components/Sheets/Sheets"; +export { Tabs } from "./components/Tabs/Tabs"; diff --git a/src/utils/attributes.ts b/src/utils/attributes.ts deleted file mode 100644 index c15c399..0000000 --- a/src/utils/attributes.ts +++ /dev/null @@ -1,6 +0,0 @@ -type Attributes = Record; - -export const attributes = (attributes: Attributes) => - Object.keys(attributes) - .filter((key) => attributes[key] !== false) - .reduce((prev, key) => ({ ...prev, [key]: attributes[key] }), {}); diff --git a/src/utils/bytes.ts b/src/utils/bytes.ts deleted file mode 100644 index 7cf307a..0000000 --- a/src/utils/bytes.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const PrettyBytes = (bytes: number) => { - const sizes = ["bytes", "KB", "MB", "GB", "TB"]; - if (bytes == 0) { - return "0 b"; - } - - const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)).toString()); - - if (i == 0) { - return bytes + " " + sizes[i]; - } - - return (bytes / Math.pow(1024, i)).toFixed(1) + " " + sizes[i]; -}; diff --git a/src/utils/classnames.ts b/src/utils/classnames.ts deleted file mode 100644 index bbec5ce..0000000 --- a/src/utils/classnames.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type Classname = [string, boolean?]; - -export const classnames = (...classnames: Classname[]) => - classnames - .filter(([, visible = true]) => visible) - .map(([name]) => name) - .join(" "); diff --git a/stories/Tabs.stories.tsx b/stories/Tabs.stories.tsx new file mode 100644 index 0000000..d6d08db --- /dev/null +++ b/stories/Tabs.stories.tsx @@ -0,0 +1,48 @@ +import type { Meta } from "@storybook/react"; +import { useState } from "react"; +import { FilesIcon, Star } from "lucide-react"; +import { Tabs } from "../src/components/Tabs/Tabs"; +import { fn } from "@storybook/test"; + +const meta = { + title: "Components/Tabs", + component: Tabs, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: {}, + args: { onTabChange: fn() }, +} satisfies Meta; + +export default meta; + +const Template = (props: { onTabChange: () => void }) => { + const [tabIndex, setTabIndex] = useState(0); + + const onTabChange = (index: number) => { + props.onTabChange(); + setTabIndex(index); + }; + + return ( +
+ , + }, + { + label: "Favorites", + Icon: () => , + }, + ]} + /> +
+ ); +}; + +export const Default = Template.bind({});