@brad-frost-web/eddie-recipes 0.19.0 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/package.json +2 -2
  2. package/recipes/bradfrost-com/timeline/timeline.scss +24 -0
  3. package/recipes/bradfrost-com/timeline/timeline.ts +35 -0
  4. package/recipes/bradfrost-com/timeline-node/timeline-node.scss +92 -0
  5. package/recipes/bradfrost-com/timeline-node/timeline-node.ts +84 -0
  6. package/recipes/we-are-here/chunky-checkbox/chunky-checkbox.scss +68 -0
  7. package/recipes/we-are-here/chunky-checkbox/chunky-checkbox.stories.ts +55 -0
  8. package/recipes/we-are-here/chunky-checkbox/chunky-checkbox.ts +88 -0
  9. package/recipes/we-are-here/chunky-checkbox/test/chunky-checkbox.test.ts +5 -0
  10. package/recipes/we-are-here/wah-link-list/test/wah-link-list.test.ts +5 -0
  11. package/recipes/we-are-here/wah-link-list/wah-link-list.scss +56 -0
  12. package/recipes/we-are-here/wah-link-list/wah-link-list.stories.ts +50 -0
  13. package/recipes/we-are-here/wah-link-list/wah-link-list.ts +89 -0
  14. package/recipes/we-are-here/wah-logo/test/wah-logo.test.ts +5 -0
  15. package/recipes/we-are-here/wah-logo/wah-logo.scss +36 -0
  16. package/recipes/we-are-here/wah-logo/wah-logo.stories.ts +14 -0
  17. package/recipes/we-are-here/wah-logo/wah-logo.ts +62 -0
  18. package/recipes/we-are-here/wah-site-footer/test/wah-site-footer.test.ts +5 -0
  19. package/recipes/we-are-here/wah-site-footer/wah-site-footer.scss +33 -0
  20. package/recipes/we-are-here/wah-site-footer/wah-site-footer.stories.ts +10 -0
  21. package/recipes/we-are-here/wah-site-footer/wah-site-footer.ts +68 -0
  22. package/recipes/we-are-here/wah-site-header/test/wah-site-header.test.ts +5 -0
  23. package/recipes/we-are-here/wah-site-header/wah-site-header.scss +137 -0
  24. package/recipes/we-are-here/wah-site-header/wah-site-header.stories.ts +13 -0
  25. package/recipes/we-are-here/wah-site-header/wah-site-header.ts +132 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brad-frost-web/eddie-recipes",
