From 8a7ed3d4252b9c975b465646cc404d6e6f27951e Mon Sep 17 00:00:00 2001 From: coderzhaxor Date: Mon, 10 Nov 2025 10:51:09 +0700 Subject: [PATCH] feat(tabs): add tabs component with documentation --- ui/.vscode/settings.json | 10 +++ ui/content/docs/components/tabs.mdx | 123 ++++++++++++++++++++++++++++ ui/public/trakteer/index.css | 42 +++++++++- ui/public/trakteer/tabs/index.css | 48 +++++++++++ ui/public/trakteer/tabs/index.tsx | 93 +++++++++++++++++++++ 5 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 ui/.vscode/settings.json create mode 100644 ui/content/docs/components/tabs.mdx create mode 100644 ui/public/trakteer/tabs/index.css create mode 100644 ui/public/trakteer/tabs/index.tsx diff --git a/ui/.vscode/settings.json b/ui/.vscode/settings.json new file mode 100644 index 0000000..fc5233d --- /dev/null +++ b/ui/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/.DS_Store": true, + "**/Thumbs.db": true + }, + "hide-files.files": [] +} \ No newline at end of file diff --git a/ui/content/docs/components/tabs.mdx b/ui/content/docs/components/tabs.mdx new file mode 100644 index 0000000..a068857 --- /dev/null +++ b/ui/content/docs/components/tabs.mdx @@ -0,0 +1,123 @@ +--- +title: Tabs +description: A tabs component +--- + +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@trakteer/tabs"; +import { TypeTable } from "fumadocs-ui/components/type-table"; + +
+ + + Home + Profile + Settings + + + +

This is Home tab.

+
+ +

This is Profile tab.

+
+ +

This is Settings tab.

+
+
+
+ +### Usage + +```bash tab="npm" +npx @fydemy/ui@latest add tabs +``` + +```bash tab="pnpm" +pnpm dlx @fydemy/ui@latest add tabs +``` + +```bash tab="yarn" +yarn @fydemy/ui@latest add tabs +``` + +```bash tab="bun" +bunx --bun @fydemy/ui@latest add tabs +``` + +```jsx + + + Home + Profile + Settings + + + +

This is Home tab.

+
+ +

This is Profile tab.

+
+ +

This is Settings tab.

+
+
+``` + + + +### Examples + +import { Home, UserRound, Settings } from 'lucide-react'; + +#### With Icons + +A tabs component with icons beside the text. Use `lucide-react` icon is recommended. + +
+ + + Home + Profile + Settings + + + +

Welcome to your home dashboard.

+
+ +

Manage your profile settings here.

+
+ +

Configure your application preferences.

+
+
+
+ +```jsx + + + Home + Profile + Settings + + + +

Welcome to your home dashboard.

+
+ +

Manage your profile settings here.

+
+ +

Configure your application preferences.

