From 111a5f73cdfd37f283357ed3c830e95f065a6b7f Mon Sep 17 00:00:00 2001 From: David McKelvey Date: Sun, 24 Nov 2024 19:42:03 -0800 Subject: [PATCH 1/4] add pricing card --- .../interactive/cards/pricing-card/index.ts | 1 + .../pricing-card/pricing-card.stories.tsx | 144 ++++++++++++++++++ .../cards/pricing-card/pricing-card.tsx | 128 ++++++++++++++++ .../cards/pricing-card/pricing-card.types.ts | 14 ++ 4 files changed, 287 insertions(+) create mode 100644 components/interactive/cards/pricing-card/index.ts create mode 100644 components/interactive/cards/pricing-card/pricing-card.stories.tsx create mode 100644 components/interactive/cards/pricing-card/pricing-card.tsx create mode 100644 components/interactive/cards/pricing-card/pricing-card.types.ts diff --git a/components/interactive/cards/pricing-card/index.ts b/components/interactive/cards/pricing-card/index.ts new file mode 100644 index 0000000..02e57d1 --- /dev/null +++ b/components/interactive/cards/pricing-card/index.ts @@ -0,0 +1 @@ +export { PricingCard } from './pricing-card' diff --git a/components/interactive/cards/pricing-card/pricing-card.stories.tsx b/components/interactive/cards/pricing-card/pricing-card.stories.tsx new file mode 100644 index 0000000..f621d4a --- /dev/null +++ b/components/interactive/cards/pricing-card/pricing-card.stories.tsx @@ -0,0 +1,144 @@ +/* eslint-disable import/no-default-export */ +import { ButtonLink } from '@/components/interactive/buttons/button-link' +import { PricingCard as PricingCardUi } from './pricing-card' +import { periods } from './pricing-card.types' +import type { Meta, StoryObj } from '@storybook/react' + +const meta = { + title: 'Interactive / Cards / Pricing Card', + component: PricingCardUi, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + ctas: { + control: { + type: 'text', + }, + description: 'One or more CTAs', + table: { + defaultValue: { + summary: '[required]', + }, + type: { + summary: 'ReactNode', + }, + }, + }, + description: { + control: { + type: 'text', + }, + description: 'Optional text content', + table: { + defaultValue: { + summary: 'undefined', + }, + type: { + summary: 'string', + }, + }, + }, + details: { + control: { + type: 'text', + }, + description: 'Detailed bullets', + table: { + defaultValue: { + summary: '[]', + }, + type: { + summary: 'string[]', + }, + }, + }, + isFeatured: { + control: { + type: 'boolean', + }, + description: 'Whether the plan is the featured one', + table: { + defaultValue: { + summary: 'false', + }, + type: { + summary: 'boolean', + }, + }, + }, + name: { + control: { + type: 'text', + }, + description: 'The name', + table: { + defaultValue: { + summary: '[required]', + }, + type: { + summary: 'string', + }, + }, + }, + period: { + control: { + type: 'select', + }, + description: 'The period the applies to the price', + options: periods, + table: { + defaultValue: { + summary: periods[0], + }, + type: { + summary: `Period: any of: ${periods.join(', ')}.`, + }, + }, + }, + price: { + control: { + type: 'range', + min: 0, + max: 1000, + step: 10, + }, + description: + 'The price of the plan; use zero for “Free”; values below zero are not allowed', + table: { + defaultValue: { + summary: '[required]', + }, + type: { + summary: 'number', + }, + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const PricingCard: Story = { + args: { + ctas: ( + + Action + + ), + description: '', + details: [ + 'Lorem adipiscing', + 'Sollicitudin', + 'Lorem ipsum tincidunt', + 'Lorem ipsum posuere', + 'Lorem ipsum vulputate', + ], + isFeatured: false, + name: 'Standard', + period: 'month', + price: 100, + }, +} diff --git a/components/interactive/cards/pricing-card/pricing-card.tsx b/components/interactive/cards/pricing-card/pricing-card.tsx new file mode 100644 index 0000000..4707608 --- /dev/null +++ b/components/interactive/cards/pricing-card/pricing-card.tsx @@ -0,0 +1,128 @@ +'use client' + +import styled, { css } from 'styled-components' +import { Text } from '@/components/core/typography' +import { inverseThemePaletteVar } from '@/styles/themes' +import type { PricingCardProps } from './pricing-card.types' + +const StyledPricingCard = styled.li<{ $inverse: boolean }>` + // vars + --surface: var(--color-primary-surface); + --surface-contrast: var(--color-primary-surface-contrast); + + align-self: stretch; + display: grid; + align-items: start; + grid-template-columns: 1fr; + grid-template-rows: min-content min-content 1fr 3rem; + grid-gap: 0.75rem; + margin: 0; + padding: 1.25rem; + max-width: 20.4375rem; + color: var(--surface-contrast); + background: var(--surface); + border: 0.0625rem solid var(--surface-contrast); + border-radius: 0.25rem; + box-shadow: 0.25rem 0.25rem 0 0 var(--neutral-black); + list-style-type: none; + + ${({ $inverse, theme }) => + $inverse + ? css` + --surface: var( + ${inverseThemePaletteVar(theme, '--color-primary-surface')} + ); + --surface-contrast: var( + ${inverseThemePaletteVar(theme, '--color-primary-surface-contrast')} + ); + ` + : ''} +` + +const StyledPricing = styled.p` + display: flex; + align-items: baseline; + gap: 0.25rem; + margin: 0; + color: var(--surface-contrast); + text-transform: uppercase; +` + +const StyledDetails = styled.ul` + display: flex; + flex-direction: column; + margin: 1.5rem 0; + padding: 0; + color: var(--surface-contrast); + list-style-type: none; + + li { + padding-top: 0.3125rem; + padding-left: 1em; + + &::before { + content: '\003e'; + margin-left: -1em; + padding-right: 0.4rem; + } + } + + li:not(:last-child) { + padding-bottom: 0.3125rem; + border-bottom: 0.0625rem dotted var(--surface-contrast); + } +` + +const StyledActions = styled.div` + display: flex; + flex-direction: column; + gap: 1.25rem; +` + +export const PricingCard: React.FC = ({ + ctas, + description, + details, + isFeatured = false, + name, + period = 'month', + price, + ...props +}) => { + if (!ctas || !name || typeof price !== 'number' || price < 0) { + return null + } + + return ( + + + {price === 0 ? ( + Free + ) : ( + <> + + {new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + }) + .format(price) + .replace(/\.00$/, '')} + + + {`/a ${period}`} + + + )} + + {name} + + {details.map(detail => ( + + {detail} + + ))} + + {ctas} + + ) +} diff --git a/components/interactive/cards/pricing-card/pricing-card.types.ts b/components/interactive/cards/pricing-card/pricing-card.types.ts new file mode 100644 index 0000000..be52f1d --- /dev/null +++ b/components/interactive/cards/pricing-card/pricing-card.types.ts @@ -0,0 +1,14 @@ +import { HTMLAttributes, ReactNode } from 'react' + +export const periods = ['month', 'year'] as const +export type Period = (typeof periods)[number] + +export interface PricingCardProps extends HTMLAttributes { + ctas: ReactNode + description?: string + details: string[] + isFeatured?: boolean + name: string + period?: Period + price: number +} From daa6f8602ffdb83d77bd6e0365de9fdd8ff05f06 Mon Sep 17 00:00:00 2001 From: David McKelvey Date: Sun, 24 Nov 2024 19:42:34 -0800 Subject: [PATCH 2/4] add button link xl --- .../button-link-xl/button-link-xl.stories.tsx | 101 ++++++++++ .../buttons/button-link-xl/button-link-xl.tsx | 172 ++++++++++++++++++ .../button-link-xl/button-link-xl.types.ts | 9 + .../buttons/button-link-xl/index.ts | 0 4 files changed, 282 insertions(+) create mode 100644 components/interactive/buttons/button-link-xl/button-link-xl.stories.tsx create mode 100644 components/interactive/buttons/button-link-xl/button-link-xl.tsx create mode 100644 components/interactive/buttons/button-link-xl/button-link-xl.types.ts create mode 100644 components/interactive/buttons/button-link-xl/index.ts diff --git a/components/interactive/buttons/button-link-xl/button-link-xl.stories.tsx b/components/interactive/buttons/button-link-xl/button-link-xl.stories.tsx new file mode 100644 index 0000000..c2e84a4 --- /dev/null +++ b/components/interactive/buttons/button-link-xl/button-link-xl.stories.tsx @@ -0,0 +1,101 @@ +/* eslint-disable import/no-default-export */ +// eslint-disable-next-line import/no-extraneous-dependencies +import { fn } from '@storybook/test' +import { MouseEvent } from 'react' +import { + ButtonState, + buttonStates, + StatesGrid, +} from '@/components/helpers/states-grid' +import { ButtonLinkXl as ButtonLinkXlUi } from './button-link-xl' +import type { Meta, StoryObj } from '@storybook/react' + +const meta = { + title: 'Interactive / Buttons / Button Link XL', + component: ButtonLinkXlUi, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + children: { + control: { + type: 'text', + }, + description: 'The button content', + table: { + defaultValue: { + summary: '[required]', + }, + type: { + summary: 'ReactNode', + }, + }, + }, + href: { + control: { + type: 'text', + }, + description: 'The destination href', + table: { + defaultValue: { + summary: '[required]', + }, + type: { + summary: 'string Url', + }, + }, + }, + inverse: { + control: { + type: 'boolean', + }, + description: 'Whether the theme colors should be inverted', + table: { + defaultValue: { + summary: 'false', + }, + type: { + summary: 'boolean', + }, + }, + }, + }, + args: { + onClick: (event: MouseEvent) => { + event.stopPropagation() + event.preventDefault() + fn() + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const States: Story = { + args: { + children: 'Button', + href: 'https://plasticlabs.ai', + inverse: false, + }, + decorators: [ + (_, { args }) => { + const story: React.FC<{ column: string; row: string }> = ({ row }) => { + return + } + + return ( + + ) + }, + ], +} + +export const ButtonLinkXl: Story = { + args: { + children: 'Button', + href: 'https://plasticlabs.ai', + inverse: false, + }, +} diff --git a/components/interactive/buttons/button-link-xl/button-link-xl.tsx b/components/interactive/buttons/button-link-xl/button-link-xl.tsx new file mode 100644 index 0000000..b00522a --- /dev/null +++ b/components/interactive/buttons/button-link-xl/button-link-xl.tsx @@ -0,0 +1,172 @@ +'use client' + +import styled, { css } from 'styled-components' +import { inverseThemePaletteVar } from '@/styles/themes' +import { isInternalUrl } from '@/utils/url' +import { ButtonChildren } from '../shared/button-children' +import type { ButtonLinkXlProps } from './button-link-xl.types' +import type { SharedThemeStylesProps } from '../shared/theme.types' + +const StyledAnchor = styled.a` + // vars + --surface: var(--interactive-primary-button-surface); + --surface-contrast: var(--interactive-primary-button-surface-contrast); + --accent: var(--interactive-primary-button-accent); + + // base styles + justify-self: stretch; + position: relative; + display: flex; + align-items: center; + justify-content: center; + margin: 0; + padding: 0 3rem; + height: 6.5rem; + min-height: 6.5rem; + overflow: hidden; + + font-family: var(--font-family-roboto-mono); + font-size: 1rem; + font-weight: 300; + line-height: 1; + letter-spacing: -0.01em; + text-transform: uppercase; + text-decoration: none; + white-space: nowrap; + + border-width: 0; + border-radius: 0.25rem; + + transition: color var(--ui-transition-speed) ease; + + box-sizing: border-box; + appearance: none; + user-select: none; + + * { + z-index: 2; + } + + // over background + &::before { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: var(--surface); + opacity: 0; + z-index: 0; + transition: opacity var(--ui-transition-speed) ease; + } + + // border + &::after { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + border-width: 0.0625rem; + border-style: dotted; + border-color: var(--surface); + border-radius: inherit; + z-index: 1; + transition: border-color var(--ui-transition-speed) ease; + } + + // states + &, + &:visited { + color: var(--surface); + background: var(--surface-contrast); + } + + &:hover:not(:active):not([href='']):not([href='#']):not([data-state]), + &[data-state='hover'] { + color: var(--surface-contrast); + + &::before { + opacity: 1; + } + + &::after { + border-color: var(--surface-contrast); + } + } + + &:active:not([href='']):not([href='#']):not([data-state]), + &:hover:active:not([href='']):not([href='#']):not([data-state]), + &[data-state='pressed'] { + color: var(--color-black); + background: var(--accent); + + &::after { + border-color: var(--color-black); + } + } + + &[href='']:not([data-state]), + &[href='#']:not([data-state]), + &[data-state='disabled'] { + color: hsl(from var(--color-black) h s l / 0.6); + background: var(--neutral-grey); + pointer-events: none; + + &::after { + border-color: hsl(from var(--color-black) h s l / 0.3); + } + } + + ${({ $inverse, theme }) => + $inverse + ? css` + --surface: var( + ${inverseThemePaletteVar( + theme, + '--interactive-primary-button-surface', + )} + ); + --surface-contrast: var( + ${inverseThemePaletteVar( + theme, + '--interactive-primary-button-surface-contrast', + )} + ); + --accent: var( + ${inverseThemePaletteVar( + theme, + '--interactive-primary-button-accent', + )} + ); + ` + : ''} +` + +/** + * This component is for outbound links which appear as gigantic buttons. + */ +export const ButtonLinkXl: React.FC = ({ + children, + href, + inverse, + ...props +}) => { + if (!children || isInternalUrl(href)) { + return null + } + + return ( + + {children} + + ) +} diff --git a/components/interactive/buttons/button-link-xl/button-link-xl.types.ts b/components/interactive/buttons/button-link-xl/button-link-xl.types.ts new file mode 100644 index 0000000..48d4c40 --- /dev/null +++ b/components/interactive/buttons/button-link-xl/button-link-xl.types.ts @@ -0,0 +1,9 @@ +import { AnchorHTMLAttributes, ReactNode } from 'react' +import { SharedThemeProps } from '../shared/theme.types' + +export interface ButtonLinkXlProps + extends AnchorHTMLAttributes, + SharedThemeProps { + href: string + children: ReactNode +} diff --git a/components/interactive/buttons/button-link-xl/index.ts b/components/interactive/buttons/button-link-xl/index.ts new file mode 100644 index 0000000..e69de29 From 1d3c4f3f5a497deee9f2af29045ad83bd5221eed Mon Sep 17 00:00:00 2001 From: David McKelvey Date: Sun, 24 Nov 2024 19:48:20 -0800 Subject: [PATCH 3/4] update typography --- .../core/typography/typography.constants.ts | 5 ++- components/core/typography/typography.tsx | 45 ++++++++++++++++++- .../core/typography/typography.types.ts | 5 ++- .../sections/partners/partners-section.tsx | 2 +- 4 files changed, 52 insertions(+), 5 deletions(-) diff --git a/components/core/typography/typography.constants.ts b/components/core/typography/typography.constants.ts index 73fd5c4..a5aee24 100644 --- a/components/core/typography/typography.constants.ts +++ b/components/core/typography/typography.constants.ts @@ -2,7 +2,10 @@ import { DefaultTagMap } from './typography.types' export const defaultTag: DefaultTagMap = { Banner: 'h2', - Accent: 'h3', + Accent1: 'h2', + Accent2: 'h3', + Accent3: 'h3', + Accent4: 'p', H1: 'h1', H2: 'h2', H3: 'h3', diff --git a/components/core/typography/typography.tsx b/components/core/typography/typography.tsx index e536141..7a5ea77 100644 --- a/components/core/typography/typography.tsx +++ b/components/core/typography/typography.tsx @@ -18,7 +18,9 @@ const sharedBodyStyles = css` ` const isHeaderFontFamily = (style: TypographyProps['variant']): boolean => - ['Banner', 'Accent', 'H1', 'H3'].includes(style) + ['Banner', 'Accent1', 'Accent2', 'Accent3', 'Accent4', 'H1', 'H3'].includes( + style, + ) const StyledText = styled.p<{ $align: TypographyProps['align'] @@ -47,7 +49,20 @@ const StyledText = styled.p<{ font-size: 12.5rem; } ` - case 'Accent': // Still very large text + case 'Accent1': // Very very large text + return css` + font-size: 3.75rem; + line-height: ${70 / 60}; + letter-spacing: -0.023em; + text-transform: uppercase; + text-wrap: balance; + + @media (min-width: ${THIN_BREAKPOINT}rem) { + font-size: 4.6875rem; + line-height: ${80 / 75}; + } + ` + case 'Accent2': // Very large text return css` font-size: 2.1875rem; line-height: ${40 / 35}; @@ -60,6 +75,32 @@ const StyledText = styled.p<{ line-height: ${55 / 45}; } ` + case 'Accent3': // Large text + return css` + font-size: 1.5rem; + line-height: ${30 / 24}; + letter-spacing: -0.023em; + text-transform: uppercase; + text-wrap: balance; + + @media (min-width: ${THIN_BREAKPOINT}rem) { + font-size: 1.875rem; + line-height: ${35 / 30}; + } + ` + case 'Accent4': + return css` + font-size: 1rem; + line-height: ${18 / 16}; + letter-spacing: -0.023em; + text-transform: uppercase; + text-wrap: balance; + + @media (min-width: ${THIN_BREAKPOINT}rem) { + font-size: 1.25rem; + line-height: ${24 / 20}; + } + ` case 'H1': // Not visible return css` font-size: 2rem; diff --git a/components/core/typography/typography.types.ts b/components/core/typography/typography.types.ts index a952405..da27d8a 100644 --- a/components/core/typography/typography.types.ts +++ b/components/core/typography/typography.types.ts @@ -18,7 +18,10 @@ export type TypographyTag = (typeof typographyTags)[number] export const typographyVariants = [ 'Banner', - 'Accent', + 'Accent1', + 'Accent2', + 'Accent3', + 'Accent4', 'H1', 'H2', 'H3', diff --git a/components/sections/partners/partners-section.tsx b/components/sections/partners/partners-section.tsx index c2d2be2..c00ef16 100644 --- a/components/sections/partners/partners-section.tsx +++ b/components/sections/partners/partners-section.tsx @@ -43,7 +43,7 @@ export const PartnersSection: React.FC = () => { - <StyledAccent variant="Accent">We are backed by</StyledAccent> + <StyledAccent variant="Accent2">We are backed by</StyledAccent> </StyledIntro> <Partners /> </StyledPartnersSection> From de7d3eb3eaa7f12ed9523ea4d94b8621df3b92d1 Mon Sep 17 00:00:00 2001 From: David McKelvey <david@mckelveycreative.co> Date: Sun, 24 Nov 2024 19:48:42 -0800 Subject: [PATCH 4/4] add link-navigation --- .../links/link-navigation/index.ts | 1 + .../link-navigation.stories.tsx | 85 +++++++++++++++++ .../links/link-navigation/link-navigation.tsx | 94 +++++++++++++++++++ .../link-navigation/link-navigation.types.ts | 8 ++ 4 files changed, 188 insertions(+) create mode 100644 components/interactive/links/link-navigation/index.ts create mode 100644 components/interactive/links/link-navigation/link-navigation.stories.tsx create mode 100644 components/interactive/links/link-navigation/link-navigation.tsx create mode 100644 components/interactive/links/link-navigation/link-navigation.types.ts diff --git a/components/interactive/links/link-navigation/index.ts b/components/interactive/links/link-navigation/index.ts new file mode 100644 index 0000000..2896dc5 --- /dev/null +++ b/components/interactive/links/link-navigation/index.ts @@ -0,0 +1 @@ +export { LinkNavigation } from './link-navigation' diff --git a/components/interactive/links/link-navigation/link-navigation.stories.tsx b/components/interactive/links/link-navigation/link-navigation.stories.tsx new file mode 100644 index 0000000..9c901ea --- /dev/null +++ b/components/interactive/links/link-navigation/link-navigation.stories.tsx @@ -0,0 +1,85 @@ +/* eslint-disable import/no-default-export */ +// eslint-disable-next-line import/no-extraneous-dependencies +import { fn } from '@storybook/test' +import { MouseEvent } from 'react' +import { + ButtonState, + buttonStates, + StatesGrid, +} from '@/components/helpers/states-grid' +import { LinkNavigation as LinkNavigationUi } from './link-navigation' +import type { Meta, StoryObj } from '@storybook/react' + +const meta = { + title: 'Interactive / Links / Link Navigation', + component: LinkNavigationUi, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + children: { + control: { + type: 'text', + }, + description: 'The button content', + table: { + defaultValue: { + summary: '[required]', + }, + type: { + summary: 'ReactNode', + }, + }, + }, + href: { + control: { + type: 'text', + }, + description: 'The destination href', + table: { + defaultValue: { + summary: '[required]', + }, + type: { + summary: 'string Url', + }, + }, + }, + }, + args: { + onClick: (event: MouseEvent<HTMLAnchorElement>) => { + event.stopPropagation() + event.preventDefault() + fn() + }, + }, +} satisfies Meta<typeof LinkNavigationUi> + +export default meta +type Story = StoryObj<typeof meta> + +export const States: Story = { + args: { + children: 'Button', + href: 'https://plasticlabs.ai', + }, + decorators: [ + (_, { args }) => { + const story: React.FC<{ column: string; row: string }> = ({ row }) => { + return <LinkNavigationUi {...args} data-state={row as ButtonState} /> + } + + return ( + <StatesGrid columns={['State']} rows={buttonStates} Story={story} /> + ) + }, + ], +} + +export const LinkNavigation: Story = { + args: { + children: 'Inline Link', + href: 'https://plasticlabs.ai', + }, +} diff --git a/components/interactive/links/link-navigation/link-navigation.tsx b/components/interactive/links/link-navigation/link-navigation.tsx new file mode 100644 index 0000000..c927275 --- /dev/null +++ b/components/interactive/links/link-navigation/link-navigation.tsx @@ -0,0 +1,94 @@ +'use client' + +import Link from 'next/link' +import styled, { css } from 'styled-components' +import { isInternalUrl } from '@/utils/url' +import type { LinkNavigationProps } from './link-navigation.types' + +const linkStyles = css` + // base styles + display: inline; + + font-family: var(--font-family-roboto-mono); + font-size: 1rem; + font-weight: 400; + line-height: 1; + letter-spacing: -0.02em; + text-decoration: none; + white-space: nowrap; + + transition: color var(--ui-transition-speed) ease; + + user-select: none; + + // states + &, + &:visited { + color: var(--color-primary-surface-contrast); + } + + &:hover:not([href='']):not([href='#']):not([data-state]), + &[data-state='hover'] { + text-decoration: underline; + } + + &:active:not([href='']):not([href='#']):not([data-state]), + &:hover:active:not([href='']):not([href='#']):not([data-state]), + &[data-state='pressed'] { + text-decoration: underline; + text-decoration-color: var(--color-primary-accent); + } + + &[href='']:not([data-state]), + &[href='#']:not([data-state]), + &[data-state='disabled'] { + color: hsl(from var(--color-black) h s l / 0.6); + text-decoration: underline; + text-decoration-color: hsl(from var(--color-black) h s l / 0.3); + + pointer-events: none; + cursor: default; + } +` + +const StyledNextLink = styled(Link)` + ${linkStyles} +` + +const StyledAnchor = styled.a` + ${linkStyles} +` + +/** + * This component is for links which appear within the main navigation. + */ +export const LinkNavigation: React.FC<LinkNavigationProps> = ({ + children, + href, + ...props +}) => { + if (!children) { + return null + } + + if (isInternalUrl(href)) { + return ( + <StyledNextLink {...props} href={href}> + {children} + </StyledNextLink> + ) + } + + const { as, ...rest } = props + + return ( + <StyledAnchor + {...rest} + href={href.toString()} + rel="noopener" + target="_blank" + > + {children} + </StyledAnchor> + ) +} diff --git a/components/interactive/links/link-navigation/link-navigation.types.ts b/components/interactive/links/link-navigation/link-navigation.types.ts new file mode 100644 index 0000000..9f111ab --- /dev/null +++ b/components/interactive/links/link-navigation/link-navigation.types.ts @@ -0,0 +1,8 @@ +import { LinkProps } from 'next/link' +import { AnchorHTMLAttributes, ReactNode } from 'react' + +export interface LinkNavigationProps + extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>, + LinkProps { + children: ReactNode +}