Compare commits
3 Commits
72d5425090
...
c585ec3266
| Author | SHA1 | Date | |
|---|---|---|---|
| c585ec3266 | |||
| dd48a02e3b | |||
| 3cdb0b709e |
@@ -1,42 +0,0 @@
|
|||||||
/**
|
|
||||||
* Scans src/components/ and regenerates src/components/index.ts.
|
|
||||||
* Each component folder must have an index.ts that exports the component.
|
|
||||||
* Run: npm run barrel
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { readdir, writeFile, stat } from 'fs/promises';
|
|
||||||
import { join, resolve } from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
|
|
||||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
||||||
const componentsDir = resolve(__dirname, '../src/components');
|
|
||||||
const outputFile = join(componentsDir, 'index.ts');
|
|
||||||
|
|
||||||
const entries = await readdir(componentsDir);
|
|
||||||
|
|
||||||
const componentFolders = (
|
|
||||||
await Promise.all(
|
|
||||||
entries.map(async (name) => {
|
|
||||||
const fullPath = join(componentsDir, name);
|
|
||||||
const info = await stat(fullPath);
|
|
||||||
return info.isDirectory() ? name : null;
|
|
||||||
})
|
|
||||||
)
|
|
||||||
).filter(Boolean);
|
|
||||||
|
|
||||||
if (componentFolders.length === 0) {
|
|
||||||
console.log('No component folders found.');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const lines = [
|
|
||||||
'// Auto-generated by scripts/generate-barrel.mjs — do not edit manually.',
|
|
||||||
'',
|
|
||||||
...componentFolders.map((name) => `export * from './${name}/index.ts';`),
|
|
||||||
'',
|
|
||||||
];
|
|
||||||
|
|
||||||
await writeFile(outputFile, lines.join('\n'), 'utf-8');
|
|
||||||
|
|
||||||
console.log(`Barrel generated with ${componentFolders.length} component(s):`);
|
|
||||||
componentFolders.forEach((name) => console.log(` - ${name}`));
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
@use '../../styles/tokens/typography' as *;
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--nds-spacing-xs);
|
||||||
|
justify-content: center;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: fit-content;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: var(--nds-spacing-2xs);
|
||||||
|
@include text-base;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
color: var(--nds-primary);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: 120%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: var(--nds-primary);
|
||||||
|
border-radius: var(--nds-radius-sm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
id: string;
|
||||||
|
defaultActive?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, defaultActive } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="tab" data-tab-group id={id} data-default-active={defaultActive}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll<HTMLElement>('[data-tab-group]').forEach((group) => {
|
||||||
|
const defaultActive = group.dataset.defaultActive;
|
||||||
|
|
||||||
|
function setActive(itemId: string) {
|
||||||
|
const groupId = group.id;
|
||||||
|
|
||||||
|
group.querySelectorAll<HTMLElement>('[data-tab-item]').forEach((item) => {
|
||||||
|
item.classList.toggle('tab__item--active', item.dataset.tabItem === itemId);
|
||||||
|
item.setAttribute('aria-selected', String(item.dataset.tabItem === itemId));
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll<HTMLElement>(`[data-tab-content][data-tab-id="${groupId}"]`).forEach((content) => {
|
||||||
|
content.hidden = content.dataset.tabItemId !== itemId;
|
||||||
|
});
|
||||||
|
|
||||||
|
group.dispatchEvent(new CustomEvent('tab:change', {
|
||||||
|
detail: { itemId },
|
||||||
|
bubbles: true,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstItem = group.querySelector<HTMLElement>('[data-tab-item]');
|
||||||
|
const initialId = defaultActive ?? firstItem?.dataset.tabItem ?? '';
|
||||||
|
if (initialId) setActive(initialId);
|
||||||
|
|
||||||
|
group.addEventListener('tab-item:click', (e: Event) => {
|
||||||
|
setActive((e as CustomEvent<{ itemId: string }>).detail.itemId);
|
||||||
|
});
|
||||||
|
|
||||||
|
group.addEventListener('tab:set', (e: Event) => {
|
||||||
|
setActive((e as CustomEvent<{ itemId: string }>).detail.itemId);
|
||||||
|
});
|
||||||
|
|
||||||
|
(group as any).setActiveTab = (itemId: string) => setActive(itemId);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use './tab';
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
id: string;
|
||||||
|
itemId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, itemId } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div
|
||||||
|
data-tab-content
|
||||||
|
data-tab-id={id}
|
||||||
|
data-tab-item-id={itemId}
|
||||||
|
hidden
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use './tab';
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
export interface Props {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="tab__item"
|
||||||
|
data-tab-item={id}
|
||||||
|
role="tab"
|
||||||
|
aria-selected="false"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll<HTMLElement>('[data-tab-item]').forEach((item) => {
|
||||||
|
function notify() {
|
||||||
|
const group = item.closest('[data-tab-group]');
|
||||||
|
if (!group) return;
|
||||||
|
group.dispatchEvent(new CustomEvent('tab-item:click', {
|
||||||
|
detail: { itemId: item.dataset.tabItem },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
item.addEventListener('click', notify);
|
||||||
|
item.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use './tab';
|
||||||
|
</style>
|
||||||
@@ -2,4 +2,7 @@
|
|||||||
|
|
||||||
export * from './Icons/index.ts';
|
export * from './Icons/index.ts';
|
||||||
export { default as Notification } from './Notifications/notification.astro';
|
export { default as Notification } from './Notifications/notification.astro';
|
||||||
export { default as Toggle } from './Toggle/toggle.astro';
|
export { default as Toggle } from './Toggle/toggle.astro';
|
||||||
|
export { default as Tab } from './Tabs/tab.astro';
|
||||||
|
export { default as TabItem } from './Tabs/tabItem.astro';
|
||||||
|
export { default as TabContent } from './Tabs/tabContent.astro';
|
||||||
+25
-1
@@ -1,6 +1,10 @@
|
|||||||
---
|
---
|
||||||
import Layout from '../layouts/Layout.astro';
|
import Layout from '../layouts/Layout.astro';
|
||||||
|
import Notification from '../components/Notifications/notification.astro';
|
||||||
import Toggle from '../components/Toggle/toggle.astro';
|
import Toggle from '../components/Toggle/toggle.astro';
|
||||||
|
import Tab from '../components/Tabs/tab.astro';
|
||||||
|
import TabItem from '../components/Tabs/tabItem.astro';
|
||||||
|
import TabContent from '../components/Tabs/tabContent.astro';
|
||||||
import {
|
import {
|
||||||
Arrow2Icon, BinIcon, BurgerIcon, CalendarIcon, CheckIcon, CloseIcon,
|
Arrow2Icon, BinIcon, BurgerIcon, CalendarIcon, CheckIcon, CloseIcon,
|
||||||
CodeIcon, CubeIcon, DownloadIcon, FilterIcon, HelpIcon, HomeIcon,
|
CodeIcon, CubeIcon, DownloadIcon, FilterIcon, HelpIcon, HomeIcon,
|
||||||
@@ -8,7 +12,6 @@ import {
|
|||||||
SearchIcon, SettingsIcon, ShareIcon, ShieldIcon, SortIcon, StatsIcon,
|
SearchIcon, SettingsIcon, ShareIcon, ShieldIcon, SortIcon, StatsIcon,
|
||||||
UploadIcon,
|
UploadIcon,
|
||||||
} from '../components/Icons/index.ts';
|
} from '../components/Icons/index.ts';
|
||||||
import Notification from '../components/Notifications/notification.astro';
|
|
||||||
|
|
||||||
const initialChecked = true;
|
const initialChecked = true;
|
||||||
---
|
---
|
||||||
@@ -16,6 +19,27 @@ const initialChecked = true;
|
|||||||
<Layout>
|
<Layout>
|
||||||
<main>
|
<main>
|
||||||
<h1>Nova Design System</h1>
|
<h1>Nova Design System</h1>
|
||||||
|
<section>
|
||||||
|
<h2>Tabs</h2>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: var(--nds-spacing-md);">
|
||||||
|
<Tab id="my-tab" defaultActive="images">
|
||||||
|
<TabItem id="images">images</TabItem>
|
||||||
|
<TabItem id="videos">Videos</TabItem>
|
||||||
|
<TabItem id="docs">Documents</TabItem>
|
||||||
|
</Tab>
|
||||||
|
<TabContent id="my-tab" itemId="images">
|
||||||
|
<p>my images</p>
|
||||||
|
</TabContent>
|
||||||
|
|
||||||
|
<TabContent id="my-tab" itemId="videos">
|
||||||
|
<p>Video player here</p>
|
||||||
|
</TabContent>
|
||||||
|
|
||||||
|
<TabContent id="my-tab" itemId="docs">
|
||||||
|
<p>Document list here</p>
|
||||||
|
</TabContent>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>Toggle</h2>
|
<h2>Toggle</h2>
|
||||||
<div style="display: flex; flex-direction: column; gap: var(--nds-spacing-md);">
|
<div style="display: flex; flex-direction: column; gap: var(--nds-spacing-md);">
|
||||||
|
|||||||
@@ -1,49 +1,51 @@
|
|||||||
@import url('https://fonts.googleapis.com/css?family=Intel%20One%20Mono:700|Intel%20One%20Mono:400');
|
@import url('https://fonts.googleapis.com/css?family=Intel%20One%20Mono:700|Intel%20One%20Mono:400');
|
||||||
|
|
||||||
|
$font-mono: 'Intel One Mono', monospace;
|
||||||
|
|
||||||
@mixin text-sm {
|
@mixin text-sm {
|
||||||
font-size: 0.600rem;
|
font-size: 0.600rem;
|
||||||
font-family: 'Intel One Mono';
|
font-family: $font-mono;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin text-base {
|
@mixin text-base {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
font-family: 'Intel One Mono';
|
font-family: $font-mono;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin text-label {
|
@mixin text-label {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
font-family: 'Intel One Mono';
|
font-family: $font-mono;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin text-xl {
|
@mixin text-xl {
|
||||||
font-size: 1.066rem;
|
font-size: 1.066rem;
|
||||||
font-family: 'Intel One Mono';
|
font-family: $font-mono;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin text-2xl {
|
@mixin text-2xl {
|
||||||
font-size: 1.421rem;
|
font-size: 1.421rem;
|
||||||
font-family: 'Intel One Mono';
|
font-family: $font-mono;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin text-3xl {
|
@mixin text-3xl {
|
||||||
font-size: 1.894rem;
|
font-size: 1.894rem;
|
||||||
font-family: 'Intel One Mono';
|
font-family: $font-mono;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin text-4xl {
|
@mixin text-4xl {
|
||||||
font-size: 2.525rem;
|
font-size: 2.525rem;
|
||||||
font-family: 'Intel One Mono';
|
font-family: $font-mono;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin text-5xl {
|
@mixin text-5xl {
|
||||||
font-size: 3.366rem;
|
font-size: 3.366rem;
|
||||||
font-family: 'Intel One Mono';
|
font-family: $font-mono;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user