3
- "version": "0.19.0",
3
+ "version": "0.19.2",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "peerDependencies": {
21
21
  "@brad-frost-web/eddie-web-components": "^0.19.0",
22
- "@brad-frost-web/eddie-design-tokens": "^0.18.0"
22
+ "@brad-frost-web/eddie-design-tokens": "^0.19.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "del-cli": "^7.0.0",
@@ -0,0 +1,24 @@
1
+ @use '@brad-frost-web/eddie-design-tokens/core/scss/component.scss' as *;
2
+ /*------------------------------------*\
3
+ #TIMELINE
4
+ \*------------------------------------*/
5
+
6
+ /**
7
+ * Timeline
8
+ * 1) A vertical timeline that displays a series of nodes along a vertical line
9
+ * 2) The line runs down the left side with nodes branching to the right
10
+ */
11
+ .ed-r-c-timeline {
12
+ position: relative;
13
+ padding-inline-start: size(6);
14
+
15
+ &::before {
16
+ content: '';
17
+ position: absolute;
18
+ top: 0;
19
+ bottom: 0;
20
+ left: size(2);
21
+ width: 2px;
22
+ background: var(--ed-theme-color-border-default);
23
+ }
24
+ }
@@ -0,0 +1,35 @@
1
+ import { EdElement } from '@brad-frost-web/eddie-web-components/components/EdElement';
2
+ import { html, unsafeCSS } from 'lit';
3
+ import styles from './timeline.scss?inline';
4
+
5
+ /**
6
+ * A vertical timeline component that displays a series of timeline nodes
7
+ * along a vertical line. Used as a wrapper around `ed-r-timeline-node` elements.
8
+ *
9
+ * @slot - Timeline node elements (`ed-r-timeline-node`)
10
+ */
11
+ export class EdRTimeline extends EdElement {
12
+ static get styles() {
13
+ return unsafeCSS(styles);
14
+ }
15
+
16
+ render() {
17
+ const componentClassName = this.componentClassNames('ed-r-c-timeline', {});
18
+
19
+ return html`
20
+ <div class="${componentClassName}">
21
+ <slot></slot>
22
+ </div>
23
+ `;
24
+ }
25
+ }
26
+
27
+ if (customElements.get('ed-r-timeline') === undefined) {
28
+ customElements.define('ed-r-timeline', EdRTimeline);
29
+ }
30
+
31
+ declare global {
32
+ interface HTMLElementTagNameMap {
33
+ 'ed-r-timeline': EdRTimeline;
34
+ }
35
+ }
@@ -0,0 +1,92 @@
1
+ @use '@brad-frost-web/eddie-design-tokens/core/scss/component.scss' as *;
2
+ /*------------------------------------*\
3
+ #TIMELINE-NODE
4
+ \*------------------------------------*/
5
+
6
+ /**
7
+ * Timeline node
8
+ * 1) A single entry on the vertical timeline
9
+ * 2) Positioned relative so the marker can align with the timeline line
10
+ */
11
+ .ed-r-c-timeline-node {
12
+ position: relative;
13
+ padding-block-end: size(4);
14
+ }
15
+
16
+ /**
17
+ * Timeline node marker
18
+ * 1) The circular dot that sits on the timeline line
19
+ * 2) Absolutely positioned to align with the vertical line from the parent timeline
20
+ */
21
+ .ed-r-c-timeline-node__marker {
22
+ position: absolute;
23
+ top: size(0.5);
24
+ left: calc(size(-4) - 5px);
25
+ width: 12px;
26
+ height: 12px;
27
+ border-radius: 50%;
28
+ background: var(--ed-theme-color-background-brand);
29
+ border: 2px solid var(--ed-theme-color-background-default);
30
+ }
31
+
32
+ /**
33
+ * Timeline node marker highlight variant
34
+ * 1) Larger marker for important timeline events
35
+ */
36
+ .ed-r-c-timeline-node--highlight .ed-r-c-timeline-node__marker {
37
+ width: 16px;
38
+ height: 16px;
39
+ left: calc(size(-4) - 7px);
40
+ box-shadow: 0 0 0 3px var(--ed-theme-color-background-brand);
41
+ }
42
+
43
+ /**
44
+ * Timeline node body
45
+ * 1) The content area to the right of the marker
46
+ */
47
+ .ed-r-c-timeline-node__body {
48
+ display: flex;
49
+ flex-direction: column;
50
+ gap: size(0.5);
51
+ }
52
+
53
+ /**
54
+ * Timeline node date
55
+ * 1) The date label styled with meta typography
56
+ */
57
+ .ed-r-c-timeline-node__date {
58
+ @include ed-theme-typography-meta-default();
59
+ color: var(--ed-theme-color-content-subtle);
60
+ }
61
+
62
+ /**
63
+ * Timeline node heading
64
+ */
65
+ .ed-r-c-timeline-node__heading {
66
+ display: block;
67
+ }
68
+
69
+ /**
70
+ * Timeline node link
71
+ * 1) Inherits heading color, underlines on hover
72
+ */
73
+ .ed-r-c-timeline-node__link {
74
+ color: inherit;
75
+ text-decoration: none;
76
+
77
+ &:hover,
78
+ &:focus {
79
+ text-decoration: underline;
80
+ color: var(--ed-theme-link-color-content-hover);
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Timeline node content
86
+ * 1) Slotted content area below the heading
87
+ */
88
+ .ed-r-c-timeline-node__content {
89
+ &:empty {
90
+ display: none;
91
+ }
92
+ }
@@ -0,0 +1,84 @@
1
+ import { EdElement } from '@brad-frost-web/eddie-web-components/components/EdElement';
2
+ import '@brad-frost-web/eddie-web-components/components/heading/heading';
3
+ import { html, nothing, unsafeCSS } from 'lit';
4
+ import { property } from 'lit/decorators.js';
5
+ import styles from './timeline-node.scss?inline';
6
+
7
+ /**
8
+ * A single node on a vertical timeline. Displays a dot marker on the timeline
9
+ * line with a heading, optional date, and slotted content to the right.
10
+ *
11
+ * @slot - Additional content for this timeline entry
12
+ */
13
+ export class EdRTimelineNode extends EdElement {
14
+ static get styles() {
15
+ return unsafeCSS(styles);
16
+ }
17
+
18
+ /**
19
+ * The heading text for this timeline node
20
+ */
21
+ @property()
22
+ text = '';
23
+
24
+ /**
25
+ * Optional date or time label displayed above/beside the heading
26
+ */
27
+ @property()
28
+ date?: string;
29
+
30
+ /**
31
+ * URL the heading links to
32
+ */
33
+ @property()
34
+ href?: string;
35
+
36
+ /**
37
+ * Heading tag name for semantic heading level
38
+ */
39
+ @property()
40
+ headingTagName: 'h2' | 'h3' | 'h4' | 'h5' | 'h6' = 'h3';
41
+
42
+ /**
43
+ * Visual variant of the node marker
44
+ */
45
+ @property()
46
+ variant?: 'default' | 'highlight';
47
+
48
+ render() {
49
+ const componentClassName = this.componentClassNames('ed-r-c-timeline-node', {
50
+ 'ed-r-c-timeline-node--highlight': this.variant === 'highlight'
51
+ });
52
+
53
+ return html`
54
+ <div class="${componentClassName}">
55
+ <div class="ed-r-c-timeline-node__marker"></div>
56
+ <div class="ed-r-c-timeline-node__body">
57
+ ${this.date
58
+ ? html`<span class="ed-r-c-timeline-node__date">${this.date}</span>`
59
+ : nothing}
60
+ ${this.text
61
+ ? html`<ed-heading class="ed-r-c-timeline-node__heading" tagName="${this.headingTagName}" variant="headline-sm">
62
+ ${this.href
63
+ ? html`<a class="ed-r-c-timeline-node__link" href="${this.href}">${this.text}</a>`
64
+ : this.text}
65
+ </ed-heading>`
66
+ : nothing}
67
+ <div class="ed-r-c-timeline-node__content">
68
+ <slot></slot>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ `;
73
+ }
74
+ }
75
+
76
+ if (customElements.get('ed-r-timeline-node') === undefined) {
77
+ customElements.define('ed-r-timeline-node', EdRTimelineNode);
78
+ }
79
+
80
+ declare global {
81
+ interface HTMLElementTagNameMap {
82
+ 'ed-r-timeline-node': EdRTimelineNode;
83
+ }
84
+ }
@@ -0,0 +1,68 @@
1
+ @use '@brad-frost-web/eddie-design-tokens/core/scss/component.scss' as *;
2
+ /*------------------------------------*\
3
+ #CHUNKY-CHECKBOX
4
+ \*------------------------------------*/
5
+
6
+ :host {
7
+ display: block;
8
+ }
9
+
10
+ /**
11
+ * Chunky checkbox card
12
+ * 1) Cursor pointer to indicate interactivity
13
+ * 2) Transition border color for checked state feedback
14
+ */
15
+ .ed-r-c-chunky-checkbox {
16
+ display: flex;
17
+ align-items: flex-start;
18
+ gap: size(2);
19
+ padding: size(2.5);
20
+ border: var(--ed-border-width-md) solid var(--ed-theme-color-border-subtle);
21
+ border-radius: var(--ed-border-radius-md);
22
+ cursor: pointer; /* 1 */
23
+ transition: border-color 0.15s ease, background-color 0.15s ease; /* 2 */
24
+ background-color: var(--ed-theme-color-background-default);
25
+
26
+ &:hover {
27
+ border-color: var(--ed-theme-color-border-default);
28
+ background-color: var(--ed-theme-color-background-subtle);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Checked state
34
+ */
35
+ .ed-r-c-chunky-checkbox--checked {
36
+ border-color: var(--ed-theme-color-border-brand);
37
+ background-color: var(--ed-theme-color-background-subtle);
38
+ }
39
+
40
+ /**
41
+ * Native checkbox input
42
+ * 1) Visually present but styled minimally; browser default checkbox appearance
43
+ * 2) Flex-shrink to prevent squishing
44
+ */
45
+ .ed-r-c-chunky-checkbox__input {
46
+ flex-shrink: 0; /* 2 */
47
+ margin-block-start: size(0.5);
48
+ width: size(2.5);
49
+ height: size(2.5);
50
+ accent-color: var(--ed-theme-color-content-brand);
51
+ }
52
+
53
+ .ed-r-c-chunky-checkbox__body {
54
+ flex: 1;
55
+ }
56
+
57
+ .ed-r-c-chunky-checkbox__heading {
58
+ margin: 0;
59
+ font-size: var(--ed-font-size-lg);
60
+ font-weight: var(--ed-font-weight-bold);
61
+ color: var(--ed-theme-color-content-default);
62
+ }
63
+
64
+ .ed-r-c-chunky-checkbox__description {
65
+ margin-block-start: size(0.5);
66
+ font-size: var(--ed-font-size-sm);
67
+ color: var(--ed-theme-color-content-subtle);
68
+ }
@@ -0,0 +1,55 @@
1
+ import { html } from 'lit';
2
+ import './chunky-checkbox';
3
+
4
+ export default {
5
+ title: 'Recipes/we are here./Chunky Checkbox',
6
+ component: 'ed-r-chunky-checkbox',
7
+ parameters: { status: { type: 'beta' } },
8
+ };
9
+
10
+ export const Default = () =>
11
+ html`<ed-r-chunky-checkbox
12
+ text="Connect"
13
+ description="Connect and hang out with cool people with shared interests."
14
+ name="purpose"
15
+ value="connect"
16
+ ></ed-r-chunky-checkbox>`;
17
+
18
+ export const Checked = () =>
19
+ html`<ed-r-chunky-checkbox
20
+ text="Be"
21
+ description="Find calm, slow down, and cultivate presence."
22
+ name="purpose"
23
+ value="be"
24
+ ?checked=${true}
25
+ ></ed-r-chunky-checkbox>`;
26
+
27
+ export const Group = () =>
28
+ html`
29
+ <ed-grid>
30
+ <ed-grid-item>
31
+ <ed-r-chunky-checkbox text="Be" description="Find calm, slow down, and cultivate presence." name="purpose" value="be"></ed-r-chunky-checkbox>
32
+ </ed-grid-item>
33
+ <ed-grid-item>
34
+ <ed-r-chunky-checkbox text="Connect" description="Connect and hang out with cool people with shared interests." name="purpose" value="connect"></ed-r-chunky-checkbox>
35
+ </ed-grid-item>
36
+ <ed-grid-item>
37
+ <ed-r-chunky-checkbox text="Explore" description="Explore your location, hobbies, expand your horizons." name="purpose" value="explore"></ed-r-chunky-checkbox>
38
+ </ed-grid-item>
39
+ <ed-grid-item>
40
+ <ed-r-chunky-checkbox text="Grow" description="Improve areas of your life with support and guidance." name="purpose" value="grow"></ed-r-chunky-checkbox>
41
+ </ed-grid-item>
42
+ <ed-grid-item>
43
+ <ed-r-chunky-checkbox text="Help" description="Help the people in your life, your community, and the world." name="purpose" value="help"></ed-r-chunky-checkbox>
44
+ </ed-grid-item>
45
+ <ed-grid-item>
46
+ <ed-r-chunky-checkbox text="Create" description="Unlock your creative potential and express yourself." name="purpose" value="create"></ed-r-chunky-checkbox>
47
+ </ed-grid-item>
48
+ <ed-grid-item>
49
+ <ed-r-chunky-checkbox text="Learn" description="Expand your horizons and learn new things." name="purpose" value="learn"></ed-r-chunky-checkbox>
50
+ </ed-grid-item>
51
+ <ed-grid-item>
52
+ <ed-r-chunky-checkbox text="I'm not sure!" description="Just checking things out for now, and will decide later." name="purpose" value="unsure"></ed-r-chunky-checkbox>
53
+ </ed-grid-item>
54
+ </ed-grid>
55
+ `;
@@ -0,0 +1,88 @@
1
+ import { EdElement } from '@brad-frost-web/eddie-web-components/components/EdElement';
2
+ import { html, unsafeCSS } from 'lit';
3
+ import { property } from 'lit/decorators.js';
4
+ import styles from './chunky-checkbox.scss?inline';
5
+
6
+ /**
7
+ * A large, card-style checkbox for multi-select interfaces (e.g. onboarding flows).
8
+ * Renders a native checkbox input inside a styled card with a heading and description.
9
+ */
10
+ export class EdRChunkyCheckbox extends EdElement {
11
+ static get styles() {
12
+ return unsafeCSS(styles);
13
+ }
14
+
15
+ /**
16
+ * The checkbox label / heading text.
17
+ */
18
+ @property()
19
+ text!: string;
20
+
21
+ /**
22
+ * Description text shown below the heading.
23
+ */
24
+ @property()
25
+ description?: string;
26
+
27
+ /**
28
+ * The checkbox input value attribute.
29
+ */
30
+ @property()
31
+ value?: string;
32
+
33
+ /**
34
+ * The name attribute for the checkbox input.
35
+ */
36
+ @property()
37
+ name?: string;
38
+
39
+ /**
40
+ * Whether the checkbox is checked.
41
+ */
42
+ @property({ type: Boolean })
43
+ checked = false;
44
+
45
+ private handleChange(e: Event) {
46
+ const input = e.target as HTMLInputElement;
47
+ this.checked = input.checked;
48
+ this.dispatch({
49
+ eventName: 'change',
50
+ detailObj: { checked: this.checked, value: this.value },
51
+ });
52
+ }
53
+
54
+ render() {
55
+ const componentClassName = this.componentClassNames('ed-r-c-chunky-checkbox', {
56
+ 'ed-r-c-chunky-checkbox--checked': this.checked,
57
+ });
58
+
59
+ return html`
60
+ <label class="${componentClassName}">
61
+ <input
62
+ type="checkbox"
63
+ class="ed-r-c-chunky-checkbox__input"
64
+ .checked="${this.checked}"
65
+ name="${this.name || ''}"
66
+ value="${this.value || ''}"
67
+ @change="${this.handleChange}"
68
+ />
69
+ <div class="ed-r-c-chunky-checkbox__body">
70
+ <h3 class="ed-r-c-chunky-checkbox__heading">${this.text}</h3>
71
+ ${this.description
72
+ ? html`<div class="ed-r-c-chunky-checkbox__description">${this.description}</div>`
73
+ : ''}
74
+ </div>
75
+ </label>
76
+ `;
77
+ }
78
+ }
79
+
80
+ if (customElements.get('ed-r-chunky-checkbox') === undefined) {
81
+ customElements.define('ed-r-chunky-checkbox', EdRChunkyCheckbox);
82
+ }
83
+
84
+ declare global {
85
+ interface HTMLElementTagNameMap {
86
+ 'ed-r-chunky-checkbox': EdRChunkyCheckbox;
87
+ }
88
+ }
@@ -0,0 +1,5 @@
1
+ import { expect, test } from 'vitest';
2
+
3
+ test('ed-r-chunky-checkbox renders', async () => {
4
+ expect(true).toBe(true);
5
+ });
@@ -0,0 +1,5 @@
1
+ import { expect, test } from 'vitest';
2
+
3
+ test('ed-r-wah-link-list renders', async () => {
4
+ expect(true).toBe(true);
5
+ });
@@ -0,0 +1,56 @@
1
+ @use '@brad-frost-web/eddie-design-tokens/core/scss/component.scss' as *;
2
+ /*------------------------------------*\
3
+ #WAH-LINK-LIST
4
+ \*------------------------------------*/
5
+
6
+ :host {
7
+ display: block;
8
+ }
9
+
10
+ .ed-r-c-wah-link-list__header {
11
+ display: flex;
12
+ align-items: baseline;
13
+ gap: size(2);
14
+ margin-block-end: size(1);
15
+ }
16
+
17
+ .ed-r-c-wah-link-list__heading {
18
+ margin: 0;
19
+ font-size: var(--ed-font-size-lg);
20
+ font-weight: var(--ed-font-weight-bold);
21
+ color: var(--ed-theme-color-content-default);
22
+ }
23
+
24
+ .ed-r-c-wah-link-list__heading-link {
25
+ font-size: var(--ed-font-size-sm);
26
+ color: var(--ed-theme-color-content-subtle);
27
+ }
28
+
29
+ .ed-r-c-wah-link-list__list {
30
+ display: flex;
31
+ flex-wrap: wrap;
32
+ gap: size(0.5) size(1.5);
33
+ list-style: none;
34
+ margin: 0;
35
+ padding: 0;
36
+ }
37
+
38
+ .ed-r-c-wah-link-list__link {
39
+ color: var(--ed-theme-color-content-subtle);
40
+ text-decoration: none;
41
+ font-size: var(--ed-font-size-sm);
42
+
43
+ &:hover {
44
+ color: var(--ed-theme-color-content-default);
45
+ text-decoration: underline;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Featured items
51
+ * 1) Bolder color and weight to stand out from normal items
52
+ */
53
+ .ed-r-c-wah-link-list__item--featured .ed-r-c-wah-link-list__link {
54
+ color: var(--ed-theme-color-content-default); /* 1 */
55
+ font-weight: var(--ed-font-weight-bold); /* 1 */
56
+ }
@@ -0,0 +1,50 @@
1
+ import { html } from 'lit';
2
+ import './wah-link-list';
3
+
4
+ export default {
5
+ title: 'Recipes/we are here./Wah Link List',
6
+ component: 'ed-r-wah-link-list',
7
+ parameters: { status: { type: 'beta' } },
8
+ };
9
+
10
+ export const Default = () =>
11
+ html`<ed-r-wah-link-list
12
+ heading="Music"
13
+ headingLinkText="Links to Last.fm, BandCamp, Music Services"
14
+ headingLinkHref="#"
15
+ .items=${[
16
+ { text: 'Rubblebucket', href: '#', featured: true },
17
+ { text: 'Led Zeppelin', href: '#', featured: true },
18
+ { text: 'CAKE', href: '#', featured: true },
19
+ { text: 'Outkast', href: '#', featured: true },
20
+ { text: 'Fela Kuti', href: '#' },
21
+ { text: 'Tune Yards', href: '#' },
22
+ { text: 'Dr. Dog', href: '#' },
23
+ { text: 'Beasie Boys', href: '#' },
24
+ { text: 'View All', href: '#' },
25
+ ]}
26
+ ></ed-r-wah-link-list>`;
27
+
28
+ export const Communities = () =>
29
+ html`<ed-r-wah-link-list
30
+ heading="My Communities"
31
+ .items=${[
32
+ { text: 'Brad Frost Web', href: '/communities', featured: true },
33
+ { text: 'Smashing Membership', href: '/communities' },
34
+ { text: 'Friends of beyond tellerrand', href: '/communities' },
35
+ { text: 'Design Better', href: '/communities' },
36
+ { text: 'OpenUI', href: '/communities' },
37
+ ]}
38
+ ></ed-r-wah-link-list>`;
39
+
40
+ export const NoFeatured = () =>
41
+ html`<ed-r-wah-link-list
42
+ heading="Hobbies"
43
+ .items=${[
44
+ { text: 'Playing Drums', href: '#' },
45
+ { text: 'Playing Bass', href: '#' },
46
+ { text: 'Making Art', href: '#' },
47
+ { text: 'Painting', href: '#' },
48
+ { text: 'Woodworking', href: '#' },
49
+ ]}
50
+ ></ed-r-wah-link-list>`;
@@ -0,0 +1,89 @@
1
+ import { EdElement } from '@brad-frost-web/eddie-web-components/components/EdElement';
2
+ import { html, unsafeCSS, nothing } from 'lit';
3
+ import { property } from 'lit/decorators.js';
4
+ import styles from './wah-link-list.scss?inline';
5
+
6
+ interface WahLinkItem {
7
+ text: string;
8
+ href: string;
9
+ featured?: boolean;
10
+ }
11
+
12
+ /**
13
+ * A link list with optional "featured" items rendered with visual emphasis.
14
+ * Used extensively in topic profiles for lists of interests, media, communities, etc.
15
+ *
16
+ * @slot - Optional slotted content rendered after the generated list
17
+ */
18
+ export class EdRWahLinkList extends EdElement {
19
+ static get styles() {
20
+ return unsafeCSS(styles);
21
+ }
22
+
23
+ /**
24
+ * Array of link items. Each item can optionally be marked as `featured`.
25
+ */
26
+ @property({ type: Array })
27
+ items: WahLinkItem[] = [];
28
+
29
+ /**
30
+ * Optional heading text rendered above the list.
31
+ */
32
+ @property()
33
+ heading?: string;
34
+
35
+ /**
36
+ * Optional heading-level link (e.g. "Link to Last.fm").
37
+ */
38
+ @property()
39
+ headingLinkText?: string;
40
+
41
+ /**
42
+ * URL for the heading-level link.
43
+ */
44
+ @property()
45
+ headingLinkHref?: string;
46
+
47
+ render() {
48
+ const componentClassName = this.componentClassNames('ed-r-c-wah-link-list', {});
49
+
50
+ return html`
51
+ <div class="${componentClassName}">
52
+ ${this.heading
53
+ ? html`
54
+ <div class="ed-r-c-wah-link-list__header">
55
+ <h3 class="ed-r-c-wah-link-list__heading">${this.heading}</h3>
56
+ ${this.headingLinkText && this.headingLinkHref
57
+ ? html`<a href="${this.headingLinkHref}" class="ed-r-c-wah-link-list__heading-link">${this.headingLinkText}</a>`
58
+ : nothing}
59
+ </div>
60
+ `
61
+ : nothing}
62
+ ${this.items.length > 0
63
+ ? html`
64
+ <ul class="ed-r-c-wah-link-list__list">
65
+ ${this.items.map(
66
+ (item) => html`
67
+ <li class="ed-r-c-wah-link-list__item ${item.featured ? 'ed-r-c-wah-link-list__item--featured' : ''}">
68
+ <a href="${item.href}" class="ed-r-c-wah-link-list__link">${item.text}</a>
69
+ </li>
70
+ `
71
+ )}
72
+ </ul>
73
+ `
74
+ : nothing}
75
+ <slot></slot>
76
+ </div>
77
+ `;
78
+ }
79
+ }
80
+
81
+ if (customElements.get('ed-r-wah-link-list') === undefined) {
82
+ customElements.define('ed-r-wah-link-list', EdRWahLinkList);
83
+ }
84
+
85
+ declare global {
86
+ interface HTMLElementTagNameMap {
87
+ 'ed-r-wah-link-list': EdRWahLinkList;
88
+ }
89
+ }
@@ -0,0 +1,5 @@
1
+ import { expect, test } from 'vitest';
2
+
3
+ test('ed-r-wah-logo renders', async () => {
4
+ expect(true).toBe(true);
5
+ });
@@ -0,0 +1,36 @@
1
+ @use '@brad-frost-web/eddie-design-tokens/core/scss/component.scss' as *;
2
+ /*------------------------------------*\
3
+ #WAH-LOGO
4
+ \*------------------------------------*/
5
+
6
+ /**
7
+ * we are here. concentric-circle logo
8
+ */
9
+
10
+ :host {
11
+ display: inline-block;
12
+ }
13
+
14
+ .ed-r-c-wah-logo {
15
+ display: inline-flex;
16
+ align-items: center;
17
+ }
18
+
19
+ /**
20
+ * Logo link
21
+ * 1) Remove default link underline
22
+ * 2) Inherit dimensions from parent
23
+ */
24
+ .ed-r-c-wah-logo__link {
25
+ display: inline-flex;
26
+ text-decoration: none; /* 1 */
27
+ }
28
+
29
+ /**
30
+ * Logo SVG image
31
+ * 1) Size the logo mark; consumers can override via CSS custom property
32
+ */
33
+ .ed-r-c-wah-logo__image {
34
+ width: var(--ed-r-c-wah-logo-size, size(8)); /* 1 */
35
+ height: auto;
36
+ }
@@ -0,0 +1,14 @@
1
+ import { html } from 'lit';
2
+ import './wah-logo';
3
+
4
+ export default {
5
+ title: 'Recipes/we are here./Wah Logo',
6
+ component: 'ed-r-wah-logo',
7
+ parameters: { status: { type: 'beta' } },
8
+ };
9
+
10
+ export const Default = () => html`<ed-r-wah-logo></ed-r-wah-logo>`;
11
+
12
+ export const WithLink = () => html`<ed-r-wah-logo href="/dashboard"></ed-r-wah-logo>`;
13
+
14
+ export const CustomSize = () => html`<ed-r-wah-logo style="--ed-r-c-wah-logo-size: 12rem;"></ed-r-wah-logo>`;
@@ -0,0 +1,62 @@
1
+ import { EdElement } from '@brad-frost-web/eddie-web-components/components/EdElement';
2
+ import { html, unsafeCSS } from 'lit';
3
+ import { property } from 'lit/decorators.js';
4
+ import styles from './wah-logo.scss?inline';
5
+
6
+ /**
7
+ * we are here. logo — concentric circle SVG mark.
8
+ *
9
+ * @slot - Optional slotted content (e.g. wordmark text)
10
+ */
11
+ export class EdRWahLogo extends EdElement {
12
+ static get styles() {
13
+ return unsafeCSS(styles);
14
+ }
15
+
16
+ /**
17
+ * Optional link URL. When provided, the logo renders as an anchor.
18
+ */
19
+ @property()
20
+ href?: string;
21
+
22
+ /**
23
+ * Accessible title for the SVG.
24
+ */
25
+ @property()
26
+ logoTitle?: string = 'we are here.';
27
+
28
+ render() {
29
+ const componentClassName = this.componentClassNames('ed-r-c-wah-logo', {});
30
+
31
+ const svg = html`
32
+ <svg class="ed-r-c-wah-logo__image" fill="none" height="502" viewBox="0 0 502 502" width="502" xmlns="http://www.w3.org/2000/svg">
33
+ <title>${this.logoTitle}</title>
34
+ <circle cx="251.176" cy="250.62" fill="#be6700" r="250.62"></circle>
35
+ <circle cx="251.176" cy="250.62" fill="#fa8700" r="210.521"></circle>
36
+ <circle cx="251.176" cy="250.62" fill="#ffa438" r="170.422"></circle>
37
+ <circle cx="251.176" cy="250.62" fill="#ffc075" r="130.322"></circle>
38
+ <circle cx="251.176" cy="250.62" fill="#ffdcb3" r="90.2232"></circle>
39
+ <circle cx="251.176" cy="250.62" fill="#fff8f0" r="50.2232"></circle>
40
+ </svg>
41
+ `;
42
+
43
+ return html`
44
+ <div class="${componentClassName}">
45
+ ${this.href
46
+ ? html`<a href="${this.href}" class="ed-r-c-wah-logo__link">${svg}</a>`
47
+ : svg}
48
+ <slot></slot>
49
+ </div>
50
+ `;
51
+ }
52
+ }
53
+
54
+ if (customElements.get('ed-r-wah-logo') === undefined) {
55
+ customElements.define('ed-r-wah-logo', EdRWahLogo);
56
+ }
57
+
58
+ declare global {
59
+ interface HTMLElementTagNameMap {
60
+ 'ed-r-wah-logo': EdRWahLogo;
61
+ }
62
+ }
@@ -0,0 +1,5 @@
1
+ import { expect, test } from 'vitest';
2
+
3
+ test('ed-r-wah-site-footer renders', async () => {
4
+ expect(true).toBe(true);
5
+ });
@@ -0,0 +1,33 @@
1
+ @use '@brad-frost-web/eddie-design-tokens/core/scss/component.scss' as *;
2
+ /*------------------------------------*\
3
+ #WAH-SITE-FOOTER
4
+ \*------------------------------------*/
5
+
6
+ :host {
7
+ display: block;
8
+ }
9
+
10
+ .ed-r-c-wah-site-footer {
11
+ padding-block: size(4);
12
+ border-block-start: var(--ed-border-width-sm) solid var(--ed-theme-color-border-subtle);
13
+ }
14
+
15
+ .ed-r-c-wah-site-footer__nav-list {
16
+ display: flex;
17
+ flex-wrap: wrap;
18
+ gap: size(1) size(3);
19
+ list-style: none;
20
+ margin: 0;
21
+ padding: 0;
22
+ }
23
+
24
+ .ed-r-c-wah-site-footer__nav-link {
25
+ color: var(--ed-theme-color-content-subtle);
26
+ text-decoration: none;
27
+ font-size: var(--ed-font-size-sm);
28
+
29
+ &:hover {
30
+ color: var(--ed-theme-color-content-default);
31
+ text-decoration: underline;
32
+ }
33
+ }
@@ -0,0 +1,10 @@
1
+ import { html } from 'lit';
2
+ import './wah-site-footer';
3
+
4
+ export default {
5
+ title: 'Recipes/we are here./Wah Site Footer',
6
+ component: 'ed-r-wah-site-footer',
7
+ parameters: { status: { type: 'beta' } },
8
+ };
9
+
10
+ export const Default = () => html`<ed-r-wah-site-footer></ed-r-wah-site-footer>`;
@@ -0,0 +1,68 @@
1
+ import { EdElement } from '@brad-frost-web/eddie-web-components/components/EdElement';
2
+ import '@brad-frost-web/eddie-web-components/components/layout-container/layout-container';
3
+ import { html, unsafeCSS } from 'lit';
4
+ import { property } from 'lit/decorators.js';
5
+ import styles from './wah-site-footer.scss?inline';
6
+
7
+ interface WahFooterNavItem {
8
+ text: string;
9
+ href: string;
10
+ }
11
+
12
+ /**
13
+ * we are here. site footer with navigation links.
14
+ */
15
+ export class EdRWahSiteFooter extends EdElement {
16
+ static get styles() {
17
+ return unsafeCSS(styles);
18
+ }
19
+
20
+ /**
21
+ * Footer navigation items.
22
+ */
23
+ @property({ type: Array })
24
+ navItems: WahFooterNavItem[] = [
25
+ { text: 'Report an issue', href: '/contact' },
26
+ { text: 'Code of Conduct', href: '/code-of-conduct' },
27
+ { text: 'Privacy Policy', href: '/privacy-policy' },
28
+ { text: 'Terms of Service', href: '/terms-of-service' },
29
+ { text: 'About we are here.', href: '/team' },
30
+ { text: 'Mission & Values', href: '/mission-values' },
31
+ { text: 'Idea Jar', href: '/idea-jar' },
32
+ { text: 'Contact', href: '/contact' },
33
+ ];
34
+
35
+ render() {
36
+ const componentClassName = this.componentClassNames('ed-r-c-wah-site-footer', {});
37
+
38
+ return html`
39
+ <footer class="${componentClassName}" role="contentinfo">
40
+ <ed-layout-container>
41
+ <div class="ed-r-c-wah-site-footer__inner">
42
+ <nav class="ed-r-c-wah-site-footer__nav" aria-label="Footer">
43
+ <ul class="ed-r-c-wah-site-footer__nav-list">
44
+ ${this.navItems.map(
45
+ (item) => html`
46
+ <li class="ed-r-c-wah-site-footer__nav-item">
47
+ <a href="${item.href}" class="ed-r-c-wah-site-footer__nav-link">${item.text}</a>
48
+ </li>
49
+ `
50
+ )}
51
+ </ul>
52
+ </nav>
53
+ </div>
54
+ </ed-layout-container>
55
+ </footer>
56
+ `;
57
+ }
58
+ }
59
+
60
+ if (customElements.get('ed-r-wah-site-footer') === undefined) {
61
+ customElements.define('ed-r-wah-site-footer', EdRWahSiteFooter);
62
+ }
63
+
64
+ declare global {
65
+ interface HTMLElementTagNameMap {
66
+ 'ed-r-wah-site-footer': EdRWahSiteFooter;
67
+ }
68
+ }
@@ -0,0 +1,5 @@
1
+ import { expect, test } from 'vitest';
2
+
3
+ test('ed-r-wah-site-header renders', async () => {
4
+ expect(true).toBe(true);
5
+ });
@@ -0,0 +1,137 @@
1
+ @use '@brad-frost-web/eddie-design-tokens/core/scss/component.scss' as *;
2
+ /*------------------------------------*\
3
+ #WAH-SITE-HEADER
4
+ \*------------------------------------*/
5
+
6
+ :host {
7
+ display: block;
8
+ }
9
+
10
+ /**
11
+ * Header root
12
+ * 1) Sticky to top of viewport
13
+ */
14
+ .ed-r-c-wah-site-header {
15
+ position: sticky; /* 1 */
16
+ top: 0;
17
+ z-index: 10;
18
+ background-color: var(--ed-theme-color-background-default);
19
+ border-block-end: var(--ed-border-width-sm) solid var(--ed-theme-color-border-subtle);
20
+ }
21
+
22
+ .ed-r-c-wah-site-header__inner {
23
+ display: flex;
24
+ flex-wrap: wrap;
25
+ align-items: center;
26
+ padding-block: size(1.5);
27
+ gap: size(2);
28
+ }
29
+
30
+ .ed-r-c-wah-site-header__logo {
31
+ flex-shrink: 0;
32
+ }
33
+
34
+ /**
35
+ * Menu button
36
+ * 1) Only visible on small screens
37
+ */
38
+ .ed-r-c-wah-site-header__menu-button {
39
+ margin-inline-start: auto;
40
+
41
+ @media all and (min-width: $ed-bp-md) {
42
+ display: none; /* 1 */
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Primary nav
48
+ * 1) Hidden on mobile until menu is toggled open
49
+ * 2) Full width on small screens
50
+ */
51
+ .ed-r-c-wah-site-header__primary-nav {
52
+ display: none; /* 1 */
53
+ width: 100%; /* 2 */
54
+
55
+ .ed-r-c-wah-site-header.ed-is-active & {
56
+ display: block;
57
+ }
58
+
59
+ @media all and (min-width: $ed-bp-md) {
60
+ display: block;
61
+ width: auto;
62
+ margin-inline-start: auto;
63
+ }
64
+ }
65
+
66
+ .ed-r-c-wah-site-header__primary-nav-list {
67
+ display: flex;
68
+ flex-direction: column;
69
+ gap: size(0.5);
70
+ list-style: none;
71
+ margin: 0;
72
+ padding: 0;
73
+
74
+ @media all and (min-width: $ed-bp-md) {
75
+ flex-direction: row;
76
+ gap: size(2);
77
+ }
78
+ }
79
+
80
+ .ed-r-c-wah-site-header__primary-nav-link {
81
+ display: block;
82
+ padding: size(1) 0;
83
+ color: var(--ed-theme-color-content-default);
84
+ text-decoration: none;
85
+ font-weight: var(--ed-font-weight-normal);
86
+
87
+ &:hover {
88
+ color: var(--ed-theme-color-content-brand);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Active nav item modifier
94
+ */
95
+ .ed-r-c-wah-site-header__primary-nav-item--active .ed-r-c-wah-site-header__primary-nav-link {
96
+ font-weight: var(--ed-font-weight-bold);
97
+ color: var(--ed-theme-color-content-brand);
98
+ }
99
+
100
+ /**
101
+ * Utility nav
102
+ * 1) Hidden on mobile until menu is toggled open
103
+ */
104
+ .ed-r-c-wah-site-header__utility-nav {
105
+ display: none; /* 1 */
106
+ width: 100%;
107
+
108
+ .ed-r-c-wah-site-header.ed-is-active & {
109
+ display: block;
110
+ }
111
+
112
+ @media all and (min-width: $ed-bp-md) {
113
+ display: none;
114
+ }
115
+ }
116
+
117
+ .ed-r-c-wah-site-header__utility-nav-list {
118
+ display: flex;
119
+ flex-direction: column;
120
+ gap: size(0.5);
121
+ list-style: none;
122
+ margin: 0;
123
+ padding: size(2) 0 0;
124
+ border-block-start: var(--ed-border-width-sm) solid var(--ed-theme-color-border-subtle);
125
+ }
126
+
127
+ .ed-r-c-wah-site-header__utility-nav-link {
128
+ display: block;
129
+ padding: size(0.5) 0;
130
+ color: var(--ed-theme-color-content-subtle);
131
+ text-decoration: none;
132
+ font-size: var(--ed-font-size-sm);
133
+
134
+ &:hover {
135
+ color: var(--ed-theme-color-content-default);
136
+ }
137
+ }
@@ -0,0 +1,13 @@
1
+ import { html } from 'lit';
2
+ import './wah-site-header';
3
+
4
+ export default {
5
+ title: 'Recipes/we are here./Wah Site Header',
6
+ component: 'ed-r-wah-site-header',
7
+ parameters: { status: { type: 'beta' } },
8
+ };
9
+
10
+ export const Default = () => html`<ed-r-wah-site-header></ed-r-wah-site-header>`;
11
+
12
+ export const WithActiveItem = () =>
13
+ html`<ed-r-wah-site-header activeNavItem="Your Profile"></ed-r-wah-site-header>`;
@@ -0,0 +1,132 @@
1
+ import { EdElement } from '@brad-frost-web/eddie-web-components/components/EdElement';
2
+ import '@brad-frost-web/eddie-web-components/components/button/button';
3
+ import '@brad-frost-web/eddie-web-components/components/layout-container/layout-container';
4
+ import '../wah-logo/wah-logo';
5
+ import { html, unsafeCSS } from 'lit';
6
+ import { property, state } from 'lit/decorators.js';
7
+ import styles from './wah-site-header.scss?inline';
8
+
9
+ interface WahNavItem {
10
+ text: string;
11
+ href: string;
12
+ }
13
+
14
+ /**
15
+ * we are here. site header recipe composing the logo, primary navigation,
16
+ * utility navigation, and a mobile menu toggle.
17
+ */
18
+ export class EdRWahSiteHeader extends EdElement {
19
+ static get styles() {
20
+ return unsafeCSS(styles);
21
+ }
22
+
23
+ /**
24
+ * Primary nav items displayed in the main navigation.
25
+ */
26
+ @property({ type: Array })
27
+ primaryNavItems: WahNavItem[] = [
28
+ { text: 'Your Profile', href: '/profile' },
29
+ { text: 'Be', href: '/be' },
30
+ { text: 'Connect', href: '/connect' },
31
+ { text: 'Grow', href: '/grow' },
32
+ { text: 'Help', href: '/help' },
33
+ { text: 'Create', href: '/create' },
34
+ { text: 'Learn', href: '/learn' },
35
+ { text: 'Explore', href: '/explore' },
36
+ { text: 'Random', href: '#' },
37
+ ];
38
+
39
+ /**
40
+ * Utility nav items displayed below primary nav.
41
+ */
42
+ @property({ type: Array })
43
+ utilityNavItems: WahNavItem[] = [
44
+ { text: 'Report an issue', href: '/contact' },
45
+ { text: 'Code of Conduct', href: '/code-of-conduct' },
46
+ { text: 'Privacy Policy', href: '/privacy-policy' },
47
+ { text: 'Terms of Service', href: '/terms-of-service' },
48
+ { text: 'Our Mission & Values', href: '/mission-values' },
49
+ ];
50
+
51
+ /**
52
+ * URL for the logo link.
53
+ */
54
+ @property()
55
+ logoHref?: string = '/dashboard';
56
+
57
+ /**
58
+ * Highlighted primary nav item text (e.g. "Your Profile" gets a special style).
59
+ */
60
+ @property()
61
+ activeNavItem?: string;
62
+
63
+ /**
64
+ * Internal mobile menu state.
65
+ */
66
+ @state()
67
+ private isMenuOpen = false;
68
+
69
+ private toggleMenu() {
70
+ this.isMenuOpen = !this.isMenuOpen;
71
+ }
72
+
73
+ render() {
74
+ const componentClassName = this.componentClassNames('ed-r-c-wah-site-header', {
75
+ 'ed-is-active': this.isMenuOpen,
76
+ });
77
+
78
+ return html`
79
+ <header class="${componentClassName}" role="banner">
80
+ <ed-layout-container class="ed-r-c-wah-site-header__container">
81
+ <div class="ed-r-c-wah-site-header__inner">
82
+ <ed-r-wah-logo class="ed-r-c-wah-site-header__logo" href="${this.logoHref}"></ed-r-wah-logo>
83
+
84
+ <ed-button
85
+ class="ed-r-c-wah-site-header__menu-button"
86
+ variant="bare"
87
+ text="Menu"
88
+ iconName="menu"
89
+ iconPosition="before"
90
+ aria-expanded="${this.isMenuOpen}"
91
+ @click="${this.toggleMenu}"
92
+ ></ed-button>
93
+
94
+ <nav class="ed-r-c-wah-site-header__primary-nav" aria-label="Primary">
95
+ <ul class="ed-r-c-wah-site-header__primary-nav-list">
96
+ ${this.primaryNavItems.map(
97
+ (item) => html`
98
+ <li class="ed-r-c-wah-site-header__primary-nav-item ${item.text === this.activeNavItem ? 'ed-r-c-wah-site-header__primary-nav-item--active' : ''}">
99
+ <a href="${item.href}" class="ed-r-c-wah-site-header__primary-nav-link">${item.text}</a>
100
+ </li>
101
+ `
102
+ )}
103
+ </ul>
104
+ </nav>
105
+
106
+ <nav class="ed-r-c-wah-site-header__utility-nav" aria-label="Utility">
107
+ <ul class="ed-r-c-wah-site-header__utility-nav-list">
108
+ ${this.utilityNavItems.map(
109
+ (item) => html`
110
+ <li class="ed-r-c-wah-site-header__utility-nav-item">
111
+ <a href="${item.href}" class="ed-r-c-wah-site-header__utility-nav-link">${item.text}</a>
112
+ </li>
113
+ `
114
+ )}
115
+ </ul>
116
+ </nav>
117
+ </div>
118
+ </ed-layout-container>
119
+ </header>
120
+ `;
121
+ }
122
+ }
123
+
124
+ if (customElements.get('ed-r-wah-site-header') === undefined) {
125
+ customElements.define('ed-r-wah-site-header', EdRWahSiteHeader);
126
+ }
127
+
128
+ declare global {
129
+ interface HTMLElementTagNameMap {
130
+ 'ed-r-wah-site-header': EdRWahSiteHeader;
131
+ }
132
+ }