Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
},
"dependencies": {
"@fluentui/react-button": "^9.4.6",
"@fluentui/react-link": "^9.6.7",
"@fluentui/semantic-tokens": "^0.0.1",
"@fluentui/react-icons": "^2.0.245",
"@fluentui/react-provider": "^9.22.7",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
'use client';

import { shorthands, makeStyles, mergeClasses } from '@griffel/react';
import { createCustomFocusIndicatorStyle } from '@fluentui/react-tabster';
import { tokens } from '@fluentui/react-theme';
import * as semanticTokens from '@fluentui/semantic-tokens';
import { linkClassNames, type LinkState } from '@fluentui/react-link';
import { getSlotClassNameProp_unstable } from '@fluentui/react-utilities';

const useStyles = makeStyles({
focusIndicator: createCustomFocusIndicatorStyle({
textDecorationColor: semanticTokens.groupFocusInnerStroke,
textDecorationLine: 'underline',
textDecorationStyle: 'double',
outlineStyle: 'none',
}),
// Common styles.
root: {
':focus-visible': {
outlineStyle: 'none',
},
textDecoration: semanticTokens.groupLinkTextUnderlineStyle,
backgroundColor: 'transparent',
boxSizing: 'border-box',
color: semanticTokens.groupLinkBrandTextForeground,
cursor: 'pointer',
display: 'inline',
fontFamily: semanticTokens.groupLinkTextFontfamily,
fontSize: semanticTokens.groupLinkMediumTextFontsize,
lineHeight: semanticTokens.groupLinkMediumTextLineheight,
fontWeight: semanticTokens.groupLinkMediumTextFontweight,
margin: '0',
padding: '0',
overflow: 'inherit',
textAlign: 'left',
textDecorationLine: semanticTokens.groupLinkOnpageTextDecorationline,
textDecorationThickness: semanticTokens.groupLinkTextUnderlineStrokewidth,
textOverflow: 'inherit',
userSelect: 'text',

':hover': {
textDecorationLine: semanticTokens.groupLinkOnpageTextDecorationlineHover,
color: semanticTokens.groupLinkBrandTextForegroundHover,
},

':active': {
textDecorationLine: semanticTokens.groupLinkOnpageTextDecorationlinePressed,
color: semanticTokens.groupLinkBrandTextForegroundPressed,
},
},
// Overrides when the Link renders as a button.
button: {
...shorthands.borderStyle('none'),
},
// Overrides when an href is present so the Link renders as an anchor.
href: {
fontSize: 'inherit',
lineHeight: 'inherit',
},
// Overrides when the Link appears subtle.
subtle: {
color: semanticTokens.groupLinkNeutralTextForeground,

':hover': {
color: semanticTokens.groupLinkNeutralTextForegroundHover,
},

':active': {
color: semanticTokens.groupLinkNeutralTextForegroundPressed,
},
},
// Overrides when the Link is rendered inline within text.
inline: {
textDecorationLine: 'underline',
':hover': {
textDecorationLine: 'underline',
},
':active': {
textDecorationLine: 'underline',
},
},
// Overrides when the Link is disabled.
disabled: {
textDecorationLine: 'none',
color: semanticTokens.groupLinkBrandTextForegroundDisabled,
cursor: 'not-allowed',

':hover': {
textDecorationLine: 'none',
color: semanticTokens.groupLinkBrandTextForegroundDisabled,
},

':active': {
textDecorationLine: 'none',
color: semanticTokens.groupLinkBrandTextForegroundDisabled,
},
},
// Overrides when the Link is disabled.
disabledSubtle: {
textDecorationLine: 'none',
color: semanticTokens.groupLinkNeutralTextForegroundDisabled,
cursor: 'not-allowed',

':hover': {
textDecorationLine: 'none',
color: semanticTokens.groupLinkNeutralTextForegroundDisabled,
},

':active': {
textDecorationLine: 'none',
color: semanticTokens.groupLinkNeutralTextForegroundDisabled,
},
},

// Semantic-tokens does not include inverted tokens, use legacy/inverted themes
inverted: {
color: tokens.colorBrandForegroundInverted,
':hover': {
color: tokens.colorBrandForegroundInvertedHover,
},
':active': {
color: tokens.colorBrandForegroundInvertedPressed,
},
},
});

export const useSemanticLinkStyles = (_state: unknown): LinkState => {
'use no memo';

const state = _state as LinkState;

const styles = useStyles();
const { appearance, disabled, inline, root, backgroundAppearance } = state;

state.root.className = mergeClasses(
state.root.className,
linkClassNames.root,
styles.root,
styles.focusIndicator,
root.as === 'a' && root.href && styles.href,
root.as === 'button' && styles.button,
appearance === 'subtle' && styles.subtle,
backgroundAppearance === 'inverted' && styles.inverted,
inline && styles.inline,
disabled && styles.disabled,
appearance === 'subtle' && disabled && styles.disabledSubtle,
getSlotClassNameProp_unstable(state.root),
);

return state;
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
useSemanticSplitButtonStyles,
useSemanticToggleButtonStyles,
} from './Button';
import { useSemanticLinkStyles } from './Link/useSemanticLinkStyles.styles';

export const SEMANTIC_STYLE_HOOKS: FluentProviderCustomStyleHooks = {
// Button styles
Expand All @@ -15,4 +16,6 @@ export const SEMANTIC_STYLE_HOOKS: FluentProviderCustomStyleHooks = {
useSplitButtonStyles_unstable: useSemanticSplitButtonStyles,
useMenuButtonStyles_unstable: useSemanticMenuButtonStyles,
useCompoundButtonStyles_unstable: useSemanticCompoundButtonStyles,
// Link styles
useLinkStyles_unstable: useSemanticLinkStyles,
};
108 changes: 108 additions & 0 deletions packages/semantic-tokens/etc/semantic-tokens.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,114 @@ export const groupFocusOuterStroke = "var(--smtc-group-focus-outer-stroke, var(-
// @public (undocumented)
export const groupFocusOuterStrokewidth = "var(--smtc-group-focus-outer-strokewidth, var(--strokeWidthThick))";

// @public (undocumented)
export const groupLinkBrandIconForeground = "var(--smtc-group-link-brand-icon-foreground)";

// @public (undocumented)
export const groupLinkBrandIconForegroundDisabled = "var(--smtc-group-link-brand-icon-foreground-disabled)";

// @public (undocumented)
export const groupLinkBrandIconForegroundHover = "var(--smtc-group-link-brand-icon-foreground-hover)";

// @public (undocumented)
export const groupLinkBrandIconForegroundPressed = "var(--smtc-group-link-brand-icon-foreground-pressed)";

// @public (undocumented)
export const groupLinkBrandTextForeground = "var(--smtc-group-link-brand-text-foreground, var(--colorBrandForegroundLink))";

// @public (undocumented)
export const groupLinkBrandTextForegroundDisabled = "var(--smtc-group-link-brand-text-foreground-disabled, var(--colorNeutralForegroundDisabled))";

// @public (undocumented)
export const groupLinkBrandTextForegroundHover = "var(--smtc-group-link-brand-text-foreground-hover, var(--colorBrandForegroundLinkHover))";

// @public (undocumented)
export const groupLinkBrandTextForegroundPressed = "var(--smtc-group-link-brand-text-foreground-pressed, var(--colorBrandForegroundLinkPressed))";

// @public (undocumented)
export const groupLinkGap = "var(--smtc-group-link-gap)";

// @public (undocumented)
export const groupLinkLargeIconSize = "var(--smtc-group-link-large-icon-size)";

// @public (undocumented)
export const groupLinkLargeTextFontsize = "var(--smtc-group-link-large-text-fontsize)";

// @public (undocumented)
export const groupLinkLargeTextFontweight = "var(--smtc-group-link-large-text-fontweight)";

// @public (undocumented)
export const groupLinkLargeTextLineheight = "var(--smtc-group-link-large-text-lineheight)";

// @public (undocumented)
export const groupLinkMediumIconSize = "var(--smtc-group-link-medium-icon-size)";

// @public (undocumented)
export const groupLinkMediumTextFontsize = "var(--smtc-group-link-medium-text-fontsize, var(--fontSizeBase300))";

// @public (undocumented)
export const groupLinkMediumTextFontweight = "var(--smtc-group-link-medium-text-fontweight, var(--fontWeightRegular))";

// @public (undocumented)
export const groupLinkMediumTextLineheight = "var(--smtc-group-link-medium-text-lineheight, var(--lineHeightBase300))";

// @public (undocumented)
export const groupLinkNeutralIconForeground = "var(--smtc-group-link-neutral-icon-foreground)";

// @public (undocumented)
export const groupLinkNeutralIconForegroundDisabled = "var(--smtc-group-link-neutral-icon-foreground-disabled)";

// @public (undocumented)
export const groupLinkNeutralIconForegroundHover = "var(--smtc-group-link-neutral-icon-foreground-hover)";

// @public (undocumented)
export const groupLinkNeutralIconForegroundPressed = "var(--smtc-group-link-neutral-icon-foreground-pressed)";

// @public (undocumented)
export const groupLinkNeutralTextForeground = "var(--smtc-group-link-neutral-text-foreground, var(--colorNeutralForeground2))";

// @public (undocumented)
export const groupLinkNeutralTextForegroundDisabled = "var(--smtc-group-link-neutral-text-foreground-disabled, var(--colorNeutralForegroundDisabled))";

// @public (undocumented)
export const groupLinkNeutralTextForegroundHover = "var(--smtc-group-link-neutral-text-foreground-hover, var(--colorNeutralForeground2Hover))";

// @public (undocumented)
export const groupLinkNeutralTextForegroundPressed = "var(--smtc-group-link-neutral-text-foreground-pressed, var(--colorNeutralForeground2Pressed))";

// @public (undocumented)
export const groupLinkOnpageTextDecorationline = "var(--smtc-group-link-onpage-text-decorationline, none)";

// @public (undocumented)
export const groupLinkOnpageTextDecorationlineDisabled = "var(--smtc-group-link-onpage-text-decorationline-disabled, none)";

// @public (undocumented)
export const groupLinkOnpageTextDecorationlineHover = "var(--smtc-group-link-onpage-text-decorationline-hover, underline)";

// @public (undocumented)
export const groupLinkOnpageTextDecorationlinePressed = "var(--smtc-group-link-onpage-text-decorationline-pressed, underline)";

// @public (undocumented)
export const groupLinkSmallIconSize = "var(--smtc-group-link-small-icon-size)";

// @public (undocumented)
export const groupLinkSmallTextFontsize = "var(--smtc-group-link-small-text-fontsize)";

// @public (undocumented)
export const groupLinkSmallTextFontweight = "var(--smtc-group-link-small-text-fontweight)";

// @public (undocumented)
export const groupLinkSmallTextLineheight = "var(--smtc-group-link-small-text-lineheight)";

// @public (undocumented)
export const groupLinkTextFontfamily = "var(--smtc-group-link-text-fontfamily, var(--fontFamilyBase))";

// @public (undocumented)
export const groupLinkTextUnderlineStrokewidth = "var(--smtc-group-link-text-underline-strokewidth, var(--strokeWidthThin))";

// @public (undocumented)
export const groupLinkTextUnderlineStyle = "var(--smtc-group-link-text-underline-style, solid)";

// (No @packageDocumentation comment for this package)

```
39 changes: 39 additions & 0 deletions packages/semantic-tokens/scripts/definitions/components/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { GroupPart } from '../groups.types';

// Link tokens
export const linkGroup: GroupPart = {
components: ['link'],
coreProperties: ['gap'],
variants: ['brand', 'neutral'],
states: ['', 'hover', 'pressed', 'disabled'],
parts: {
icon: {
variants: ['brand', 'neutral'],
states: ['', 'hover', 'pressed', 'disabled'],
scales: ['small', 'medium', 'large'],
scaleProperties: ['size'],
variantStateProperties: ['foreground'],
},
text: {
variants: ['brand', 'neutral'],
coreProperties: ['fontfamily'],
variantStateProperties: ['foreground'],
scales: ['small', 'medium', 'large'],
states: ['', 'hover', 'pressed', 'disabled'],
scaleProperties: ['fontsize', 'lineheight', 'fontweight'],
exceptions: [
{
// Onpage has full control over how to apply underline styles per state
states: ['', 'hover', 'pressed', 'disabled'],
variants: ['onpage'],
variantStateProperties: ['decorationline'],
},
],
},
'text.underline': {
coreProperties: ['strokewidth', 'style'],
variants: ['brand', 'neutral'],
states: ['', 'hover', 'pressed', 'disabled'],
},
},
};
57 changes: 57 additions & 0 deletions packages/semantic-tokens/scripts/definitions/fallbacks/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { tokens } from '@fluentui/tokens';
import { GroupFallback } from './fallbacks.types';

export const linkFallbacks: GroupFallback = {
groupLinkTextUnderlineStrokewidth: {
fluent: tokens.strokeWidthThin,
},
groupLinkTextUnderlineStyle: { fluent: 'solid' },
groupLinkTextFontfamily: {
fluent: tokens.fontFamilyBase,
},
groupLinkMediumTextFontsize: {
fluent: tokens.fontSizeBase300,
},
groupLinkMediumTextFontweight: {
fluent: tokens.fontWeightRegular,
},
groupLinkBrandTextForeground: {
fluent: tokens.colorBrandForegroundLink,
},
groupLinkBrandTextForegroundHover: {
fluent: tokens.colorBrandForegroundLinkHover,
},
groupLinkBrandTextForegroundPressed: {
fluent: tokens.colorBrandForegroundLinkPressed,
},
groupLinkNeutralTextForeground: {
fluent: tokens.colorNeutralForeground2,
},
groupLinkNeutralTextForegroundHover: {
fluent: tokens.colorNeutralForeground2Hover,
},
groupLinkNeutralTextForegroundPressed: {
fluent: tokens.colorNeutralForeground2Pressed,
},
groupLinkBrandTextForegroundDisabled: {
fluent: tokens.colorNeutralForegroundDisabled,
},
groupLinkNeutralTextForegroundDisabled: {
fluent: tokens.colorNeutralForegroundDisabled,
},
groupLinkMediumTextLineheight: {
fluent: tokens.lineHeightBase300,
},
groupLinkOnpageTextDecorationline: {
fluent: 'none',
},
groupLinkOnpageTextDecorationlineHover: {
fluent: 'underline',
},
groupLinkOnpageTextDecorationlinePressed: {
fluent: 'underline',
},
groupLinkOnpageTextDecorationlineDisabled: {
fluent: 'none',
},
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { buttonFallbacks } from './fallbacks/button';
import { focusFallbacks } from './fallbacks/focus';
import { GroupFallbacks } from './fallbacks/fallbacks.types';
import { linkFallbacks } from './fallbacks/link';

export const groupFallbacks: GroupFallbacks = {
focus: focusFallbacks,
button: buttonFallbacks,
link: linkFallbacks,
};
Loading
Loading