From 913e55c16764e3ea1391c55ab5ef8315a5d79e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Mendon=C3=A7a?= Date: Mon, 1 Jun 2026 15:18:04 -0300 Subject: [PATCH 1/4] feat(link): align component with Figma and add Code Connect Use data attributes for size/disabled styling, consolidate ghost hover classes in the template, and document the Figma source in the spec. Co-authored-by: Cursor --- .specs/link.md | 12 ++-- .../stories/webkit/navigation/Link.stories.js | 41 ++++++----- .../components/navigation/link/link.figma.ts | 24 +++++++ .../src/components/navigation/link/link.vue | 69 +++++-------------- 4 files changed, 74 insertions(+), 72 deletions(-) create mode 100644 packages/webkit/src/components/navigation/link/link.figma.ts diff --git a/.specs/link.md b/.specs/link.md index 64f67c5c7..8e88b7d30 100644 --- a/.specs/link.md +++ b/.specs/link.md @@ -4,15 +4,18 @@ category: navigation structure: monolithic status: implemented spec_version: 1 -checksum: 1de51434b1fd172a6c0de7cd7f1c86f291a0a264bf66629d28438e926e753665 +checksum: 42815455514e846bb1b8f6825c1ab7a00e53d155ae890edd2ffd48898ed9aa0c created: 2026-05-22 -last_updated: 2026-05-26 +last_updated: 2026-06-01 +figma: + url: https://www.figma.com/design/t97pXRs7xME3SJDs5iZ5RF/Webkit?node-id=3548-578 + node_id: 3548:578 --- # Link — Component Spec ## Purpose -Helps users move between views or sections. Migrated from the existing implementation at `packages/webkit/src/components/navigation/link/`. +Helps users move between views or sections. Migrated from the existing implementation at `packages/webkit/src/components/navigation/link/`. Design source: [Webkit — Link](https://www.figma.com/design/t97pXRs7xME3SJDs5iZ5RF/Webkit?node-id=3548-578) (`Size` × `State` variants; hover/active handled in CSS). ## Props @@ -76,8 +79,7 @@ Helps users move between views or sections. Migrated from the existing implement ## Stories (Storybook) - Default -- Medium (size) -- Large (size) +- Sizes - Disabled ## Constraints — DO NOT diff --git a/apps/storybook/src/stories/webkit/navigation/Link.stories.js b/apps/storybook/src/stories/webkit/navigation/Link.stories.js index a72a4b093..6b431489f 100644 --- a/apps/storybook/src/stories/webkit/navigation/Link.stories.js +++ b/apps/storybook/src/stories/webkit/navigation/Link.stories.js @@ -94,11 +94,20 @@ export default meta const Template = (args) => ({ components: { Link }, setup() { - const { onClick, ...props } = args - - return { props, onClick } + return { args } }, - template: '' + template: ` + + ` }) /** @type {import('@storybook/vue3').StoryObj} */ @@ -110,20 +119,18 @@ export const Default = { } /** @type {import('@storybook/vue3').StoryObj} */ -export const Medium = { - args: { size: 'medium' }, - render: Template, - parameters: { - docs: { description: { story: 'Medium size variant.' } } - } -} - -/** @type {import('@storybook/vue3').StoryObj} */ -export const Large = { - args: { size: 'large' }, - render: Template, +export const Sizes = { + render: () => ({ + components: { Link }, + template: ` +
+ + +
+ ` + }), parameters: { - docs: { description: { story: 'Large size variant.' } } + docs: { description: { story: 'All size variants side by side.' } } } } diff --git a/packages/webkit/src/components/navigation/link/link.figma.ts b/packages/webkit/src/components/navigation/link/link.figma.ts new file mode 100644 index 000000000..d5cdbeb3a --- /dev/null +++ b/packages/webkit/src/components/navigation/link/link.figma.ts @@ -0,0 +1,24 @@ +import figma, { html } from '@figma/code-connect/html' + +const FIGMA_NODE = 'https://www.figma.com/design/t97pXRs7xME3SJDs5iZ5RF/Webkit?node-id=3548-578' + +figma.connect(FIGMA_NODE, { + label: 'Link', + imports: ['import Link from "@aziontech/webkit/navigation/link"'], + props: { + label: figma.string('label'), + size: figma.enum('Size', { + Large: 'large', + Medium: 'medium' + }), + showIcon: figma.boolean('showIcon') + }, + example: ({ label, size, showIcon }) => html` + + ` +}) diff --git a/packages/webkit/src/components/navigation/link/link.vue b/packages/webkit/src/components/navigation/link/link.vue index 63d44e2b6..a8c93c331 100644 --- a/packages/webkit/src/components/navigation/link/link.vue +++ b/packages/webkit/src/components/navigation/link/link.vue @@ -42,53 +42,20 @@ const attrs = useAttrs() - const passthroughAttrs = computed(() => { - const rest = { ...attrs } - - delete rest.class - delete rest['data-testid'] - - return rest - }) - const testId = computed(() => (attrs['data-testid'] as string | undefined) ?? 'navigation-link') - const sharedClasses = [ + const rootClasses = computed(() => [ 'group relative inline-flex shrink-0 items-center whitespace-nowrap', - 'rounded-[var(--shape-button)] text-[var(--text-link)]', - 'transition-colors motion-reduce:transition-none', + 'min-h-10 h-10 rounded-[var(--shape-button)] text-[var(--text-link)]', + 'transition-colors duration-150 ease-out motion-reduce:transition-none', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring-color)]', - 'focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-canvas)]' - ] - - const ghostClasses = [ - 'pointer-events-none absolute top-1/2 -translate-y-1/2', - '-left-[var(--spacing-xs)] -right-[var(--spacing-xs)] h-8', - 'rounded-[var(--shape-button)] bg-[var(--bg-surface-raised)]', - 'opacity-0 transition-opacity motion-reduce:transition-none', - 'group-hover:opacity-100 group-focus-visible:opacity-100', - 'before:pointer-events-none before:absolute before:inset-0 before:rounded-[inherit]', - "before:opacity-0 before:transition-opacity before:content-['']", - 'group-hover:before:opacity-100 group-focus-visible:before:opacity-100', - 'before:bg-[var(--bg-hover)] group-active:before:bg-[var(--bg-active)]' - ] - - const disabledClasses = - 'pointer-events-none cursor-not-allowed !text-[var(--text-disabled)] [&_.link-ghost]:hidden' - - const sizeClasses: Record = { - large: 'min-h-10 h-10 text-button-lg', - medium: 'min-h-10 h-10 text-button-md' - } - - const contentClasses = 'inline-flex items-center gap-[var(--spacing-xs)]' - - const rootClasses = computed(() => { - const state = props.disabled ? disabledClasses : '' - const size = sizeClasses[props.size] - - return [...sharedClasses, size, state, attrs.class] - }) + 'focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-canvas)]', + 'data-[size=large]:text-button-lg', + 'data-[size=medium]:text-button-md', + 'data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed', + 'data-[disabled]:!text-[var(--text-disabled)] data-[disabled]:[&_.link-ghost]:hidden', + attrs['class'] + ]) const handleClick = (event: MouseEvent) => { if (props.disabled) { @@ -102,34 +69,36 @@