Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ui/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/.DS_Store": true,
"**/Thumbs.db": true
},
"hide-files.files": []
}
123 changes: 123 additions & 0 deletions ui/content/docs/components/tabs.mdx
Original file line number Diff line number Diff line change
@@ -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";

<div class="grid place-items-center *:w-full px-10 py-10 border rounded-xl">
<Tabs defaultValue="home">
<TabsList>
<TabsTrigger value="home">Home</TabsTrigger>
<TabsTrigger value="profile">Profile</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>

<TabsContent value="home">
<p>This is Home tab.</p>
</TabsContent>
<TabsContent value="profile">
<p>This is Profile tab.</p>
</TabsContent>
<TabsContent value="settings">
<p>This is Settings tab.</p>
</TabsContent>
</Tabs>
</div>

### 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
<Tabs defaultValue="home">
<TabsList>
<TabsTrigger value="home">Home</TabsTrigger>
<TabsTrigger value="profile">Profile</TabsTrigger>
<TabsTrigger value="settings">Settings</TabsTrigger>
</TabsList>

<TabsContent value="home">
<p>This is Home tab.</p>
</TabsContent>
<TabsContent value="profile">
<p>This is Profile tab.</p>
</TabsContent>
<TabsContent value="settings">
<p>This is Settings tab.</p>
</TabsContent>
</Tabs>
```

<AutoTypeTable
type={`
export type TabsProps = {
defaultValue: string;
children: React.ReactNode;
variant: "line";
};
`}
/>

### Examples

import { Home, UserRound, Settings } from 'lucide-react';

#### With Icons

A tabs component with icons beside the text. Use `lucide-react` icon is recommended.

<div class="grid place-items-center *:w-full px-10 py-10 border rounded-xl">
<Tabs defaultValue="home">
<TabsList>
<TabsTrigger value="home"><Home size={18} /> Home</TabsTrigger>
<TabsTrigger value="profile"><UserRound size={18} /> Profile</TabsTrigger>
<TabsTrigger value="settings"><Settings size={18} /> Settings</TabsTrigger>
</TabsList>

<TabsContent value="home">
<p>Welcome to your home dashboard.</p>
</TabsContent>
<TabsContent value="profile">
<p>Manage your profile settings here.</p>
</TabsContent>
<TabsContent value="settings">
<p>Configure your application preferences.</p>
</TabsContent>
</Tabs>
</div>

```jsx
<Tabs defaultValue="home">
<TabsList>
<TabsTrigger value="home"><Home size={18} /> Home</TabsTrigger>
<TabsTrigger value="profile"><UserRound size={18} /> Profile</TabsTrigger>
<TabsTrigger value="settings"><Settings size={18} /> Settings</TabsTrigger>
</TabsList>

<TabsContent value="home">
<p>Welcome to your home dashboard.</p>
</TabsContent>
<TabsContent value="profile">
<p>Manage your profile settings here.</p>
</TabsContent>
<TabsContent value="settings">
<p>Configure your application preferences.</p>
</TabsContent>
</Tabs>
```
42 changes: 41 additions & 1 deletion ui/public/trakteer/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
48 changes: 48 additions & 0 deletions ui/public/trakteer/tabs/index.css
Original file line number Diff line number Diff line change
@@ -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;
}
93 changes: 93 additions & 0 deletions ui/public/trakteer/tabs/index.tsx
Original file line number Diff line number Diff line change
@@ -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<TabsContextType | undefined>(undefined);

const useTabsContext = () => {
const context = useContext(TabsContext);
if (!context) {
throw new Error('Tabs components must be used within a <Tabs> component');
}
return context;
};

// Components
const Tabs = ({ defaultValue, children, variant = "line" }: TabsProps) => {
const [value, setValue] = useState(defaultValue);

return (
<TabsContext.Provider value={{ value, setValue }}>
<div
data-theme="trakteer"
data-variant={variant}
className='tabs'
>
{children}
</div>
</TabsContext.Provider>
);
};

const TabsList = ({ children }: TabsListProps) => {
return <div className='tabs-list'>{children}</div>;
};

const TabsTrigger = ({ value, children }: TabsTriggerProps) => {
const { value: activeValue, setValue } = useTabsContext();
const isActive = activeValue === value;

return (
<div
role="button"
onClick={() => setValue(value)}
aria-selected={isActive}
className='tabs-trigger'
>
{children}
</div>
);
};

const TabsContent = ({ value, children }: TabsContentProps) => {
const { value: activeValue } = useTabsContext();

if (activeValue !== value) {
return null;
}

return <div className='tabs-content'>{children}</div>;
};

export { Tabs, TabsList, TabsTrigger, TabsContent };
Loading