Skip to content

Commit

Permalink
feat(component): create link component (#502)
Browse files Browse the repository at this point in the history
  • Loading branch information
tassioFront authored Jul 30, 2024
1 parent 31b6788 commit 356fe0c
Show file tree
Hide file tree
Showing 14 changed files with 473 additions and 8 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion packages/core/loader/cdn.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
module.exports = require('../dist/cjs/loader.cjs.js');

module.exports = require('../dist/cjs/loader.cjs.js');
module.exports.applyPolyfills = function() { return Promise.resolve() };
4 changes: 3 additions & 1 deletion packages/core/loader/index.cjs.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
module.exports = require('../dist/cjs/loader.cjs.js');

module.exports = require('../dist/cjs/loader.cjs.js');
module.exports.applyPolyfills = function() { return Promise.resolve() };
3 changes: 0 additions & 3 deletions packages/core/loader/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ export interface CustomElementsDefineOptions {
rel?: (el: EventTarget, eventName: string, listener: EventListenerOrEventListenerObject, options: boolean | AddEventListenerOptions) => void;
}
export declare function defineCustomElements(win?: Window, opts?: CustomElementsDefineOptions): void;
/**
* @deprecated
*/
export declare function applyPolyfills(): Promise<void>;

/**
Expand Down
4 changes: 3 additions & 1 deletion packages/core/loader/index.es2017.js
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from '../dist/esm/loader.js';

export * from '../dist/esm/polyfills/index.js';
export * from '../dist/esm/loader.js';
4 changes: 3 additions & 1 deletion packages/core/loader/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@

(function(){if("undefined"!==typeof window&&void 0!==window.Reflect&&void 0!==window.customElements){var a=HTMLElement;window.HTMLElement=function(){return Reflect.construct(a,[],this.constructor)};HTMLElement.prototype=a.prototype;HTMLElement.prototype.constructor=HTMLElement;Object.setPrototypeOf(HTMLElement,a)}})();
export * from '../dist/esm/loader.js';
export * from '../dist/esm/polyfills/index.js';
export * from '../dist/esm/loader.js';
33 changes: 33 additions & 0 deletions packages/core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ export namespace Components {
"type": TextFieldTypes;
"value"?: IonTypes.IonInput['value'];
}
interface AtomLink {
"color": 'primary' | 'secondary';
"type": 'anchor' | 'button';
}
interface AtomListSlider {
"centralized": boolean;
"hasNavigation": boolean;
Expand Down Expand Up @@ -247,6 +251,10 @@ export interface AtomInputCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLAtomInputElement;
}
export interface AtomLinkCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLAtomLinkElement;
}
export interface AtomListSliderCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLAtomListSliderElement;
Expand Down Expand Up @@ -380,6 +388,23 @@ declare global {
prototype: HTMLAtomInputElement;
new (): HTMLAtomInputElement;
};
interface HTMLAtomLinkElementEventMap {
"click": any;
}
interface HTMLAtomLinkElement extends Components.AtomLink, HTMLStencilElement {
addEventListener<K extends keyof HTMLAtomLinkElementEventMap>(type: K, listener: (this: HTMLAtomLinkElement, ev: AtomLinkCustomEvent<HTMLAtomLinkElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof HTMLAtomLinkElementEventMap>(type: K, listener: (this: HTMLAtomLinkElement, ev: AtomLinkCustomEvent<HTMLAtomLinkElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
var HTMLAtomLinkElement: {
prototype: HTMLAtomLinkElement;
new (): HTMLAtomLinkElement;
};
interface HTMLAtomListSliderElementEventMap {
"clickNext": any;
"clickPrev": any;
Expand Down Expand Up @@ -464,6 +489,7 @@ declare global {
"atom-grid": HTMLAtomGridElement;
"atom-icon": HTMLAtomIconElement;
"atom-input": HTMLAtomInputElement;
"atom-link": HTMLAtomLinkElement;
"atom-list-slider": HTMLAtomListSliderElement;
"atom-list-slider-item": HTMLAtomListSliderItemElement;
"atom-select": HTMLAtomSelectElement;
Expand Down Expand Up @@ -608,6 +634,11 @@ declare namespace LocalJSX {
"type"?: TextFieldTypes;
"value"?: IonTypes.IonInput['value'];
}
interface AtomLink {
"color"?: 'primary' | 'secondary';
"onClick"?: (event: AtomLinkCustomEvent<any>) => void;
"type"?: 'anchor' | 'button';
}
interface AtomListSlider {
"centralized"?: boolean;
"hasNavigation"?: boolean;
Expand Down Expand Up @@ -716,6 +747,7 @@ declare namespace LocalJSX {
"atom-grid": AtomGrid;
"atom-icon": AtomIcon;
"atom-input": AtomInput;
"atom-link": AtomLink;
"atom-list-slider": AtomListSlider;
"atom-list-slider-item": AtomListSliderItem;
"atom-select": AtomSelect;
Expand All @@ -739,6 +771,7 @@ declare module "@stencil/core" {
"atom-grid": LocalJSX.AtomGrid & JSXBase.HTMLAttributes<HTMLAtomGridElement>;
"atom-icon": LocalJSX.AtomIcon & JSXBase.HTMLAttributes<HTMLAtomIconElement>;
"atom-input": LocalJSX.AtomInput & JSXBase.HTMLAttributes<HTMLAtomInputElement>;
"atom-link": LocalJSX.AtomLink & JSXBase.HTMLAttributes<HTMLAtomLinkElement>;
"atom-list-slider": LocalJSX.AtomListSlider & JSXBase.HTMLAttributes<HTMLAtomListSliderElement>;
"atom-list-slider-item": LocalJSX.AtomListSliderItem & JSXBase.HTMLAttributes<HTMLAtomListSliderItemElement>;
"atom-select": LocalJSX.AtomSelect & JSXBase.HTMLAttributes<HTMLAtomSelectElement>;
Expand Down
31 changes: 31 additions & 0 deletions packages/core/src/components/link/link.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@import '~@atomium/scss-utils/index';

:host {
display: inline-block;
}

.atom-link {
align-items: center;
cursor: pointer;
display: flex;
font: var(--text-link-medium);
gap: var(--spacing-xxsmall);
letter-spacing: var(--text-link-medium-letter);
text-decoration: underline;

&[color='primary'] {
color: var(--color-brand-primary-regular);
}

&[color='secondary'] {
color: var(--color-brand-secondary-regular)
}

&__button {
background: none;
border: none;
padding: 0;
position: relative;
text-decoration: none;
}
}
74 changes: 74 additions & 0 deletions packages/core/src/components/link/link.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { newSpecPage } from '@stencil/core/testing'

import { AtomLink } from './link'

describe('atom-link', () => {
it('should render a span element with secondary color - default mode', async () => {
const page = await newSpecPage({
components: [AtomLink],
html: `<atom-link>styled link</atom-link>`,
})

await page.waitForChanges()

expect(page.root).toEqualHtml(`
<atom-link>
<mock:shadow-root>
<span class="atom-link" color="primary">
<slot></slot>
</span>
</mock:shadow-root>
styled link
</atom-link>
`)
})

it('should render a span element with secondary color', async () => {
const page = await newSpecPage({
components: [AtomLink],
html: `<atom-link color="secondary">styled link</atom-link>`,
})

await page.waitForChanges()

expect(page.root).toEqualHtml(`
<atom-link color="secondary">
<mock:shadow-root>
<span class="atom-link" color="secondary">
<slot></slot>
</span>
</mock:shadow-root>
styled link
</atom-link>
`)
})

it('should render a clickable button element with secondary color ', async () => {
const page = await newSpecPage({
components: [AtomLink],
html: `<atom-link color="secondary" type="button">styled link</atom-link>`,
})

await page.waitForChanges()

expect(page.root).toEqualHtml(`
<atom-link color="secondary" type="button">
<mock:shadow-root>
<button class="atom-link__button">
<span class="atom-link" color="secondary">
<slot></slot>
</span>
</button>
</mock:shadow-root>
styled link
</atom-link>
`)

const buttonEl = page.root?.shadowRoot?.querySelector('button')
const clickEventSpy = jest.spyOn(page.rootInstance.click, 'emit')

buttonEl?.click()

expect(clickEventSpy).toHaveBeenCalled()
})
})
41 changes: 41 additions & 0 deletions packages/core/src/components/link/link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Component, Event, EventEmitter, Host, Prop, h } from '@stencil/core'

@Component({
tag: 'atom-link',
styleUrl: 'link.scss',
shadow: true,
})
export class AtomLink {
@Prop() color: 'primary' | 'secondary' = 'primary'
@Prop() type: 'anchor' | 'button' = 'anchor'

@Event() click: EventEmitter

private handleClick = (event: MouseEvent) => {
event.preventDefault()
event.stopPropagation()

return this.click.emit(event)
}

render() {
return (
<Host>
{this.type === 'anchor' ? (
<span class='atom-link' color={this.color}>
<slot />
</span>
) : (
<button
class='atom-link__button'
onClick={this.handleClick.bind(this)}
>
<span class='atom-link' color={this.color}>
<slot />
</span>
</button>
)}
</Host>
)
}
}
47 changes: 47 additions & 0 deletions packages/core/src/components/link/stories/link.args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export const LinkStoryArgs = {
decorators: [],
parameters: {
actions: {
handles: [],
},
docs: {
description: {
component:
'atom-link components are link children styled components. They are used to navigate to different pages (when used inside router components, such as router-link(Vue) and Link(Next)) or used to trigger user actions.',
},
},
},
argTypes: {
color: {
control: 'select',
options: ['primary', 'secondary'],
defaultValue: { summary: 'primary' },
description: 'The link color.',
},
type: {
control: 'select',
options: ['anchor', 'button'],
defaultValue: { summary: 'anchor' },
description:
'The atom-link type. Use anchor for navigation (combined with router-link or Link) and button for user actions.',
},
},
}

const LinkReactStoryArgs = JSON.parse(JSON.stringify(LinkStoryArgs))

LinkReactStoryArgs.parameters.docs.description.component =
'atom-link components are link children styled components. They are used to navigate to different pages (when used inside Link(Next)) or used to trigger user actions.<br/><br/> OBS: Link (Next) component does not render a anchor tag by default, so you need to wrap it with a tag for semantic reasons. You can create a wrapper component on your project to do this.'

LinkReactStoryArgs.argTypes.type.description =
'The atom-link type. Use anchor for navigation (combined with Link) and button for user actions.'

const LinkVueStoryArgs = JSON.parse(JSON.stringify(LinkStoryArgs))

LinkVueStoryArgs.parameters.docs.description.component =
'atom-link components are link children styled components. They are used to navigate to different pages (when used inside router-link or NuxtLink or used to trigger user actions.'

LinkVueStoryArgs.argTypes.type.description =
'The atom-link type. Use anchor for navigation (combined with router-link or NuxtLink) and button for user actions.'

export { LinkReactStoryArgs, LinkVueStoryArgs }
65 changes: 65 additions & 0 deletions packages/core/src/components/link/stories/link.core.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Meta, StoryObj } from '@storybook/web-components'
import { html } from 'lit'

import { LinkStoryArgs } from './link.args'

export default {
title: 'Components/Link',
...LinkStoryArgs,
} as Meta

const createLink = (
args,
textExample = 'It should be used inside router components'
) => {
return html`
<a href="/nice-example">
<atom-link type="${args.type}" color="${args.color}">
${textExample}
</atom-link>
</a>
`
}

export const Primary: StoryObj = {
render: (args) => createLink(args),
args: {
type: 'anchor',
color: 'primary',
},
}

export const Secondary: StoryObj = {
render: (args) => createLink(args),
args: {
...Primary.args,
color: 'secondary',
},
}

export const WithIcon: StoryObj = {
render: (args) => html`
<a href="/nice-example">
<atom-link type="${args.type}" color="${args.color}">
<span> Nice example with icon </span>
<atom-icon icon="heart" />
</atom-link>
</a>
`,
args: {
...Primary.args,
color: 'secondary',
},
}

export const Button: StoryObj = {
render: (args) => html`
<atom-link type="${args.type}" color="${args.color}">
It is a button! and can be used to trigger user actions
</atom-link>
`,
args: {
...Primary.args,
type: 'button',
},
}
Loading

0 comments on commit 356fe0c

Please sign in to comment.