+
+
+``` diff --git a/ui/public/trakteer/index.css b/ui/public/trakteer/index.css index f33aa61..20cbe84 100644 --- a/ui/public/trakteer/index.css +++ b/ui/public/trakteer/index.css @@ -17,9 +17,49 @@ --trakteer-dark: #242525; --trakteer-dark-border: #101010; --trakteer-dark-color: #fefefe; + + --trakteer-bg-primary: #ffffff; + --trakteer-bg-secondary: #f9fafb; + + --trakteer-border-light: #e0e0e0; + --trakteer-border-medium: #cccccc; + + --trakteer-text-primary: #242525; + --trakteer-text-secondary: #888888; + --trakteer-text-muted: #a0a0a0; +} + +@media (prefers-color-scheme: dark) { + :root { + --trakteer-default: #3cd278; + --trakteer-default-border: #2b9555; + --trakteer-default-color: #f8f8f8; + + --trakteer-outline: #333333; + --trakteer-outline-border: #666666; + --trakteer-outline-color: #a0a0a0; + + --trakteer-destructive: #ff4757; + --trakteer-destructive-border: #c23342; + --trakteer-destructive-color: #f8f8f8; + + --trakteer-dark: #fefefe; + --trakteer-dark-border: #333333; + --trakteer-dark-color: #242525; + + --trakteer-bg-primary: #1a1a1a; + --trakteer-bg-secondary: #242424; + + --trakteer-border-light: #333333; + --trakteer-border-medium: #444444; + + --trakteer-text-primary: #fefefe; + --trakteer-text-secondary: #a0a0a0; + --trakteer-text-muted: #888888; + } } [data-theme="trakteer"] { margin: 0; padding: 0; -} +} \ No newline at end of file diff --git a/ui/public/trakteer/tabs/index.css b/ui/public/trakteer/tabs/index.css new file mode 100644 index 0000000..86294f7 --- /dev/null +++ b/ui/public/trakteer/tabs/index.css @@ -0,0 +1,48 @@ +/* Tabs Container */ +.tabs[data-theme="trakteer"] { + padding: 1.5rem; + font-family: "Fredoka", sans-serif; + background-color: var(--trakteer-bg-primary); + border: 1px solid var(--trakteer-border-light); + border-bottom: 4px solid var(--trakteer-border-light); + border-radius: 1rem; + transition: background-color 0.3s ease, border-color 0.3s ease; +} + +/* Tabs List */ +.tabs[data-theme="trakteer"]>.tabs-list { + display: flex; + align-items: baseline; + border-bottom: 1px solid var(--trakteer-border-light); + transition: border-color 0.3s ease; +} + +/* Tabs Trigger */ +.tabs[data-theme="trakteer"] [role="button"].tabs-trigger { + position: relative; + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.625rem 1rem; + font-weight: 500; + color: var(--trakteer-text-secondary); + cursor: pointer; + transition: color 0.2s ease; +} + +.tabs[data-theme="trakteer"] [role="button"].tabs-trigger:hover { + color: var(--trakteer-text-primary); +} + +.tabs[data-theme="trakteer"] [role="button"].tabs-trigger[aria-selected="true"] { + color: var(--trakteer-default); + margin-bottom: -1px; + border-bottom: 2px solid var(--trakteer-default); +} + +/* Tabs Content */ +.tabs[data-theme="trakteer"]>.tabs-contents { + padding-top: 1rem; + color: var(--trakteer-text-primary); + transition: color 0.3s ease; +} \ No newline at end of file diff --git a/ui/public/trakteer/tabs/index.tsx b/ui/public/trakteer/tabs/index.tsx new file mode 100644 index 0000000..ce145d8 --- /dev/null +++ b/ui/public/trakteer/tabs/index.tsx @@ -0,0 +1,93 @@ +'use client'; + +import { createContext, useContext, useState, type ReactNode } from 'react'; +import "../index.css"; +import "./index.css"; + +// Types +interface TabsContextType { + value: string; + setValue: (v: string) => void; +} + +interface TabsProps { + defaultValue: string; + children: ReactNode; + variant: TabsVariant; +} + +type TabsVariant = "line"; + +interface TabsListProps { + children: ReactNode; +} + +interface TabsTriggerProps { + value: string; + children: ReactNode; +} + +interface TabsContentProps { + value: string; + children: ReactNode; +} + +// Context +const TabsContext = createContext(undefined); + +const useTabsContext = () => { + const context = useContext(TabsContext); + if (!context) { + throw new Error('Tabs components must be used within a component'); + } + return context; +}; + +// Components +const Tabs = ({ defaultValue, children, variant = "line" }: TabsProps) => { + const [value, setValue] = useState(defaultValue); + + return ( + +
+ {children} +
+
+ ); +}; + +const TabsList = ({ children }: TabsListProps) => { + return
{children}
; +}; + +const TabsTrigger = ({ value, children }: TabsTriggerProps) => { + const { value: activeValue, setValue } = useTabsContext(); + const isActive = activeValue === value; + + return ( +
setValue(value)} + aria-selected={isActive} + className='tabs-trigger' + > + {children} +
+ ); +}; + +const TabsContent = ({ value, children }: TabsContentProps) => { + const { value: activeValue } = useTabsContext(); + + if (activeValue !== value) { + return null; + } + + return
{children}
; +}; + +export { Tabs, TabsList, TabsTrigger, TabsContent }; \ No newline at end of file