Skip to content

Commit 779f18b

Browse files
UI(breadcrumb): Add breadcrumb directive
1 parent 5649ae2 commit 779f18b

9 files changed

Lines changed: 377 additions & 8 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Component } from '@angular/core';
2+
import { ComponentPreview } from '@components/component-preview/component-preview';
3+
import { breadcrumbVariants, breadcrumbMeta } from './breadcrumb.variants';
4+
5+
@Component({
6+
selector: 'docs-breadcrumb',
7+
standalone: true,
8+
imports: [ComponentPreview],
9+
template: `
10+
<docs-component-preview
11+
[meta]="breadcrumbMeta"
12+
[variants]="breadcrumbVariants">
13+
</docs-component-preview>
14+
`
15+
})
16+
export class Breadcrumb {
17+
breadcrumbMeta = breadcrumbMeta;
18+
breadcrumbVariants = breadcrumbVariants;
19+
}
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import { Component } from '@angular/core';
2+
import { UiBreadcrumb, UiBreadcrumbItem, UiBreadcrumbLink, UiBreadcrumbPage, UiBreadcrumbSeparator, UiBreadcrumbList, UiBreadcrumbEllipsis } from 'ui';
3+
import { IVariant, IComponentMeta } from '@components/component-preview/component-preview';
4+
import { NgIcon, provideIcons } from '@ng-icons/core';
5+
import { lucideChevronRight, lucideHouse, lucideFileText, lucideSettings, lucideEllipsis } from '@ng-icons/lucide';
6+
7+
// Breadcrumb example components for dynamic rendering
8+
@Component({
9+
selector: 'breadcrumb-default-example',
10+
standalone: true,
11+
template: `
12+
<nav uiBreadcrumb>
13+
<ol uiBreadcrumbList>
14+
<li uiBreadcrumbItem>
15+
<a uiBreadcrumbLink href="#">Home</a>
16+
</li>
17+
<li uiBreadcrumbSeparator>
18+
<ng-icon name="lucideChevronRight" size="16" />
19+
</li>
20+
<li uiBreadcrumbItem>
21+
<a uiBreadcrumbLink href="#">Components</a>
22+
</li>
23+
<li uiBreadcrumbSeparator>
24+
<ng-icon name="lucideChevronRight" size="16" />
25+
</li>
26+
<li uiBreadcrumbItem>
27+
<span uiBreadcrumbPage>Breadcrumb</span>
28+
</li>
29+
</ol>
30+
</nav>
31+
`,
32+
imports: [UiBreadcrumb, UiBreadcrumbList, UiBreadcrumbItem, UiBreadcrumbLink, UiBreadcrumbPage, UiBreadcrumbSeparator, NgIcon],
33+
providers: [provideIcons({ lucideChevronRight })]
34+
})
35+
export class BreadcrumbDefaultExample {}
36+
37+
@Component({
38+
selector: 'breadcrumb-with-icons-example',
39+
standalone: true,
40+
template: `
41+
<nav uiBreadcrumb>
42+
<ol uiBreadcrumbList>
43+
<li uiBreadcrumbItem>
44+
<a uiBreadcrumbLink href="#" class="flex items-center gap-1">
45+
<ng-icon name="lucideHouse" size="16" />
46+
Home
47+
</a>
48+
</li>
49+
<li uiBreadcrumbSeparator>
50+
<ng-icon name="lucideChevronRight" size="16" />
51+
</li>
52+
<li uiBreadcrumbItem>
53+
<a uiBreadcrumbLink href="#" class="flex items-center gap-1">
54+
<ng-icon name="lucideFileText" size="16" />
55+
Documents
56+
</a>
57+
</li>
58+
<li uiBreadcrumbSeparator>
59+
<ng-icon name="lucideChevronRight" size="16" />
60+
</li>
61+
<li uiBreadcrumbItem>
62+
<span uiBreadcrumbPage class="flex items-center gap-1">
63+
<ng-icon name="lucideSettings" size="16" />
64+
Settings
65+
</span>
66+
</li>
67+
</ol>
68+
</nav>
69+
`,
70+
imports: [UiBreadcrumb, UiBreadcrumbList, UiBreadcrumbItem, UiBreadcrumbLink, UiBreadcrumbPage, UiBreadcrumbSeparator, NgIcon],
71+
providers: [provideIcons({ lucideChevronRight, lucideHouse, lucideFileText, lucideSettings })]
72+
})
73+
export class BreadcrumbWithIconsExample {}
74+
75+
@Component({
76+
selector: 'breadcrumb-collapsed-example',
77+
standalone: true,
78+
template: `
79+
<nav uiBreadcrumb>
80+
<ol uiBreadcrumbList>
81+
<li uiBreadcrumbItem>
82+
<a uiBreadcrumbLink href="#">Home</a>
83+
</li>
84+
<li uiBreadcrumbSeparator>
85+
<ng-icon name="lucideChevronRight" size="16" />
86+
</li>
87+
<li uiBreadcrumbItem>
88+
<span uiBreadcrumbEllipsis>
89+
<ng-icon name="lucideEllipsis" size="16" />
90+
</span>
91+
</li>
92+
<li uiBreadcrumbSeparator>
93+
<ng-icon name="lucideChevronRight" size="16" />
94+
</li>
95+
<li uiBreadcrumbItem>
96+
<a uiBreadcrumbLink href="#">Components</a>
97+
</li>
98+
<li uiBreadcrumbSeparator>
99+
<ng-icon name="lucideChevronRight" size="16" />
100+
</li>
101+
<li uiBreadcrumbItem>
102+
<span uiBreadcrumbPage>Breadcrumb</span>
103+
</li>
104+
</ol>
105+
</nav>
106+
`,
107+
imports: [UiBreadcrumb, UiBreadcrumbList, UiBreadcrumbItem, UiBreadcrumbLink, UiBreadcrumbPage, UiBreadcrumbSeparator, UiBreadcrumbEllipsis, NgIcon],
108+
providers: [provideIcons({ lucideChevronRight, lucideEllipsis })]
109+
})
110+
export class BreadcrumbCollapsedExample {}
111+
112+
export const breadcrumbVariants: IVariant[] = [
113+
{
114+
title: 'Default',
115+
component: BreadcrumbDefaultExample,
116+
code: `<nav uiBreadcrumb>
117+
<ol uiBreadcrumbList>
118+
<li uiBreadcrumbItem>
119+
<a uiBreadcrumbLink href="#">Home</a>
120+
</li>
121+
<li uiBreadcrumbSeparator>
122+
<ng-icon name="lucideChevronRight" size="16" />
123+
</li>
124+
<li uiBreadcrumbItem>
125+
<a uiBreadcrumbLink href="#">Components</a>
126+
</li>
127+
<li uiBreadcrumbSeparator>
128+
<ng-icon name="lucideChevronRight" size="16" />
129+
</li>
130+
<li uiBreadcrumbItem>
131+
<span uiBreadcrumbPage>Breadcrumb</span>
132+
</li>
133+
</ol>
134+
</nav>`
135+
},
136+
{
137+
title: 'With Icons',
138+
component: BreadcrumbWithIconsExample,
139+
code: `<nav uiBreadcrumb>
140+
<ol uiBreadcrumbList>
141+
<li uiBreadcrumbItem>
142+
<a uiBreadcrumbLink href="#" class="flex items-center gap-1">
143+
<ng-icon name="lucideHouse" size="16" />
144+
Home
145+
</a>
146+
</li>
147+
<li uiBreadcrumbSeparator>
148+
<ng-icon name="lucideChevronRight" size="16" />
149+
</li>
150+
<li uiBreadcrumbItem>
151+
<a uiBreadcrumbLink href="#" class="flex items-center gap-1">
152+
<ng-icon name="lucideFileText" size="16" />
153+
Documents
154+
</a>
155+
</li>
156+
<li uiBreadcrumbSeparator>
157+
<ng-icon name="lucideChevronRight" size="16" />
158+
</li>
159+
<li uiBreadcrumbItem>
160+
<span uiBreadcrumbPage class="flex items-center gap-1">
161+
<ng-icon name="lucideSettings" size="16" />
162+
Settings
163+
</span>
164+
</li>
165+
</ol>
166+
</nav>`
167+
},
168+
{
169+
title: 'Collapsed',
170+
component: BreadcrumbCollapsedExample,
171+
code: `<nav uiBreadcrumb>
172+
<ol uiBreadcrumbList>
173+
<li uiBreadcrumbItem>
174+
<a uiBreadcrumbLink href="#">Home</a>
175+
</li>
176+
<li uiBreadcrumbSeparator>
177+
<ng-icon name="lucideChevronRight" size="16" />
178+
</li>
179+
<li uiBreadcrumbItem>
180+
<span uiBreadcrumbEllipsis>
181+
<ng-icon name="lucideX" size="16" />
182+
</span>
183+
</li>
184+
<li uiBreadcrumbSeparator>
185+
<ng-icon name="lucideChevronRight" size="16" />
186+
</li>
187+
<li uiBreadcrumbItem>
188+
<a uiBreadcrumbLink href="#">Components</a>
189+
</li>
190+
<li uiBreadcrumbSeparator>
191+
<ng-icon name="lucideChevronRight" size="16" />
192+
</li>
193+
<li uiBreadcrumbItem>
194+
<span uiBreadcrumbPage>Breadcrumb</span>
195+
</li>
196+
</ol>
197+
</nav>`
198+
}
199+
];
200+
201+
export const breadcrumbMeta: IComponentMeta = {
202+
title: 'Breadcrumb',
203+
description: 'A navigation component that shows the current page location within a hierarchy.',
204+
installation: {
205+
package: 'breadcrumb',
206+
import: `import { UiBreadcrumb, UiBreadcrumbList, UiBreadcrumbItem, UiBreadcrumbLink, UiBreadcrumbPage, UiBreadcrumbSeparator, UiBreadcrumbEllipsis } from '@workspace/ui';`,
207+
usage: `<nav uiBreadcrumb>
208+
<ol uiBreadcrumbList>
209+
<li uiBreadcrumbItem>
210+
<a uiBreadcrumbLink href="#">Home</a>
211+
</li>
212+
<li uiBreadcrumbSeparator>
213+
<ng-icon name=\"lucideChevronRight\" size=\"16\" />
214+
</li>
215+
<li uiBreadcrumbItem>
216+
<span uiBreadcrumbPage>Current</span>
217+
</li>
218+
</ol>
219+
</nav>`
220+
},
221+
api: {
222+
props: [
223+
{ name: 'uiBreadcrumb', description: 'Main breadcrumb navigation container', type: 'Directive' },
224+
{ name: 'uiBreadcrumbList', description: 'Breadcrumb list wrapper (ol/ul)', type: 'Directive' },
225+
{ name: 'uiBreadcrumbItem', description: 'Individual breadcrumb item', type: 'Directive' },
226+
{ name: 'uiBreadcrumbLink', description: 'Clickable breadcrumb link', type: 'Directive' },
227+
{ name: 'uiBreadcrumbPage', description: 'Current page indicator (non-clickable)', type: 'Directive' },
228+
{ name: 'uiBreadcrumbSeparator', description: 'Separator between breadcrumb items', type: 'Directive' },
229+
{ name: 'uiBreadcrumbEllipsis', description: 'Collapsed items indicator', type: 'Directive' }
230+
]
231+
}
232+
};

