Skip to content

Commit 8d2fce1

Browse files
Merge pull request #637 from universal-ember/copilot/add-default-styles-issue-636
Create standalone Separator component with @as argument for dynamic element tags
2 parents 29a9eeb + 99e9a31 commit 8d2fce1

7 files changed

Lines changed: 592 additions & 26 deletions

File tree

docs-app/public/docs/3-ui/breadcrumb.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ Breadcrumbs help users understand their current location and provide a way to na
1010
import { Breadcrumb, Menu, PortalTargets } from 'ember-primitives';
1111
1212
<template>
13-
<PortalTargets />
14-
15-
<Breadcrumb as |b|>
13+
<Breadcrumb class="not-prose" as |b|>
1614
<li>
1715
<a href="/">Home</a>
1816
</li>
@@ -40,6 +38,7 @@ import { Breadcrumb, Menu, PortalTargets } from 'ember-primitives';
4038
</li>
4139
</Breadcrumb>
4240
41+
<PortalTargets />
4342
<style>
4443
@scope {
4544
nav {
@@ -183,7 +182,7 @@ You can use any link component, `<a>`, `<LinkTo>`, `<Link>`, etc:
183182
import { Breadcrumb, Link } from 'ember-primitives';
184183
185184
<template>
186-
<Breadcrumb @label="example-links" as |b|>
185+
<Breadcrumb @label="example-links" class="not-prose" as |b|>
187186
<li>
188187
<Link @href="/">Home</Link>
189188
</li>
@@ -245,7 +244,7 @@ You can use any content as a separator, including icons or symbols:
245244
import { Breadcrumb } from 'ember-primitives';
246245
247246
<template>
248-
<Breadcrumb @label="example-separator" as |b|>
247+
<Breadcrumb @label="example-separator" class="not-prose" as |b|>
249248
<li>
250249
<a href="/">Home</a>
251250
</li>
@@ -308,7 +307,7 @@ Since breadcrumbs can contain any component, you can even use buttons for non-na
308307
import { Breadcrumb } from 'ember-primitives';
309308
310309
<template>
311-
<Breadcrumb @label="button-example" as |b|>
310+
<Breadcrumb @label="button-example" class="not-prose" as |b|>
312311
<li>
313312
<a href="/">Home</a>
314313
</li>
@@ -380,7 +379,7 @@ You can provide a custom accessible label for the breadcrumb navigation:
380379
import { Breadcrumb } from 'ember-primitives';
381380
382381
<template>
383-
<Breadcrumb @label="Page Navigation" as |b|>
382+
<Breadcrumb @label="Page Navigation" class="not-prose" as |b|>
384383
<li>
385384
<a href="/">Home</a>
386385
</li>
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
# Separator
2+
3+
A component for rendering both **semantic** separators and **decorative** separators.
4+
5+
The `Separator` is **80% documentation and 20% boilerplate reduction**.
6+
7+
- By default it renders a semantic separator (`<hr>`) that is exposed to assistive technology.
8+
- For purely visual glyph separators (like `/` in breadcrumbs), use `@decorative={{true}}` to apply `aria-hidden="true"`.
9+
10+
<div class="featured-demo">
11+
12+
```gjs live preview
13+
import { Separator } from "ember-primitives";
14+
15+
<template>
16+
<nav aria-label="First Demo">
17+
<ol class="breadcrumb-list">
18+
<li><a href="/">Home</a></li>
19+
<Separator @as="li" @decorative={{true}}>/</Separator>
20+
<li><a href="/docs">Docs</a></li>
21+
<Separator @as="li" @decorative={{true}}>/</Separator>
22+
<li aria-current="page">Separator</li>
23+
</ol>
24+
</nav>
25+
26+
<style>
27+
@scope {
28+
nav {
29+
user-select: none;
30+
background: var(--color-page-background);
31+
border-radius: 0.25rem;
32+
filter: drop-shadow(0 0 0.75rem rgba(0, 0, 0, 0.2));
33+
padding: 0.25rem 1rem;
34+
width: min-content;
35+
36+
ol { list-style-type: none; }
37+
}
38+
39+
.breadcrumb-list {
40+
list-style: none;
41+
display: flex;
42+
align-items: center;
43+
gap: 0.5rem;
44+
padding: 0;
45+
margin: 0;
46+
}
47+
48+
a {
49+
color: #0066cc;
50+
text-decoration: none;
51+
}
52+
53+
a:hover {
54+
text-decoration: underline;
55+
}
56+
57+
li[aria-current="page"] {
58+
color: #666;
59+
font-weight: 600;
60+
}
61+
62+
li[aria-hidden] {
63+
color: #999;
64+
user-select: none;
65+
}
66+
}
67+
</style>
68+
</template>
69+
```
70+
71+
</div>
72+
73+
## Install
74+
75+
```hbs live
76+
<SetupInstructions @src="components/separator.gts" />
77+
```
78+
79+
## What It Does
80+
81+
The `Separator` component is primarily a **documentation and readability tool**. It:
82+
83+
- Makes the code more readable by clearly marking separator elements
84+
- Renders a semantic separator (`<hr>`) by default
85+
- Provides an explicit `@decorative` mode for purely visual separators (adds `aria-hidden="true"`)
86+
- Provides a consistent pattern across your codebase
87+
- Allows customizing the element tag via the `@as` argument
88+
89+
This `Separator` is intended for **non-interactive** use-cases (semantic `<hr>` and decorative glyph separators). It does **not** implement focus/drag/keyboard behaviors for splitter-style UI.
90+
91+
## The `@as` Argument
92+
93+
By default, the Separator renders as an `<hr>` element.
94+
95+
When you are using `@decorative={{true}}` (for glyph separators like `/`), you typically want a non-void element so you can provide visible content. In lists, use `@as="li"` so separators are siblings to `<li>` elements:
96+
97+
```gjs
98+
<ol>
99+
<li>Item 1</li>
100+
<Separator @as="li" @decorative={{true}}>/</Separator>
101+
<li>Item 2</li>
102+
</ol>
103+
```
104+
105+
This is important because in HTML, `<ol>` and `<ul>` elements should only have `<li>` children. Using `<span>` elements as siblings to `<li>` elements is invalid HTML.
106+
107+
## Plain HTML Alternative
108+
109+
**Using plain HTML is just as easy!**
110+
111+
- For a semantic separator, use `<hr>`.
112+
- For a decorative glyph separator in breadcrumbs, use an element with `aria-hidden="true"`.
113+
114+
```gjs live preview
115+
<template>
116+
<nav aria-label="Demo with plain HTML">
117+
<ol class="breadcrumb-list">
118+
<li><a href="/">Home</a></li>
119+
<li aria-hidden="true">/</li>
120+
<li><a href="/docs">Docs</a></li>
121+
<li aria-hidden="true">/</li>
122+
<li aria-current="page">Plain HTML</li>
123+
</ol>
124+
</nav>
125+
126+
<style>
127+
@scope {
128+
nav {
129+
user-select: none;
130+
background: var(--color-page-background);
131+
border-radius: 0.25rem;
132+
filter: drop-shadow(0 0 0.75rem rgba(0, 0, 0, 0.2));
133+
padding: 0.25rem 1rem;
134+
width: min-content;
135+
136+
ol { list-style-type: none; }
137+
}
138+
139+
.breadcrumb-list {
140+
list-style: none;
141+
display: flex;
142+
align-items: center;
143+
gap: 0.5rem;
144+
padding: 0;
145+
margin: 0;
146+
}
147+
148+
a {
149+
color: #0066cc;
150+
text-decoration: none;
151+
}
152+
153+
a:hover {
154+
text-decoration: underline;
155+
}
156+
157+
li[aria-current="page"] {
158+
color: #666;
159+
font-weight: 600;
160+
}
161+
162+
li[aria-hidden] {
163+
color: #999;
164+
user-select: none;
165+
}
166+
}
167+
</style>
168+
</template>
169+
```
170+
171+
Both approaches are equally valid. Choose whichever feels more natural for your codebase!
172+
173+
## Anatomy
174+
175+
```gjs
176+
import { Separator } from "ember-primitives/components/separator";
177+
178+
<template>
179+
{{! Default: semantic separator (renders as <hr>) }}
180+
<Separator />
181+
182+
{{! Decorative glyph separator (renders as <span aria-hidden="true">) }}
183+
<Separator @as="span" @decorative={{true}}>/</Separator>
184+
185+
{{! Decorative glyph separator in lists (renders as <li aria-hidden="true">) }}
186+
<Separator @as="li" @decorative={{true}}>/</Separator>
187+
</template>
188+
```
189+
190+
## Example: In Breadcrumbs
191+
192+
When using with Breadcrumb, the yielded `b.Separator` is automatically configured with `@as="li"`:
193+
194+
```gjs live preview
195+
import { Breadcrumb } from "ember-primitives";
196+
197+
<template>
198+
<Breadcrumb class="not-prose" as |b|>
199+
<li><a href="/">Home</a></li>
200+
<b.Separator>/</b.Separator>
201+
<li><a href="/docs">Docs</a></li>
202+
<b.Separator>/</b.Separator>
203+
<li aria-current="page">Current</li>
204+
</Breadcrumb>
205+
206+
<style>
207+
@scope {
208+
nav {
209+
background: var(--color-page-background);
210+
padding: 0.25rem 1rem;
211+
ol { list-style-type: none; }
212+
}
213+
214+
nav ol {
215+
list-style: none;
216+
display: flex;
217+
align-items: center;
218+
gap: 0.5rem;
219+
padding: 0;
220+
margin: 0;
221+
}
222+
223+
nav a {
224+
color: #0066cc;
225+
text-decoration: none;
226+
}
227+
228+
nav a:hover {
229+
text-decoration: underline;
230+
}
231+
232+
nav li[aria-current="page"] {
233+
color: #666;
234+
font-weight: 600;
235+
}
236+
237+
nav li[aria-hidden] {
238+
color: #999;
239+
}
240+
}
241+
</style>
242+
</template>
243+
```
244+
245+
## Example: Custom Separators
246+
247+
You can use any content as a separator, including icons or symbols:
248+
249+
```gjs live preview
250+
import { Separator } from "ember-primitives";
251+
252+
<template>
253+
<nav aria-label="Custom Separator component usage">
254+
<ol class="breadcrumb-list">
255+
<li><a href="/">Home</a></li>
256+
<Separator @as="li" @decorative={{true}}>&gt;</Separator>
257+
<li><a href="/products">Products</a></li>
258+
<Separator @as="li" @decorative={{true}}>→</Separator>
259+
<li aria-current="page">Details</li>
260+
</ol>
261+
</nav>
262+
263+
<style>
264+
@scope {
265+
nav {
266+
user-select: none;
267+
background: var(--color-page-background);
268+
border-radius: 0.25rem;
269+
padding: 0.25rem 1rem;
270+
width: min-content;
271+
}
272+
273+
.breadcrumb-list {
274+
list-style: none;
275+
display: flex;
276+
align-items: center;
277+
gap: 0.5rem;
278+
padding: 0;
279+
margin: 0;
280+
}
281+
282+
a {
283+
color: #0066cc;
284+
text-decoration: none;
285+
}
286+
287+
a:hover {
288+
text-decoration: underline;
289+
}
290+
291+
li[aria-current="page"] {
292+
color: #666;
293+
}
294+
295+
li[aria-hidden] {
296+
color: #999;
297+
}
298+
}
299+
</style>
300+
</template>
301+
```
302+
303+
## API Reference
304+
305+
```gjs live no-shadow
306+
import { ComponentSignature } from "kolay";
307+
308+
<template>
309+
<ComponentSignature
310+
@package="ember-primitives"
311+
@module="declarations/components/separator"
312+
@name="Signature"
313+
/>
314+
</template>
315+
```
316+
317+
## Accessibility
318+
319+
When used as a semantic separator (`<Separator />`), the separator is exposed to assistive technology.
320+
321+
When used as a decorative glyph separator (`@decorative={{true}}`), the component adds `aria-hidden="true"` so screen reader users don't hear unnecessary characters like "/" or ">".
322+
323+
### Best Practices
324+
325+
- Use `<Separator />` (semantic) when the separation itself is meaningful structure.
326+
- Use `@decorative={{true}}` only for visual separators.
327+
- When decorative, the content is hidden from screen readers, so don't use it for meaningful content.
328+
- Ensure sufficient color contrast between separators and background.

0 commit comments

Comments
 (0)