diff --git a/package-lock.json b/package-lock.json index 9582f9521..2c7b45c40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31622,7 +31622,7 @@ }, "packages/core": { "name": "@juntossomosmais/atomium", - "version": "2.7.0" + "version": "2.9.0" }, "packages/icons": { "name": "@juntossomosmais/atomium-icons" diff --git a/packages/core/loader/cdn.js b/packages/core/loader/cdn.js index 45e7d5abe..0eb97e625 100644 --- a/packages/core/loader/cdn.js +++ b/packages/core/loader/cdn.js @@ -1 +1,3 @@ -module.exports = require('../dist/cjs/loader.cjs.js'); \ No newline at end of file + +module.exports = require('../dist/cjs/loader.cjs.js'); +module.exports.applyPolyfills = function() { return Promise.resolve() }; diff --git a/packages/core/loader/index.cjs.js b/packages/core/loader/index.cjs.js index 45e7d5abe..0eb97e625 100644 --- a/packages/core/loader/index.cjs.js +++ b/packages/core/loader/index.cjs.js @@ -1 +1,3 @@ -module.exports = require('../dist/cjs/loader.cjs.js'); \ No newline at end of file + +module.exports = require('../dist/cjs/loader.cjs.js'); +module.exports.applyPolyfills = function() { return Promise.resolve() }; diff --git a/packages/core/loader/index.d.ts b/packages/core/loader/index.d.ts index 2d2403438..eed17ccf3 100644 --- a/packages/core/loader/index.d.ts +++ b/packages/core/loader/index.d.ts @@ -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; /** diff --git a/packages/core/loader/index.es2017.js b/packages/core/loader/index.es2017.js index 15b1d13d5..596dbf69f 100644 --- a/packages/core/loader/index.es2017.js +++ b/packages/core/loader/index.es2017.js @@ -1 +1,3 @@ -export * from '../dist/esm/loader.js'; \ No newline at end of file + +export * from '../dist/esm/polyfills/index.js'; +export * from '../dist/esm/loader.js'; diff --git a/packages/core/loader/index.js b/packages/core/loader/index.js index 8e1a393bc..170d5f893 100644 --- a/packages/core/loader/index.js +++ b/packages/core/loader/index.js @@ -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'; \ No newline at end of file +export * from '../dist/esm/polyfills/index.js'; +export * from '../dist/esm/loader.js'; diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index 8fefe2ced..4207eed29 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -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; @@ -247,6 +251,10 @@ export interface AtomInputCustomEvent extends CustomEvent { detail: T; target: HTMLAtomInputElement; } +export interface AtomLinkCustomEvent extends CustomEvent { + detail: T; + target: HTMLAtomLinkElement; +} export interface AtomListSliderCustomEvent extends CustomEvent { detail: T; target: HTMLAtomListSliderElement; @@ -380,6 +388,23 @@ declare global { prototype: HTMLAtomInputElement; new (): HTMLAtomInputElement; }; + interface HTMLAtomLinkElementEventMap { + "click": any; + } + interface HTMLAtomLinkElement extends Components.AtomLink, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLAtomLinkElement, ev: AtomLinkCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLAtomLinkElement, ev: AtomLinkCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(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; @@ -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; @@ -608,6 +634,11 @@ declare namespace LocalJSX { "type"?: TextFieldTypes; "value"?: IonTypes.IonInput['value']; } + interface AtomLink { + "color"?: 'primary' | 'secondary'; + "onClick"?: (event: AtomLinkCustomEvent) => void; + "type"?: 'anchor' | 'button'; + } interface AtomListSlider { "centralized"?: boolean; "hasNavigation"?: boolean; @@ -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; @@ -739,6 +771,7 @@ declare module "@stencil/core" { "atom-grid": LocalJSX.AtomGrid & JSXBase.HTMLAttributes; "atom-icon": LocalJSX.AtomIcon & JSXBase.HTMLAttributes; "atom-input": LocalJSX.AtomInput & JSXBase.HTMLAttributes; + "atom-link": LocalJSX.AtomLink & JSXBase.HTMLAttributes; "atom-list-slider": LocalJSX.AtomListSlider & JSXBase.HTMLAttributes; "atom-list-slider-item": LocalJSX.AtomListSliderItem & JSXBase.HTMLAttributes; "atom-select": LocalJSX.AtomSelect & JSXBase.HTMLAttributes; diff --git a/packages/core/src/components/link/link.scss b/packages/core/src/components/link/link.scss new file mode 100644 index 000000000..92ac73b1a --- /dev/null +++ b/packages/core/src/components/link/link.scss @@ -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; + } +} diff --git a/packages/core/src/components/link/link.spec.ts b/packages/core/src/components/link/link.spec.ts new file mode 100644 index 000000000..14d9a81b2 --- /dev/null +++ b/packages/core/src/components/link/link.spec.ts @@ -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: `styled link`, + }) + + await page.waitForChanges() + + expect(page.root).toEqualHtml(` + + + + + + + styled link + + `) + }) + + it('should render a span element with secondary color', async () => { + const page = await newSpecPage({ + components: [AtomLink], + html: `styled link`, + }) + + await page.waitForChanges() + + expect(page.root).toEqualHtml(` + + + + + + + styled link + + `) + }) + + it('should render a clickable button element with secondary color ', async () => { + const page = await newSpecPage({ + components: [AtomLink], + html: `styled link`, + }) + + await page.waitForChanges() + + expect(page.root).toEqualHtml(` + + + + + styled link + + `) + + const buttonEl = page.root?.shadowRoot?.querySelector('button') + const clickEventSpy = jest.spyOn(page.rootInstance.click, 'emit') + + buttonEl?.click() + + expect(clickEventSpy).toHaveBeenCalled() + }) +}) diff --git a/packages/core/src/components/link/link.tsx b/packages/core/src/components/link/link.tsx new file mode 100644 index 000000000..b285358c1 --- /dev/null +++ b/packages/core/src/components/link/link.tsx @@ -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 ( + + {this.type === 'anchor' ? ( + + + + ) : ( + + )} + + ) + } +} diff --git a/packages/core/src/components/link/stories/link.args.ts b/packages/core/src/components/link/stories/link.args.ts new file mode 100644 index 000000000..6599aa572 --- /dev/null +++ b/packages/core/src/components/link/stories/link.args.ts @@ -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.

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 } diff --git a/packages/core/src/components/link/stories/link.core.stories.tsx b/packages/core/src/components/link/stories/link.core.stories.tsx new file mode 100644 index 000000000..9402d2fff --- /dev/null +++ b/packages/core/src/components/link/stories/link.core.stories.tsx @@ -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` + + + ${textExample} + + + ` +} + +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` + + + Nice example with icon + + + + `, + args: { + ...Primary.args, + color: 'secondary', + }, +} + +export const Button: StoryObj = { + render: (args) => html` + + It is a button! and can be used to trigger user actions + + `, + args: { + ...Primary.args, + type: 'button', + }, +} diff --git a/packages/core/src/components/link/stories/link.react.stories.tsx b/packages/core/src/components/link/stories/link.react.stories.tsx new file mode 100644 index 000000000..38b541c9c --- /dev/null +++ b/packages/core/src/components/link/stories/link.react.stories.tsx @@ -0,0 +1,71 @@ +import { AtomIcon, AtomLink } from '@juntossomosmais/atomium/react' +import { Meta, StoryObj } from '@storybook/react' + +import { LinkReactStoryArgs } from './link.args' + +const Link = ({ children }) => { + return <>{children} +} + +export default { + title: 'Components/Link', + component: AtomLink, + ...LinkReactStoryArgs, +} as Meta + +const createLink = ( + args, + textExample = 'It should be used inside Link (Next) component' +) => ( + + + + {textExample} + + + +) + +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) => ( + + + + Nice example with icon + + + + + ), + args: { + ...Primary.args, + }, +} + +export const Button: StoryObj = { + render: (args) => ( + + It is a button! and can be used to trigger user actions + + ), + args: { + ...Primary.args, + type: 'button', + }, +} diff --git a/packages/core/src/components/link/stories/link.vue.stories.tsx b/packages/core/src/components/link/stories/link.vue.stories.tsx new file mode 100644 index 000000000..970c37092 --- /dev/null +++ b/packages/core/src/components/link/stories/link.vue.stories.tsx @@ -0,0 +1,98 @@ +import { AtomIcon, AtomLink } from '@juntossomosmais/atomium/vue' +import { Meta, StoryObj } from '@storybook/vue3' +import { defineComponent, h } from 'vue' + +import { LinkVueStoryArgs } from './link.args' + +const RouterLink = defineComponent({ + name: 'RouterLink', + props: { + to: { + type: String, + required: false, + }, + }, + setup(props, { slots }) { + return () => h('div', slots.default()) + }, +}) + +const createLink = ( + args, + textExample = 'It should be used inside router components' +) => ({ + components: { AtomLink, RouterLink }, + setup() { + return { args } + }, + template: ` + + + ${textExample} + + + `, +}) + +export default { + title: 'Components/Link', + component: AtomLink, + ...LinkVueStoryArgs, +} as Meta + +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) => ({ + components: { AtomLink, AtomIcon }, + setup() { + return { args } + }, + template: ` + + + Nice example with icon + + + + `, + }), + args: { + ...Primary.args, + }, +} + +export const Button: StoryObj = { + render: (args) => ({ + components: { AtomLink }, + setup() { + return { args } + }, + template: ` + + It is a button! and can be used to trigger user actions + + `, + }), + args: { + ...Primary.args, + type: 'button', + }, +}