projects/docs/src/app/pages/docs/components/components.routes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export const routes: Routes = [
2020
path: 'badge',
2121
loadComponent: () => import('./badge/badge').then(m => m.Badge)
2222
},
23+
{
24+
path: 'breadcrumb',
25+
loadComponent: () => import('./breadcrumb/breadcrumb').then(m => m.Breadcrumb)
26+
},
2327
{
2428
path: 'card',
2529
loadComponent: () => import('./card/card').then(m => m.Card)

projects/docs/src/app/shared/components/sidebar/sidebar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export class Sidebar {
4949
{ name: 'Avatar', path: 'avatar' },
5050
{ name: 'Badge', path: 'badge' },
5151
{ name: 'Button', path: 'button' },
52+
{ name: 'Breadcrumb', path: 'breadcrumb' },
5253
{ name: 'Card', path: 'card' },
5354
// { name: 'Checkbox', path: 'checkbox' },
5455
{ name: 'Dialog', path: 'dialog' },
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { computed, Directive, input } from '@angular/core';
2+
import { tv } from 'tailwind-variants';
3+
4+
const breadcrumbVariants = tv({
5+
slots: {
6+
breadcrumb: 'flex flex-wrap items-center gap-1 break-words text-sm text-muted-foreground',
7+
breadcrumbList: 'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',
8+
breadcrumbItem: 'inline-flex items-center gap-1.5',
9+
breadcrumbLink: 'hover:text-foreground transition-colors',
10+
breadcrumbPage: 'text-foreground font-normal',
11+
breadcrumbSeparator: 'size-3.5',
12+
breadcrumbEllipsis: 'flex size-9 items-center justify-center'
13+
}
14+
});
15+
16+
const { breadcrumb, breadcrumbList, breadcrumbItem, breadcrumbLink, breadcrumbPage, breadcrumbSeparator, breadcrumbEllipsis } = breadcrumbVariants();
17+
18+
@Directive({
19+
selector: '[uiBreadcrumb]',
20+
exportAs: 'uiBreadcrumb',
21+
host: {
22+
'[class]': 'computedClass()'
23+
}
24+
})
25+
export class UiBreadcrumb {
26+
inputClass = input<string>('', { alias: 'class' });
27+
computedClass = computed(() => breadcrumb({ class: this.inputClass() }));
28+
}
29+
30+
@Directive({
31+
selector: '[uiBreadcrumbList]',
32+
exportAs: 'uiBreadcrumbList',
33+
host: {
34+
'[class]': 'computedClass()'
35+
}
36+
})
37+
export class UiBreadcrumbList {
38+
inputClass = input<string>('', { alias: 'class' });
39+
computedClass = computed(() => breadcrumbList({ class: this.inputClass() }));
40+
}
41+
42+
@Directive({
43+
selector: '[uiBreadcrumbItem]',
44+
exportAs: 'uiBreadcrumbItem',
45+
host: {
46+
'[class]': 'computedClass()'
47+
}
48+
})
49+
export class UiBreadcrumbItem {
50+
inputClass = input<string>('', { alias: 'class' });
51+
computedClass = computed(() => breadcrumbItem({ class: this.inputClass() }));
52+
}
53+
54+
@Directive({
55+
selector: '[uiBreadcrumbLink]',
56+
exportAs: 'uiBreadcrumbLink',
57+
host: {
58+
'[class]': 'computedClass()'
59+
}
60+
})
61+
export class UiBreadcrumbLink {
62+
inputClass = input<string>('', { alias: 'class' });
63+
computedClass = computed(() => breadcrumbLink({ class: this.inputClass() }));
64+
}
65+
66+
@Directive({
67+
selector: '[uiBreadcrumbPage]',
68+
exportAs: 'uiBreadcrumbPage',
69+
host: {
70+
'[class]': 'computedClass()'
71+
}
72+
})
73+
export class UiBreadcrumbPage {
74+
inputClass = input<string>('', { alias: 'class' });
75+
computedClass = computed(() => breadcrumbPage({ class: this.inputClass() }));
76+
}
77+
78+
@Directive({
79+
selector: '[uiBreadcrumbSeparator]',
80+
exportAs: 'uiBreadcrumbSeparator',
81+
host: {
82+
'[class]': 'computedClass()'
83+
}
84+
})
85+
export class UiBreadcrumbSeparator {
86+
inputClass = input<string>('', { alias: 'class' });
87+
computedClass = computed(() => breadcrumbSeparator({ class: this.inputClass() }));
88+
}
89+
90+
@Directive({
91+
selector: '[uiBreadcrumbEllipsis]',
92+
exportAs: 'uiBreadcrumbEllipsis',
93+
host: {
94+
'[class]': 'computedClass()'
95+
}
96+
})
97+
export class UiBreadcrumbEllipsis {
98+
inputClass = input<string>('', { alias: 'class' });
99+
computedClass = computed(() => breadcrumbEllipsis({ class: this.inputClass() }));
100+
}

projects/ui/src/directives/label.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ import { tv } from "tailwind-variants";
33
import { NgpLabel } from "ng-primitives/form-field";
44

55
const labelVariants = tv({
6-
base: 'flex items-center gap-2 text-sm leading-none font-medium select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50'
6+
slots: {
7+
label: 'flex items-center gap-2 text-sm leading-none font-medium select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50'
8+
}
79
});
810

11+
const { label } = labelVariants();
12+
913
@Directive({
1014
selector: '[uiLabel]',
1115
exportAs: 'uiLabel',
@@ -16,5 +20,5 @@ const labelVariants = tv({
1620
})
1721
export class UiLabel {
1822
inputClass = input<string>('', { alias: 'class' });
19-
computedClass = computed(() => labelVariants({ class: this.inputClass() }));
23+
computedClass = computed(() => label({ class: this.inputClass() }));
2024
}

0 commit comments

Comments
 (0)