Moving staging react package into playground package (#2152)

* moves playground into main

* @types/react match version

* fixed with webpack workaround

* adding react-theming back to lint ignore

* fix lint

* adding a changelog entry

* updated to add username to changelog line

* fixing syncpack issues

* fixed the dependency weirdness with react

* adding exceptions for non-startdust packages in testing and then adding them to be tested via lerna call instead of gulp (in preparation of future)

* let the max workers param be for gulp instead of lerna

* skipping test for now
This commit is contained in:
Kenneth Chau 2019-12-09 11:53:48 -08:00 коммит произвёл GitHub
Родитель 545e83dc16
Коммит b0c3efb873
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
66 изменённых файлов: 4483 добавлений и 3 удалений

Просмотреть файл

@ -5,3 +5,6 @@ node_modules/
# packages that have lib as the output
packages/digest/lib
packages/perf-test/lib
packages/playground/lib
packages/react-theming/lib

3
.gitignore поставляемый
Просмотреть файл

@ -25,3 +25,6 @@ temp/
# packages that have lib as the output
packages/digest/lib
packages/perf-test/lib
packages/playground/lib
packages/react-theming/lib

Просмотреть файл

@ -16,6 +16,8 @@ perf/dist
packages/digest/lib
packages/perf-test/lib
packages/playground/lib
packages/react-theming/lib
.editorconfig
.gitignore

Просмотреть файл

@ -10,7 +10,11 @@ stats/
# packages that have lib as the output
packages/digest/lib
packages/perf-test/lib
packages/playground/lib
packages/react-theming/lib
package.json
packages/ability-attributes/src/schema.ts
CHANGELOG.md

Просмотреть файл

@ -8,7 +8,7 @@
"trailingComma": "all",
"overrides": [
{
"files": "packages/react-theming/**/*",
"files": ["packages/react-theming/**/*", "packages/playground/**/*"],
"options": {
"semi": true
}

Просмотреть файл

@ -21,6 +21,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Prevent text highlight on icon consecutive clicks in `Checkbox` @silviuavram ([#2154](https://github.com/microsoft/fluent-ui-react/pull/2154))
- Always handle provided onKeyDown event be propagated in inner zone @kolaps33 ([#2140](https://github.com/microsoft/fluent-ui-react/pull/2140))
### Features
- Add a new experimental @fluentui/react-theming package that includes a `compose()` @kenotron ([#2152](https://github.com/microsoft/fluent-ui-react/pull/2152))
<!--------------------------------[ v0.41.0 ]------------------------------- -->
## [v0.41.0](https://github.com/microsoft/fluent-ui-react/tree/v0.41.0) (2019-12-04)
[Compare changes](https://github.com/microsoft/fluent-ui-react/compare/v0.40.4...v0.41.0)

Просмотреть файл

@ -4,7 +4,13 @@ const { rollup: lernaAliases } = require('lerna-alias')
// packages/react/src -> packages/react,
// as lernaAliases append 'src' by default
const stardustPackages = lernaAliases({ sourceDirectory: false })
const projects = Object.keys(stardustPackages).map(packageName => stardustPackages[packageName])
// Excludes the non-stardust packages
const excluded = ['@fluentui/playground', '@fluentui/react-theming']
const projects = Object.keys(stardustPackages)
.filter(p => !excluded.includes(p))
.map(packageName => stardustPackages[packageName])
module.exports = {
coverageReporters,

Просмотреть файл

@ -0,0 +1,4 @@
jest.config.js
scripts/config
*.d.ts
**/*.js

Просмотреть файл

@ -0,0 +1,6 @@
{
"extends": ["../internal-tooling/eslint/index.js"],
"rules": {
"semi": ["error", "always"]
}
}

Просмотреть файл

@ -0,0 +1 @@
import '@storybook/addon-a11y/register';

Просмотреть файл

@ -0,0 +1,15 @@
import { configure } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';
import { withA11y } from '@storybook/addon-a11y';
import { addDecorator } from '@storybook/react';
addDecorator(withInfo());
addDecorator(withA11y());
const req = require.context('../src/components', true, /\.stories\.tsx$/);
function loadStories() {
return req.keys().map(req);
}
configure(loadStories, module);

Просмотреть файл

@ -0,0 +1,2 @@
const webpackConfig = require('@fluentui/scripts/config/storybook/webpack.config');
module.exports = webpackConfig;

Просмотреть файл

@ -0,0 +1,145 @@
{
"name": "@fluentui/react",
"entries": [
{
"date": "Fri, 22 Nov 2019 00:35:10 GMT",
"tag": "@fluentui/react_v0.3.2",
"version": "0.3.2",
"comments": {
"patch": [
{
"comment": "Icon: Move README spec to react package.",
"author": "Humberto.Morimoto@microsoft.com",
"commit": "44959e0f5dcdbbe9cf592f8654d9cd58a904d7cc"
}
]
}
},
{
"date": "Thu, 21 Nov 2019 00:45:42 GMT",
"tag": "@fluentui/react_v0.3.1",
"version": "0.3.1",
"comments": {
"patch": [
{
"comment": "update native controls to use new packages from @uifabricshared",
"author": "jasonmo@microsoft.com",
"commit": "717bc688b923af32a00b5e38f865e209ad0962df"
}
]
}
},
{
"date": "Tue, 19 Nov 2019 21:24:06 GMT",
"tag": "@fluentui/react_v0.3.0",
"version": "0.3.0",
"comments": {
"minor": [
{
"comment": "Link: Building first version of Fluent Link using composed.",
"author": "Humberto.Morimoto@microsoft.com",
"commit": "9fc7e7c5f0913e9e40bb6fbf3d6e293ee5d66906"
}
]
}
},
{
"date": "Tue, 12 Nov 2019 17:40:53 GMT",
"tag": "@fluentui/react_v0.2.1",
"version": "0.2.1",
"comments": {
"patch": [
{
"comment": "e2e runs do not need a flamegraph reporter",
"author": "kchau@microsoft.com",
"commit": "2956ae6b66fa3545326238077ebbe506bdbd2730"
}
]
}
},
{
"date": "Mon, 11 Nov 2019 23:53:02 GMT",
"tag": "@fluentui/react_v0.2.0",
"version": "0.2.0",
"comments": {
"minor": [
{
"comment": "add demo slider using native framework",
"author": "jasonmo@microsoft.com",
"commit": "2fc8f234d78ca812f9420e1a3c66c515bcddf23c"
}
]
}
},
{
"date": "Sat, 09 Nov 2019 05:10:48 GMT",
"tag": "@fluentui/react_v0.1.1",
"version": "0.1.1",
"comments": {
"none": [
{
"comment": "part 1 of integrating flamegrill",
"author": "kchau@microsoft.com",
"commit": "dd7c427564e8c56b8d6bb8f93b2f11d60ef65169"
}
]
}
},
{
"date": "Thu, 07 Nov 2019 21:42:52 GMT",
"tag": "@fluentui/react_v0.1.1",
"version": "0.1.1",
"comments": {
"none": [
{
"comment": "adds scenario tests via jest-puppeteer",
"author": "kchau@microsoft.com",
"commit": "4b1bee49b05777a13b8dc0432feb4c23123f32d9"
}
]
}
},
{
"date": "Wed, 06 Nov 2019 18:35:34 GMT",
"tag": "@fluentui/react_v0.1.1",
"version": "0.1.1",
"comments": {
"patch": [
{
"comment": "Adding example Teams theme to slider story; temporarily. It will move out to a more official place separately.",
"author": "dzearing@microsoft.com",
"commit": "26c730cd5b123b463828e5fc9cd95e451a3fba0f"
}
]
}
},
{
"date": "Wed, 30 Oct 2019 17:20:18 GMT",
"tag": "@fluentui/react_v0.1.0",
"version": "0.1.0",
"comments": {
"minor": [
{
"comment": "Tokens from theme",
"author": "jdh@microsoft.com",
"commit": "b7d68b1fd4d2baf4b9ddee520e3de678baedfe4e"
}
]
}
},
{
"date": "Fri, 25 Oct 2019 19:36:25 GMT",
"tag": "@fluentui/react_v0.0.1",
"version": "0.0.1",
"comments": {
"patch": [
{
"comment": "Gets rid of webpack resolve alias tools",
"author": "kchau@microsoft.com",
"commit": "90ec1264e955cf2e1d70e9da79dc4481ca42e875"
}
]
}
}
]
}

Просмотреть файл

@ -0,0 +1,52 @@
# Change Log - @fluentui/react
This log was last generated on Fri, 22 Nov 2019 00:35:10 GMT and should not be manually modified.
## 0.3.2
Fri, 22 Nov 2019 00:35:10 GMT
### Patches
- Icon: Move README spec to react package. (Humberto.Morimoto@microsoft.com)
## 0.3.1
Thu, 21 Nov 2019 00:45:42 GMT
### Patches
- update native controls to use new packages from @uifabricshared (jasonmo@microsoft.com)
## 0.3.0
Tue, 19 Nov 2019 21:24:06 GMT
### Minor changes
- Link: Building first version of Fluent Link using composed. (Humberto.Morimoto@microsoft.com)
## 0.2.1
Tue, 12 Nov 2019 17:40:53 GMT
### Patches
- e2e runs do not need a flamegraph reporter (kchau@microsoft.com)
## 0.2.0
Mon, 11 Nov 2019 23:53:02 GMT
### Minor changes
- add demo slider using native framework (jasonmo@microsoft.com)
## 0.1.1
Wed, 06 Nov 2019 18:35:34 GMT
### Patches
- Adding example Teams theme to slider story; temporarily. It will move out to a more official place separately. (dzearing@microsoft.com)
## 0.1.0
Wed, 30 Oct 2019 17:20:18 GMT
### Minor changes
- Tokens from theme (jdh@microsoft.com)
## 0.0.1
Fri, 25 Oct 2019 19:36:25 GMT
### Patches
- Gets rid of webpack resolve alias tools (kchau@microsoft.com)

Просмотреть файл

@ -0,0 +1,11 @@
# `react-mybrand`
> TODO: description
## Usage
```
const reactMybrand = require('react-mybrand');
// TODO: DEMONSTRATE API
```

Просмотреть файл

@ -0,0 +1,3 @@
{
"extends": "@fluentui/scripts/config/api-extractor/api-extractor.common.json"
}

Просмотреть файл

@ -0,0 +1,24 @@
## API Report File for "@fluentui/react"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { ITheme } from '@fluentui/react-theming';
// @public (undocumented)
export const Slider: {
(props: import("@fluentui/base").ISliderProps): any;
propTypes: import("react").WeakValidationMap<import("@fluentui/base").ISliderProps> | undefined;
__optionsSet: any[];
__directRender: any;
displayName: any;
};
// @public (undocumented)
export const theme: Partial<ITheme>;
// (No @packageDocumentation comment for this package)
```

Просмотреть файл

@ -0,0 +1,3 @@
const config = require('@fluentui/scripts/config/jest/jest.common');
module.exports = config;

Просмотреть файл

@ -0,0 +1 @@
module.exports = require('@fluentui/scripts/config/jest/jest.puppeteer');

Просмотреть файл

@ -0,0 +1 @@
import '@fluentui/scripts/tasks/preset';

Просмотреть файл

@ -0,0 +1,58 @@
{
"name": "@fluentui/playground",
"version": "0.41.0",
"description": "> TODO: description",
"homepage": "https://github.com/microsoft/fluent-ui#readme",
"bugs": {
"url": "https://github.com/microsoft/fluent-ui/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/microsoft/fluent-ui.git"
},
"license": "ISC",
"author": "JD Huntington <jdh@microsoft.com>",
"files": [
"lib"
],
"main": "src/index.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"scripts": {
"build": "just-scripts build",
"clean": "just-scripts clean",
"e2e": "just-scripts e2e",
"e2e:watch": "just-scripts e2e:watch",
"just": "just-scripts",
"lint": "just-scripts lint",
"perf": "just-scripts e2e:perf",
"start": "just-scripts storybook:start",
"start-test": "just-scripts test:watch",
"test": "just-scripts test",
"update-snapshots": "just-scripts jest:snapshots"
},
"dependencies": {
"@fluentui/react-theming": "^0.3.1",
"@uifabricshared/foundation-composable": "0.5.0",
"@uifabricshared/foundation-settings": "0.4.0",
"classnames": "^2.2.6"
},
"devDependencies": {
"@fluentui/scripts": "0.41.0",
"@types/enzyme": "^3.10.3",
"@types/enzyme-adapter-react-16": "1.0.5",
"@types/jest": "^24.0.19",
"@types/node": "^10.3.2",
"@types/react": "^16.8.10",
"@types/storybook__addon-info": "^5.2.1",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.15.1",
"enzyme-to-json": "^3.3.4",
"react": "^16.8.0"
},
"peerDependencies": {
"react": "^16.8.0"
}
}

Просмотреть файл

@ -0,0 +1,25 @@
import React from 'react';
import { ForwardRefComponent } from '@fluentui/react-theming';
import { IButtonProps, IButtonSlots } from './Button.types';
import { useButton } from './useButton';
export const ButtonBase: ForwardRefComponent<IButtonProps, HTMLElement> = React.forwardRef(
(props: IButtonProps, componentRef: React.Ref<HTMLElement>) => {
const { children, href, slots } = props;
const {
root: Root = href ? 'a' : 'button',
startIcon: StartIcon = 'span',
endIcon: EndIcon = 'span',
} = slots || ({} as IButtonSlots);
const { slotProps = {} } = useButton({ ...props, componentRef });
return (
<Root {...slotProps.root}>
<StartIcon {...slotProps.startIcon} />
{children}
<EndIcon {...slotProps.endIcon} />
</Root>
);
},
);

Просмотреть файл

@ -0,0 +1,20 @@
import React from 'react';
import { ButtonBase } from './Button.base';
export default {
component: 'Button',
title: 'Base Button',
};
const _onClick = () => {
// eslint-disable-next-line no-console
console.log('Button was clicked');
};
export const baseButton = () => (
<ButtonBase onClick={_onClick}>This renders as a button</ButtonBase>
);
export const baseButtonWithHref = () => (
<ButtonBase href="https://www.bing.com">This renders as a link</ButtonBase>
);

Просмотреть файл

@ -0,0 +1,33 @@
import React from 'react';
import { mount } from 'enzyme';
import { ButtonBase } from './Button.base';
describe('ButtonBase', () => {
it('focuses correctly when focus is triggered via IButton interface', () => {
const button1 = React.createRef<HTMLElement>();
const button2 = React.createRef<HTMLElement>();
const button3 = React.createRef<HTMLElement>();
const wrapper = mount(
<div>
<ButtonBase ref={button1}>Button 1</ButtonBase>
<ButtonBase ref={button2}>Button 2</ButtonBase>
<ButtonBase ref={button3}>Button 3</ButtonBase>
</div>,
);
const buttons = wrapper.getDOMNode().querySelectorAll('button') as NodeListOf<
HTMLButtonElement
>;
expect(buttons.length).toEqual(3);
button1.current!.focus();
expect(document.activeElement!).toBe(buttons[0]);
button2.current!.focus();
expect(document.activeElement!).toBe(buttons[1]);
button3.current!.focus();
expect(document.activeElement!).toBe(buttons[2]);
});
});

Просмотреть файл

@ -0,0 +1,30 @@
import { IClasses, ISlotProps, ISlottableProps } from '@fluentui/react-theming';
export interface IButtonSlots {
/** Intended to contain the icon that appears after the specified children. */
endIcon: React.ReactType;
/** Intended to contain the button. */
root: React.ReactType;
/** Intended to contain the icon that appears before the specified children. */
startIcon: React.ReactType;
}
export type IButtonSlotProps = ISlotProps<IButtonSlots>;
export interface IButtonClasses extends IClasses<IButtonSlots> {}
export interface IButtonProps extends ISlottableProps<IButtonSlots, IButtonClasses> {
/** Defines the children of the Button component. */
children?: React.ReactNode;
/** Defines whether the Button is in an enabled or disabled state. */
disabled?: boolean;
/** Defines an href that, if provided, will make the Button render as an anchor. */
href?: string;
/** Defines a callback that is triggered when the Button is clicked. */
onClick?: (ev: MouseEvent) => void;
}

Просмотреть файл

@ -0,0 +1,3 @@
export { ButtonBase } from './Button.base';
export { IButtonProps, IButtonSlotProps, IButtonSlots } from './Button.types';
export { useButton } from './useButton';

Просмотреть файл

@ -0,0 +1,59 @@
import React from 'react';
import { mergeSlotProps, IStateProps } from '@fluentui/react-theming';
import { IButtonProps } from './Button.types';
export interface IButtonState {
onClick: (ev: MouseEvent) => void;
rootRef: React.Ref<Element>;
}
const useButtonState = (userProps: IStateProps<IButtonProps>): IButtonState => {
const { componentRef, disabled, onClick } = userProps;
const rootRef = React.useRef<HTMLElement>(null);
React.useImperativeHandle(componentRef, () => ({
focus: () => {
rootRef.current && rootRef.current.focus();
},
}));
const onButtonClick = (ev: MouseEvent) => {
if (!disabled && onClick) {
onClick(ev);
if (ev.defaultPrevented) {
return;
}
}
};
return {
onClick: onButtonClick,
rootRef,
};
};
export const useButton = (props: IStateProps<IButtonProps>) => {
const { disabled, href } = props;
const state = useButtonState(props);
const { onClick, rootRef } = state;
const slotProps = mergeSlotProps(props, {
endIcon: {},
root: {
'aria-disabled': disabled,
href,
onClick,
ref: rootRef,
role: 'button',
type: href ? 'link' : 'button',
},
startIcon: {},
});
return {
slotProps,
state,
};
};

Просмотреть файл

@ -0,0 +1,19 @@
import * as React from 'react';
import { ICheckboxProps, ICheckboxSlots } from './Checkbox.types';
import { useCheckbox } from './useCheckbox';
export const CheckboxBase: React.FunctionComponent<ICheckboxProps> = (props: ICheckboxProps) => {
const { children, slots } = props;
const { root: Root = 'label', input: Input = 'input', icon: Icon = 'i' } = (slots ||
{}) as ICheckboxSlots;
const { slotProps = {} } = useCheckbox(props);
return (
<Root {...slotProps.root}>
<Input {...slotProps.input} />
<Icon {...slotProps.icon} />
{children}
</Root>
);
};

Просмотреть файл

@ -0,0 +1,18 @@
import React from 'react';
import { CheckboxBase } from './Checkbox.base';
export default {
component: 'Checkbox',
title: 'Base Checkbox',
};
const _onChange = () => {
// eslint-disable-next-line no-console
console.log('Checkbox was clicked.');
};
export const baseCheckbox = () => (
<CheckboxBase defaultChecked={true} onChange={_onChange}>
This renders as a checkbox
</CheckboxBase>
);

Просмотреть файл

@ -0,0 +1,33 @@
import { IClasses, ISlotProps, ISlottableProps } from '@fluentui/react-theming';
export interface ICheckboxSlots {
/** Intended to contain the Checkbox. */
root: React.ReactType;
/** Custom icon that defines the checkmark rendered by the checkbox. */
icon: React.ReactType;
/** The input element that represents the actual checkbox. */
input: React.ReactType;
}
export type ICheckboxSlotProps = ISlotProps<ICheckboxSlots>;
export interface ICheckboxClasses extends IClasses<ICheckboxSlots> {}
export interface ICheckboxProps extends ISlottableProps<ICheckboxSlotProps, ICheckboxClasses> {
/** Defines whether default value of the checkbox is checked or unchecked. (Controlled) */
checked?: boolean;
/** Defines the children of the Checkbox component. */
children?: React.ReactNode;
/** Defines the default value of the checkbox for uncontrolled scenarios. */
defaultChecked?: boolean;
/** Defines whether the Checkbox is in an enabled or disabled state. */
disabled?: boolean;
/** Defines a callback that is triggered when the Checkbox is toggled. */
onChange?: (ev: MouseEvent, checked: boolean) => void;
}

Просмотреть файл

@ -0,0 +1,388 @@
# Checkbox component specification
The `Checkbox` component allows a user to choose between two mutually exclusive options.
## Related variant considerations
- Toggle: should be a separate component because it has different anatomy & warrants unique themability.
- Indeterminate/Tri State: should support groups with hierarchical checkboxes where the parent checkbox can be in a mixed state if its children checkboxes aren't all checked (and if they are all checked, then the parent is checked; same if they are all unchecked, then the parent is unchecked).
## Reference implementations
https://codesandbox.io/s/checkboxes-ggpx1
Note about the Stardust example: there's some weirdness with how the theme providers are interacting with each other, the Stardust checkbox's styling is messing up as a result.
Fabric Checkbox [docs](https://developer.microsoft.com/en-us/fabric#/controls/web/Checkbox)
Stardust Checkbox [docs](https://microsoft.github.io/fluent-ui-react/components/checkbox/definition)
Material UI Checkbox [docs](https://material-ui.com/components/checkboxes/)
BaseUI Checkbox [docs](https://baseweb.design/components/Checkbox/)
Chakra Checkbox [docs](https://chakra-ui.com/Checkbox)
Cabon Checkbox [docs](https://www.carbondesignsystem.com/components/checkbox/code)
AntD Checkbox [docs](https://ant.design/components/Checkbox/)
FastDNA Checkbox [docs](https://explore.fast.design/components/Checkbox)
## Props
> TODO: Consult the prop wizard to derive consistently defined props.
| Name | Type | Default value |
| ---- | ---- | ------------- |
### Recommended props
| Name | Type |
| -------------- | ----------------------------------------------------------- |
| ariaDescribedBy | string |
| ariaLabel | string |
| ariaLabelledBy | string |
| as | React.ElementType |
| checked | boolean |
| className | string |
| defaultChecked | boolean |
| defaultIndeterminate | boolean |
| disabled | boolean |
| indeterminate | boolean |
| label | string |
| name | string |
| onChange | (ev: Event, value: boolean) => void |
| vertical | boolean |
Note: rtl, styles, and theme come from compose or the ThemeProvider. And name has been added to support checkbox in form scenarios.
Removing the following two props because the ARIA spec dictates role='checkbox' doesn't need aria-posinset and aria-setsize. These are only valid for role='option' which is only in the case the checkbox is a part of a listbox, which is not something we need to account for in the base component API. If the user does need to provide these two props, slotProps could be used to apply additional props to any slot.
| Name | Concern |
| ------------------------------------- | ----------------------------------------------------------------- |
| ariaPositionInset | if checkbox is in a set, should be up to the user to provide a11y |
| ariaSetSize | same as above |
### Fabric Checkbox props
https://developer.microsoft.com/en-us/fabric#/controls/web/checkbox
| Name | Type | Notes |
| -------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------|
| ariaLabel | string | |
| ariaDescribedBy | string | |
| ariaLabelledBy | string | |
| ariaPositionInset | number | |
| ariaSetSize | number | |
| boxSide | 'start' or 'end' | default 'start' |
| checked | boolean | |
| checkmarkIconProps | IIconProps | |
| className | string | |
| componentRef | IRefObject<ICheckbox> | |
| defaultChecked | boolean | |
| defaultIndeterminate | boolean | |
| disabled | boolean | |
| indeterminate | boolean | |
| inputProps | React.ButtonHTMLAttributes<HTMLElement or HTMLButtonElement>| |
| keytipProps | IKpeytipProps | |
| label | string | |
| onChange | (ev, checked) => void | |
| onRenderLabel | IRenderFunction<ICheckboxProps> | |
| styles | IStyleFunctionOrObject<ICheckboxStyleProps, ICheckboxStyles>| |
| theme | ITheme | |
### Stardust Checkbox props
| Name | Type | Notes |
| -------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------|
| animation | AnimationProp | |
| as | React.ElementType | default type is "div" |
| className | string | |
| content | ReactNode | |
| design | ComponentDesign | |
| disableAnimations | boolean | default false |
| overwrite | boolean | default false |
| renderer | Renderer | |
| rtl | boolean | default false |
| styles | ComponentSlotStyle | |
| target | Document | |
| theme | ThemeInput | |
| variables | any | |
### Differences of Fabric/Stardust to resolve
| Name | Type | Notes |
| -------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------|
| animation | AnimationProp | |
| disableAnimations | boolean | default false |
| overwrite | boolean | default false |
| renderer | Renderer | |
| variables | any | |
| target | Document | |
| content | ReactNode | |
| ariaPositionInSet | number | if checkbox is in a set, should be up to the user to provide a11y |
| ariaSetSize | number | |
### Conversion process from Fabric 7 to Fluent UI Checkbox
Props being changed:
Some props, like style & className, should always go into the root or to the applicable element like name into the box element (replacing input).
Data-attributes will be spread using slotProps.
Props being removed:
ariaPoisitionInSet and ariaSetSize - when writing parent component, user should set these on the checkbox.
animations: remnant pattern of semantic UI, don't need animations for checkbox theming
defaultChecked: overloading with checked - can just set default value of checked.
## Slots
| Name | Considerations |
| --------- | ------------------------------------------------------ |
| root | label |
| input | actual checkbox element - what gets checked/unchecked |
| icon | visual checkmark, sideways if indeterminate |
| box | wraps input & icon - what actual gets the styling |
## DOM structure
General considerations:
Only use as toggle between two mutually exclusive options (binary) or in a group with shared context to offer multiple options.
Uncontrolled vs. controlled: implemented in the prototype already through useControlledState React hook.
Indeterminate state: When children checkboxes aren't checked, don't check parent checkbox.
Could consider supporting an invalid state/error state but this might just be supported via styling that's passed in by the user and done through compose.
### Recommended DOM
```html
<label class="checkbox-root">
<div class="box">
<input class="checkbox" role="checkbox" aria-checked"false" aria-label="Fluent checkbox">
<i class="icon"></i>
</div>
</label>
```
### Fabric Checkbox example DOM
```html
<div class="ms-Checkbox-checkbox">
<input type="checkbox" class="input-226" id="checkbox-268" aria-label="Unchecked checkbox (uncontrolled)" aria-checked="false">
<label class="ms-Checkbox-label label-227" for="checkbox-268">
<div class="ms-Checkbox-checkbox checkbox-228">
<i data-icon-name="CheckMark" aria-hidden="true" class="ms-Checkbox-checkmark checkmark-231">
</i>
</div>
<span aria-hidden="true" class="ms-Checkbox-text text-230">Unchecked checkbox (uncontrolled)</span>
</label>
</div>
```
### Stardust Checkbox example DOM
```html
<div class="ui-checkbox dd ol om gz de nb on cd oo op cb oq ha hb hc hd he hf hg hh hi hj hk hl hm hn ho hp or os ot ou hu hv hw hx ov ow ox oy ic id ie if ig ih ii ij ik il im oz pa pb pc ir is it iu pd pe pf pg lu ph pi pj pk" aria-checked="false" role="checkbox" tabindex="0">
<span class="ui-icon ck cb ca jm pl pm pn po pp pq pr ba bb bc bd do dp jy jz ps pt pu pv pw px gu jo gw py pz qa qb ui-checkbox__indicator" role="img" aria-hidden="true">
<svg role="presentation" focusable="false" viewBox="8 8 16 16" class="cz ql qm da cw">
<g>
<path class="ui-icon__outline cy" d="M14.3 21.3c-.1 0-.3 0-.4-.1l-4.8-4.8c-.2-.2-.2-.5 0-.7s.5-.2.7 0l4.4 4.4 7.9-7.9c.2-.2.5-.2.7 0s.2.5 0 .7l-8.3 8.3s-.1.1-.2.1z">
</path>
<path class="ui-icon__filled" d="M23.5 11.875a.968.968 0 0 1-.289.711l-8.25 8.25c-.192.193-.43.289-.711.289s-.519-.096-.711-.289l-4.75-4.75a.965.965 0 0 1-.289-.711c0-.125.027-.25.082-.375s.129-.234.223-.328a.953.953 0 0 1 .695-.297c.135 0 .266.025.391.074.125.05.231.121.32.215l4.039 4.047 7.539-7.547a.886.886 0 0 1 .32-.215c.125-.049.255-.074.391-.074a1.004 1.004 0 0 1 .922.625.97.97 0 0 1 .078.375z">
</path>
</g>
</svg>
</span>
<span class="ui-text cz qk ui-checkbox__label" dir="auto">Make my profile visible</span>
</div>
```
### MUI Checkbox example DOM
```html
<label class="MuiFormControlLabel-root">
<span class="MuiButtonBase-root MuiIconButton-root jss264 MuiCheckbox-root MuiCheckbox-colorSecondary jss265 Mui-checked MuiIconButton-colorSecondary" aria-disabled="false">
<span class="MuiIconButton-label">
<input type="checkbox" class="jss267" value="checkedA" data-indeterminate="false" checked="">
<svg class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" role="presentation">
<path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z">
</path>
</svg>
</span>
<span class="MuiTouchRipple-root"></span>
</span>
<span class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1">Secondary</span>
</label>
```
### Behaviors
Aria spec: https://www.w3.org/TR/wai-aria-practices-1.1/#checkbox
https://www.w3.org/TR/wai-aria-practices/#checkbox
Fluent UI HIG: https://microsoft.sharepoint-df.com/:w:/t/OPGUXLeads/EbBiGJ-gLPFGszdhSxb8X5IBFik0ax7wZLJc8FlDXOwDYA?e=Cy4Er3
### Disabled state
Use `aria-disabled`. Screenreaders should let users know of the existence of the checkbox but it should be read-only. Ignore all events & no change to `checked` value allowed.
### Checked state
`aria-checked` indicates whether element is checked (`true`) or unchecked (`false`) but can also be `mixed` which represents a tri-state (indeterminate) input in a situation with a group of other elements that have a mixture of checked and unchecked values.
### Indeterminate state
Mixed state checkbox represents a checkbox that can support a partially checked state. If none of the checkboxes in a set are checked, the mixed state checkbox isn't checked (and if all are checked then so is the mixed state) but if the set contains a mix of checked and unchecked boxes, then the tri-state is appropriate so `aria-checked` will be set to `mixed`. Here's an example: https://www.w3.org/TR/wai-aria-practices/examples/checkbox/checkbox-2/checkbox-2.html
### Focus indicators
Focus indicators should not show in mouse or touch interaction; they should only appear when keyboard tabbing/directional keystrokes are pressed, and should disappear when mouse/touch interactions occur.
### Keyboarding
| Key | Description |
| --------- | -------------------------------------------------------------------- |
| Tab | Moves keyboard focus to the checkbox. |
| Space | Toggles checkbox between checked and unchecked states. |
### Mouse input
- `mouseenter` should change the styling of checkbox to hovered state (preview what it looks like to be toggled but not full styling - checkmark, but not background color for example as in current Fabric 7 checkbox).
- `mouseleave` should change the styling of checkbox back to non-hovered state (so remove the preview of checked state)
- `mousedown` toggle state
- `mouseup` apply styling of new state
### Touch
Same behavior as above except no preview of toggled state through hover.
### Screenreader accessibility:
#### `root`:
- should render the native element using the `as` prop, defaulting to `div`
- should mix in native props expected for the element type defined in `as`.
Input slot: role should be set to `checkbox`
A visible label referenced by the value of `aria-labelledby` (id of element containing the label) set on the element with role `checkbox`.
If there's additional static text representing that is descriptive, `aria-describedby` should be set to id of element containing the description.
`aria-label` set on the element with role `checkbox`.
### Accessibility concerns for the user
`aria-label`, `aria-labelledby`: Describe what is the purpose of the checkbox, latter points to id of element with former.
### Themability and customization
Both Fluent and Teams themes and other custom themes will be made with compose and the design tokens specified below. Screenshots of themed variants will be posted here soon after that work is done like the example code below.
The `Checkbox` uses `react-texture` to provide a recomposable implementation that has no runtime performance penalties. The `BaseCheckbox` implementation can be used to provide new `slots` and default `props`:
```tsx
const FooCheckbox = BaseCheckbox.compose({
tokens: {},
styles: {},
slots: {}
});
render() {
<FooCheckbox defaultChecked={true} onChange={console.log("checkbox clicked!")}>
This renders as a checkbox
</FooCheckbox >
}
```
### Composition
1 per slot
1 per state, tagged on root
### Component design tokens
> Tokens represent the general look and feel of the various visual slots. Tokens feed into the styling at the right times in the right slot.
>
> Regarding naming conventions, use a camelCased name following this format:
> `{slot}{property}{state (or none for default)}`. For example: `labelSizeHovered`.
>
> Common property names: `size`, `background`, `color`, `borderRadius`
>
> Common states: `hovered`, `pressed`, `focused`, `checked`, `checkedHovered`, `disabled`
| Name | Considerations |
| ------------------ | -------------- |
| boxBorderColor | |
| boxBorderRadius | |
| boxBorderWidth | |
| boxColor | |
| boxColorDisabled | |
| boxColorFocused | |
| boxColorHovered | |
| boxColorPressed | |
| boxSize | |
| labelColor | |
| labelColorDisabled | |
| labelColorFocused | |
| labelColorHovered | |
| labelColorPressed | |
| labelSize | |
| iconColor | |
| iconColorDisabled | |
| iconColorFocused | |
| iconColorHovered | |
| iconColorPressed | |
| iconSize | |
NOTE! Stardust does not follow this convention. Their Checkbox currently uses these tokens:
```
background: string
disabledBackground: string
disabledBackgroundChecked: string
toggleBackground: string
toggleBorderColor: string
toggleIndicatorColor: string
toggleIndicatorSize: string
checkedBackground: string
checkedBorderColor: string
checkedBackgroundHover: string
checkedIndicatorColor: string
checkboxCheckedColor: string
checkboxToggleCheckedBackground: string
disabledToggleBackground: string
gap: string
borderColor: string
borderColorHover: string
checkboxColor: string
checkboxToggleCheckedBorderColor: string
checkedTextColor: string
disabledColor: string
disabledBorderColor: string
disabledToggleBorderColor: string
disabledCheckboxColor: string
disabledToggleIndicatorColor: string
disabledCheckedIndicatorColor: string
textColor: string
textColorHover: string
indicatorColor: string
```
## Considerations for different screen sizes
Won't really look or behave differently in context of different phone/tablet/desktop sizes - as in different sizes would not cause this component to look or behave differently.
## Use cases
The `Checkbox` component may be used within a `Form` component by providing the name prop to indicate the name of the input element to be fed into the form action. Example: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox
## Compatibility with other libraries
> TODO: If this component represents a selected value, how will that be used in an HTML form? Is there a code example to illustrate?
> TODO: Is it possible this component could be rendered in a focus zone? If so, should the focus model change in that case?

Просмотреть файл

@ -0,0 +1,3 @@
export { CheckboxBase } from './Checkbox.base';
export { ICheckboxProps, ICheckboxSlotProps, ICheckboxSlots } from './Checkbox.types';
export { useCheckbox } from './useCheckbox';

Просмотреть файл

@ -0,0 +1,62 @@
import * as React from 'react';
import { useControlledState } from '../../hooks/useControlledState';
import { mergeSlotProps } from '@fluentui/react-theming';
import { ICheckboxProps } from './Checkbox.types';
export interface ICheckboxState {
onClick: (ev: MouseEvent, checked: boolean) => void;
isChecked: boolean;
rootRef: React.Ref<Element>;
}
const useCheckboxState = (userProps: ICheckboxProps): ICheckboxState => {
const { checked: controlledValue, defaultChecked, disabled, onChange } = userProps;
const [isChecked, setChecked] = useControlledState(controlledValue, defaultChecked);
const rootRef = React.useRef<HTMLElement>(null);
const onCheckboxChange = (ev: MouseEvent, checked: boolean) => {
if (!disabled && onChange) {
onChange(ev, !isChecked);
setChecked(!isChecked);
if (ev.defaultPrevented) {
return;
}
}
};
return {
onClick: onCheckboxChange,
isChecked,
rootRef,
};
};
export const useCheckbox = (props: ICheckboxProps) => {
const { disabled } = props;
const state = useCheckboxState(props);
const { rootRef, onClick, isChecked } = state;
const slotProps = mergeSlotProps(props, {
root: {
ref: rootRef,
},
icon: {},
input: {
'aria-disabled': disabled,
'aria-checked': isChecked,
onClick,
ref: rootRef,
role: 'checkbox',
},
});
return {
slotProps,
state,
};
};

Просмотреть файл

@ -0,0 +1,506 @@
# Icon component specification
The `Icon` component provides a symbol, graphic or image that represents an application, a capability, or some other concept or specific entity with meaning for the user.
## Related variant considerations
The following section documents variants of the component that currently exist in Fabric and identifies variants that exist in other component libraries but don't currently exist in Fabric, documenting which component libraries have those variants.
### Variants existing in Fabric today
- `Font icons`
- `Image icons`
### Variants not in Fabric but that exist in other component libraries
- `Block icons`
- In Gestalt
- `Bordered icons`
- In Semantic UI
- In Stardust
- `Circular icons`
- In Semantic UI
- In Stardust
- `Corner icons`
- In Semantic UI
- `Focusable icons`
- In Chakra UI
- `Group icons`
- In Semantic UI
- `Inverted icons`
- In Semantic UI
- `Outlined vs filled icons via props`
- In Ant Design
- `Rotated icons`
- In Ant Design
- In Semantic UI
- In Stardust
- `Spinning/Loading icons`
- In Ant Design
- In Semantic UI
- `Two tone icons`
- In Ant Design
- In Atlaskit
## Reference implementations
The following section documents links to different UI libraries implementations of Icons, while also providing a code sandbox with a side by side implementation of them for comparison.
- [Side-by-side implementations](https://codesandbox.io/s/icon-implementations-4d5gp)
- [Ant Design Icon docs](https://ant.design/components/icon/)
- [Atlaskit Icon docs](https://atlaskit.atlassian.com/packages/core/icon)
- [Base Web Icon docs](https://baseweb.design/components/icon/)
- [Chakra UI Icon docs](https://chakra-ui.com/icon)
- [Elemental UI Icon docs](http://elemental-ui.com/forms)
- Look for the `Icons` section in the `Forms` page
- [Fabric Icon docs](https://developer.microsoft.com/en-us/fabric#/controls/web/icon)
- [Gestalt Icon docs](https://pinterest.github.io/gestalt/?ref=designrevision.com#/Icon)
- [Material-UI Icon docs](https://material-ui.com/components/icons/)
- [Semantic UI Icon docs](https://react.semantic-ui.com/elements/icon/)
- [Stardust Icon docs](https://microsoft.github.io/fluent-ui-react/components/icon/definition)
## Props
The following section documents the properties that will become part of the new component, as well as the process for mitigating all changes when moving from Fabric and Stardust to Fluent UI.
> TODO: Consult the prop wizard to derive consistently defined props.
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
### Recommended component props
| Name | Type | Default value | Description |
| ---------------- | :-------: | :-----------: | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `ariaHidden` | `boolean` | `false` | Indicates whether the element is exposed to an accessibility API. |
| `ariaLabel` | `string` | | Defines a string value that labels the current element. |
| `ariaLabelledby` | `string` | | Identifies the element (or elements) that labels the current element. |
| `as` | `string` | | Defines a component that should be used as the root element of the `Icon`. |
| `className` | `string` | | Defines an additional classname to provide on the root of the `Icon`. |
| `name` | `string` | | Defines the name of the pre-registered `Icon` to use. If the string is empty, a placeholder blank space of the same width will be rendered. |
| `role` | `string` | | Defines the accessibility role of the `Icon`. |
| `title` | `string` | | Specifies extra information about the `Icon`. |
### Props to be discussed
None at the moment.
### Fabric Icon props
https://developer.microsoft.com/en-us/fabric#/controls/web/icon
#### IIconProps interface
| Name | Type | Notes |
| -------------- | :----------------------------------------------------: | -------------------------------------------------------------------------------- |
| `ariaLabel` | `string` | |
| `iconName` | `string` | Should we rename this to just be `name`? |
| `iconType` | `IconType` | Already deprecated. |
| `imageErrorAs` | `React.ComponentType<IImageProps>` | Should be removed duo to unnecessary complexity and infrequent use. |
| `imageProps` | `IImageProps` | Should not be part of base `Icon`. Should be considered for `ImageIcon` variant. |
| `styles` | `IStyleFunctionOrObject<IIconStyleProps, IIconStyles>` | Should be deprecated in favor of recomposition. |
| `theme` | `ITheme` | Should not show up in the public props contract. |
### Stardust Icon props
#### IconProps interface
| Name | Type | Notes |
| --------------- | :-------------: | --------------------------------------------------------------------------------------------------------- |
| `accessibility` | `Accessibility` | Why would a user need this prop? |
| `bordered` | `boolean` | Should this prop be provided or is this just a matter that could be solved via styling and recomposition? |
| `circular` | `boolean` | Should this prop be provided or is this just a matter that could be solved via styling and recomposition? |
| `disabled` | `boolean` | Does it make sense for an `Icon` to be `disabled` if it is not an interactive component. |
| `name` | `string` | |
| `outline` | `boolean` | Should this prop be provided or is this just a matter that could be solved via styling and recomposition? |
| `rotate` | `number` | Should this prop be provided or is this just a matter that could be solved via styling and recomposition? |
| `size` | `SizeValue` | Should this prop be provided or is this just a matter that could be solved via styling and recomposition? |
| `xSpacing` | `IconXSpacing` | Should this prop be provided or is this just a matter that could be solved via styling and recomposition? |
### Conversion process from Fabric 7 to Fluent UI Icon
#### IIconProps interface
| Name | Action to take/taken | Property transitioned? | Breaking change? | Codemod/Shim created? |
| -------------- | ------------------------------------- | :--------------------: | :-------------------------------------: | :-------------------: |
| `ariaLabel` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `iconName` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `iconType` | Removing as it is already deprecated. | &#9745; | No, because prop is already deprecated. | &#x274C; |
| `imageErrorAs` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `imageProps` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `styles` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `theme` | TBD | &#x274C; | &#x274C; | &#x274C; |
### Conversion process from Stardust to Fluent UI Icon
#### IconProps interface
| Name | Action to take/taken | Property transitioned? | Breaking change? | Codemod/Shim created? |
| --------------- | ------------------------------------- | :--------------------: | :--------------: | :-------------------: |
| `accessibility` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `bordered` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `circular` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `disabled` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `name` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `outline` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `rotate` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `size` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `xSpacing` | TBD | &#x274C; | &#x274C; | &#x274C; |
## DOM Structure
The following section documents the DOM structure for the component from different component library examples and then suggests a recommended DOM taking into consideration common patterns between the libraries reviewed.
### Ant Design Icon
#### Example DOM
```html
<i aria-label="icon: home" class="anticon anticon-home">
<svg viewBox="64 64 896 896" focusable="false" class="" data-icon="home" width="1em" height="1em" fill="currentColor" aria-hidden="true">
<path d="M946.5 505L560.1 118.8l-25.9-25.9a31.5 31.5 0 0 0-44.4 0L77.5 505a63.9 63.9 0 0 0-18.8 46c.4 35.2 29.7 63.3 64.9 63.3h42.5V940h691.8V614.3h43.4c17.1 0 33.2-6.7 45.3-18.8a63.6 63.6 0 0 0 18.7-45.3c0-17-6.7-33.1-18.8-45.2zM568 868H456V664h112v204zm217.9-325.7V868H632V640c0-22.1-17.9-40-40-40H432c-22.1 0-40 17.9-40 40v228H238.1V542.3h-96l370-369.7 23.1 23.1L882 542.3h-96.1z">
</path>
</svg>
</i>
```
#### Considerations
- Only supports SVG icons.
### Atlaskit Icon
#### Example DOM
```html
<span class="sc-gzVnrw fihEGT" aria-label="HomeIcon">
<svg width="24" height="24" viewBox="0 0 24 24" focusable="false" role="presentation">
<path d="M10 19v-4.5a2 2 0 1 1 4 0V19h4a1 1 0 0 0 1-1v-7.831l-6.293-6.296a1 1 0 0 0-1.414 0L5 10.169V18a1 1 0 0 0 1 1h4zm11-6.83V18a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3v-5.83l-.04.04c-.39.39-1.03.39-1.42 0-.39-.39-.39-1.03 0-1.42l8.339-8.331a3 3 0 0 1 4.242 0l8.339 8.331c.39.39.39 1.03 0 1.42-.39.39-1.03.39-1.42 0l-.04-.04z" fill="currentColor">
</path>
</svg>
</span>
```
#### Considerations
- Only supports SVG icons.
- To use provided icons you need to import built-in icon directly (i.e. `BookIcon`).
### Base Web Icon
#### Example DOM
```html
<svg data-baseweb="icon" viewBox="0 0 24 24" class="by bz c0 k2 kt">
<title>
Arrow Up
</title>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2929 6.29289C11.6834 5.90237 12.3166 5.90237 12.7071 6.29289L16.7071 10.2929C17.0976 10.6834 17.0976 11.3166 16.7071 11.7071C16.3166 12.0976 15.6834 12.0976 15.2929 11.7071L13 9.41421V17C13 17.5523 12.5523 18 12 18C11.4477 18 11 17.5523 11 17V9.41421L8.70711 11.7071C8.31658 12.0976 7.68342 12.0976 7.29289 11.7071C6.90237 11.3166 6.90237 10.6834 7.29289 10.2929L11.2929 6.29289Z">
</path>
</svg>
```
#### Considerations
- Only supports SVG icons.
- To use provided icons you need to import built-in icon directly (i.e. `ArrowUp`).
### Chakra UI Icon
#### Example DOM
```html
<svg viewBox="0 0 14 14" focusable="false" role="presentation" class="css-h7g82p">
<path fill="currentColor" d="M2.20731,0.0127209 C2.1105,-0.0066419 1.99432,-0.00664663 1.91687,0.032079 C0.871279,0.438698 0.212942,1.92964 0.0580392,2.95587 C-0.426031,6.28627 2.20731,9.17133 4.62766,11.0689 C6.77694,12.7534 10.9012,15.5223 13.3409,12.8503 C13.6507,12.5211 14.0186,12.037 13.9993,11.553 C13.9412,10.7397 13.186,10.1588 12.6051,9.71349 C12.1598,9.38432 11.2304,8.47427 10.6495,8.49363 C10.1267,8.51299 9.79754,9.05515 9.46837,9.38432 L8.88748,9.96521 C8.79067,10.062 7.55145,9.24878 7.41591,9.15197 C6.91248,8.8228 6.4284,8.45491 6.00242,8.04829 C5.57644,7.64167 5.18919,7.19632 4.86002,6.73161 C4.7632,6.59607 3.96933,5.41495 4.04678,5.31813 C4.04678,5.31813 4.72448,4.58234 4.91811,4.2919 C5.32473,3.67229 5.63453,3.18822 5.16982,2.45243 C4.99556,2.18135 4.78257,1.96836 4.55021,1.73601 C4.14359,1.34875 3.73698,0.942131 3.27227,0.612963 C3.02055,0.419335 2.59457,0.0708094 2.20731,0.0127209 Z">
</path>
</svg>
```
#### Considerations
- Only supports SVG icons.
- Icons are and can be added as part of the theme.
### Fabric Icon
#### Example DOM
##### Font Icon
```html
<i data-icon-name="CompassNW" role="presentation" aria-hidden="true" class="ms-Icon root-38 css-176 css-144">
</i>
```
##### SVG Icon
```html
<i data-icon-name="onedrive-svg" role="presentation" aria-hidden="true" class="ms-Icon root-38 css-144">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0,0,2048,2048">
<g fill="#1B559B">
<path d="M 1860 1196 q 53 10 94 37 q 18 12 35 29 q 16 16 30 40 q 13 23 21 53 q 8 30 8 68 q 0 37 -10 75 q -11 38 -34 69 q -23 31 -58 51 q -36 20 -86 20 h -1079 q -78 0 -131 -24 q -54 -25 -87 -65 q -34 -40 -49 -91 q -15 -52 -15 -107 q 0 -46 12 -81 q 11 -35 31 -61 q 19 -27 43 -45 q 24 -19 50 -31 q 60 -29 136 -35 q 0 -1 4 -26 q 3 -25 16 -61 q 12 -37 36 -80 q 24 -43 64 -79 q 39 -37 98 -61 q 59 -25 141 -25 q 57 0 103 15 q 45 15 81 38 q 35 23 62 52 q 26 28 44 55 q 18 -10 42 -18 q 20 -7 48 -12 q 27 -6 60 -6 q 40 0 91 15 q 50 14 94 48 q 44 33 75 88 q 30 55 30 136 m -1463 174 q 0 53 10 99 q 10 46 29 86 h -170 q -52 0 -100 -23 q -48 -23 -85 -61 q -37 -38 -59 -87 q -22 -50 -22 -104 q 0 -49 11 -87 q 10 -38 27 -66 q 17 -29 39 -49 q 21 -21 44 -35 q 53 -33 121 -41 q -1 -9 -1 -18 q -1 -9 -1 -17 q 0 -72 27 -134 q 27 -63 73 -110 q 45 -47 106 -74 q 60 -27 127 -27 q 36 0 66 7 q 29 6 51 14 q 25 10 45 21 q 27 -48 65 -89 q 37 -41 84 -71 q 46 -30 101 -47 q 55 -17 115 -17 q 39 0 80 8 q 41 7 83 24 q 72 28 121 71 q 49 42 81 90 q 32 47 49 94 q 16 46 22 82 q -23 2 -43 5 q -21 3 -40 8 q -66 -69 -148 -104 q -82 -36 -177 -36 q -76 0 -136 17 q -60 17 -106 45 q -47 28 -81 64 q -34 36 -58 75 q -24 38 -39 76 q -15 37 -23 67 q -51 12 -102 38 q -52 26 -93 68 q -42 42 -67 101 q -26 59 -26 137">
</path>
</g>
</svg>
</i>
```
##### Image Icon
```html
<div role="presentation" aria-hidden="true" class="ms-Icon root-38 ms-Icon-imageContainer image-40 one-149">
<div class="ms-Image oneImage-266"><img src="https://static2.sharepointonline.com/files/fabric/office-ui-fabric-react-assets/icon-one.png" class="ms-Image-image is-loaded ms-Image-image--portrait is-fadeIn image-264">
</div>
</div>
```
#### Considerations
- Requires icon initialization and uses global registration.
### Gestalt Icon
#### Example DOM
```html
<svg class="gUZ pBj U9O kVc" height="16" width="16" viewBox="0 0 24 24" aria-label="Pin" role="img">
<path d="M18 13.5c0-2.22-1.21-4.15-3-5.19V2.45A2.5 2.5 0 0 0 17 0H7a2.5 2.5 0 0 0 2 2.45v5.86c-1.79 1.04-3 2.97-3 5.19h5v8.46L12 24l1-2.04V13.5h5z">
</path>
</svg>
```
#### Considerations
- Only supports SVG icons.
- Renders by default as a block, and not an inline, element.
### Material-UI Icon
#### Example DOM
##### Font Icon
```html
<span class="material-icons MuiIcon-root" aria-hidden="true">
add_circle
</span>
```
##### SVG Icon
```html
<svg class="MuiSvgIcon-root" focusable="false" viewBox="0 0 24 24" aria-hidden="true" role="presentation">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z">
</path>
</svg>
```
#### Considerations
None.
### Semantic UI Icon
#### Example DOM
```html
<i aria-hidden="true" class="users icon">
::before
</i>
```
#### Considerations
- Only supports font icons.
- Can support icon groups and icon superpositions with the second item in the corner.
### Stardust Icon
#### Example DOM
```html
<span class="ui-icon ck cb ca" role="img" aria-hidden="true">
<svg class="em ct cu en cw" viewBox="8 8 16 16" role="presentation" focusable="false">
<g class="ui-icon__outline eo">
<path d="M23.6968,12.0403c-0.1836-0.0786-0.3975-0.04-0.542,0.0981l-2.5317,2.4165C20.2212,14.9382,20,15.4514,20,16 c0,0.5483,0.2212,1.0615,0.623,1.4448l2.5317,2.4167C23.2495,19.9521,23.374,20,23.5,20c0.0664,0,0.1333-0.0132,0.1968-0.0403 C23.8809,19.8809,24,19.7002,24,19.5v-7C24,12.2998,23.8809,12.1191,23.6968,12.0403z M23,18.3315l-1.6865-1.6099v-0.0002 C21.1113,16.5286,21,16.2725,21,16s0.1113-0.5286,0.3135-0.7217L23,13.6685V18.3315z">
</path>
<path d="M17.5,11H9.8193c-0.7056,0-1.3232,0.5393-1.4692,1.2822C8.1177,13.4619,8,14.7129,8,16s0.1177,2.5381,0.3501,3.7173 C8.4961,20.4607,9.1138,21,9.8193,21H17.5c0.8271,0,1.5-0.6729,1.5-1.5v-7C19,11.6729,18.3271,11,17.5,11z M18,19.5 c0,0.2756-0.2241,0.5-0.5,0.5H9.8193c-0.2285,0-0.4341-0.2-0.4878-0.4756C9.1113,18.4082,9,17.2224,9,16 s0.1113-2.4082,0.3315-3.5249C9.3853,12.2,9.5908,12,9.8193,12H17.5c0.2759,0,0.5,0.2244,0.5,0.5V19.5z">
</path>
</g>
<g class="ui-icon__filled">
<path d="M23.6968,12.0403c-0.1841-0.0786-0.3975-0.04-0.542,0.0981l-2.5317,2.4165C20.2212,14.9382,20,15.4514,20,16 c0,0.5483,0.2212,1.0615,0.623,1.4448l2.5317,2.4167C23.2495,19.9521,23.374,20,23.5,20c0.0664,0,0.1333-0.0132,0.1968-0.0403 C23.8809,19.8809,24,19.7002,24,19.5v-7C24,12.2998,23.8809,12.1191,23.6968,12.0403z">
</path>
<path d="M17.5,11H9.8193c-0.7056,0-1.3232,0.5393-1.4692,1.2822C8.1177,13.4619,8,14.7129,8,16s0.1177,2.5381,0.3501,3.7173 C8.4961,20.4607,9.1138,21,9.8193,21H17.5c0.8271,0,1.5-0.6729,1.5-1.5v-7C19,11.6729,18.3271,11,17.5,11z">
</path>
</g>
</svg>
</span>
```
#### Considerations
- Allows for both font and SVG icons but only has SVG icons built-in.
### Recommended DOM
After looking at all the component libraries above and taking into consideration common patterns the following DOM is recommended.
```html
<span class="root" aria-hidden="true">
{fontIconName}
</span>
```
> TODO: Discuss need to shim back to Fabric with `as=i` because of different tag being used in order to not break styling.
### Slots
From the recommended DOM above we can indicate which slots are going to be required:
| Name | Considerations |
| ----------- | -------------- |
| `root` | |
> TODO: I really think we should have `ImageIcon` as a separate component, maybe via recomposition, to have a very simple and fast base `Icon`.
> TODO: Do we want a specific `SvgIcon` apart from the `ImageIcon` variant? This seems like one of the most used examples.
## Behaviors
Aria spec:
There's no aria spec available for icons.
Fluent UI HIG:
https://microsoft.sharepoint-df.com/:w:/r/teams/OPGUXLeads/_layouts/15/Doc.aspx?sourcedoc=%7B5113018C-05E7-44BF-B6D4-B164755B8D71%7D&file=Icon.docx&action=default&mobileredirect=true
### States
The following section describes the different states in which a `Icon` can be throughout the course of interaction with it.
#### Default state
An `Icon` has only one state, its default state. The `Icon` is not an interactive component and it's used only for representational purposes.
#### States that need discussion
None.
### Keyboard interaction
There is no keyboard interaction that occurs with the `Icon`.
### Cursor interaction
There is no cursor interaction that occurs with the `Icon`.
### Touch interaction
There is no touch interaction that occurs with the `Icon`.
### Screen reader accessibility
#### `root`:
- Should not be tabbable nor focusable and should have `aria-hidden` applied to it by default.
#### Accessibility concerns for the user.
All accessibility concerns would come from user manipulation of the component, so they would be a concern solely for the user and not for the component creator.
## Themability and customization
### Composition
The `Icon` component uses `react-texture` to provide a recomposable implementation that has no runtime performance penalties. The `BaseIcon` implementation can be used to provide new `slots` and default `props` without the application of additional styling:
```tsx
const FooIcon = BaseIcon.compose({
tokens: {},
styles: {},
slots: {}
});
render () {
<FooIcon name="Home" />
}
```
### Icon Registration
#### Fabric
Fabric uses global registration for its icons which needs a call to an initialization function to be used. Below are wiki and code references into this process:
- [Wiki page](https://github.com/OfficeDev/office-ui-fabric-react/wiki/Using-icons)
- [Icon font generation tool](https://uifabricicons.azurewebsites.net)
- [Main `initializeIcons` function](https://github.com/OfficeDev/office-ui-fabric-react/blob/master/packages/icons/src/index.ts)
- [Icon registration utilities](https://github.com/OfficeDev/office-ui-fabric-react/blob/master/packages/styling/src/utilities/icons.ts)
- [Icon component](https://github.com/OfficeDev/office-ui-fabric-react/tree/master/packages/office-ui-fabric-react/src/components/Icon)
#### Stardust
Stardust registers icons in the theme, with all default icons being SVGs but also supporting font icons via:
```ts
type ObjectOrFunc<TResult, TArg = {}> = ((arg: TArg) => TResult) | TResult;
type FontIconSpec = ObjectOrFunc<{
content: string
fontFamily: string
}>;
```
Below are wiki and code references into this process:
- [SVG icon processing](https://github.com/microsoft/fluent-ui-react/blob/21c2f9e3e495b3094e0db4610e9f8834cdc135b0/packages/react/src/themes/teams/components/Icon/svg/ProcessedIcons/stardust-icons.sh#L36)
- [Instructions on adding new SVG Icon](https://github.com/microsoft/fluent-ui-react/pull/585)
- [Font icon registration into the theme (fontAwesome theme example)](https://github.com/microsoft/fluent-ui-react/blob/feat/generate-css/src/themes/teams/components/Icon/fontAwesomeIconStyles.ts)
- [Font vs SVG icon rendering](https://github.com/microsoft/fluent-ui-react/blob/master/packages/react/src/themes/teams/components/Icon/iconStyles.ts)
- [Icon styles as part of theme component styles](https://github.com/microsoft/fluent-ui-react/blob/de10e334fc039370c4fe4b425050327d57f3f515/packages/react/src/themes/teams/componentStyles.ts#L51)
- [Merging icons as part of theme](https://github.com/microsoft/fluent-ui-react/blob/feat/generate-css/src/themes/teams/index.ts)
> - TODO: Decide on recommended thing to do. Leaning towards Stardust approach but worried about perf implications regarding icon definitions.
> - TODO: Discuss how to handle font loading if we put icons in the theme.
> - Should font loading also be part of the theme?
> - Has to be loaded somehow and is ok for the majority of customers to automatically load them, but some customers need to prevent this loading from MSFT CDNs.
## Class names
1 per slot
1 per state, tagged on root
### Component design tokens
> Tokens represent the general look and feel of the various visual slots. Tokens feed into the styling at the right times in the right slot.
>
> Regarding naming conventions, use a camelCased name following this format:
> `{slot}{property}{state (or none for default)}`. For example: `thumbSizeHovered`.
>
> Common property names: `size`, `background`, `color`, `borderRadius`
>
> Common states: `hovered`, `pressed`, `focused`, `checked`, `checkedHovered`, `disabled`
| Name | Considerations |
| ------------------------- | -------------- |
| `color` | |
| `fontSize` | |
| `fontWeight` | |
## Use cases
> TODO: Example use cases
## Compatibility with other libraries
> TODO: If this component represents a selected value, how will that be used in an HTML form? Is there a code example to illustrate?
> TODO: Is it possible this component could be rendered in a focus zone? If so, should the focus model change in that case?

Просмотреть файл

@ -0,0 +1,15 @@
import * as React from 'react';
import { ForwardRefComponent } from '@fluentui/react-theming';
import { ILinkProps, ILinkSlots } from './Link.types';
import { useLink } from './useLink';
export const LinkBase: ForwardRefComponent<ILinkProps, HTMLElement> = React.forwardRef(
(props: ILinkProps, componentRef: React.Ref<HTMLElement>) => {
const { children, slots } = props;
const { root: Root = 'a' } = slots || ({} as ILinkSlots);
const { slotProps = {} } = useLink({ ...props, componentRef });
return <Root {...slotProps.root}>{children}</Root>;
},
);

Просмотреть файл

@ -0,0 +1,83 @@
import React from 'react';
import { createTheme, ITheme, ThemeProvider } from '@fluentui/react-theming';
import { Link } from './Link';
import { LinkBase } from './Link.base';
export default {
component: 'Link',
title: 'Link',
};
const defaultColorRamp = {
values: [],
index: -1,
};
const fluentLight: ITheme = createTheme({
direction: 'ltr',
colors: {
background: 'white',
bodyText: 'black',
subText: '#333',
disabledText: '#ccc',
brand: defaultColorRamp,
accent: defaultColorRamp,
neutral: defaultColorRamp,
success: defaultColorRamp,
warning: defaultColorRamp,
danger: defaultColorRamp,
text: defaultColorRamp,
},
components: {},
icons: {},
radius: {
base: 0,
scale: 0,
unit: 'px',
},
fonts: {
default: '',
userContent: '',
mono: '',
},
fontSizes: {
base: 0,
scale: 0,
unit: 'px',
},
animations: {
fadeIn: {},
fadeOut: {},
},
spacing: {
base: 0,
scale: 0,
unit: 'px',
},
schemes: {
header: {
colors: {
background: 'black',
bodyText: 'white',
},
},
},
});
const Wrapper = (p: React.HTMLAttributes<any>) => <ThemeProvider theme={fluentLight} {...p} />;
export const baseLink = () => <LinkBase href="https://www.bing.com">Link with href</LinkBase>;
export const baseLinkWithoutHref = () => <LinkBase>Link without href</LinkBase>;
export const fluentLink = () => (
<Wrapper>
A link can be <Link href="https://www.bing.com">part of a text like this</Link>, whether it has
an href or <Link>whether it does not</Link>. It can{' '}
<Link disabled href="https://www.bing.com">
even be disabled
</Link>
.
</Wrapper>
);

Просмотреть файл

@ -0,0 +1,30 @@
import { IResolvedTokens } from '@fluentui/react-theming';
import { ILinkTokens } from './Link.tokens';
const styles = (t: IResolvedTokens<ILinkTokens>) => ({
root: {
color: t.color,
cursor: 'pointer',
outline: 'none',
textDecoration: t.textDecoration,
'&:active, &:hover, &:active:hover': {
color: t.colorHovered,
textDecoration: t.textDecorationHovered,
},
'&:focus': {
boxShadow: '0 0 0 1px #2b88d8 inset',
},
},
rootDisabled: {
color: t.colorDisabled,
cursor: 'default',
'&:link, &:visited': {
pointerEvents: 'none',
},
},
});
export default styles;

Просмотреть файл

@ -0,0 +1,35 @@
import React from 'react';
import { mount } from 'enzyme';
import { LinkBase } from './Link.base';
describe('LinkBase', () => {
it('focuses correctly when focus is triggered via ILink interface', () => {
const link1 = React.createRef<HTMLElement>();
const link2 = React.createRef<HTMLElement>();
const link3 = React.createRef<HTMLElement>();
const wrapper = mount(
<div>
<LinkBase ref={link1} href="#">
Link 1
</LinkBase>
<LinkBase ref={link2} href="#">
Link 2
</LinkBase>
<LinkBase ref={link3}>Link 3</LinkBase>
</div>,
);
const links = wrapper.getDOMNode().querySelectorAll('a') as NodeListOf<HTMLAnchorElement>;
expect(links.length).toEqual(3);
link1.current!.focus();
expect(document.activeElement!).toBe(links[0]);
link2.current!.focus();
expect(document.activeElement!).toBe(links[1]);
link3.current!.focus();
expect(document.activeElement!).toBe(links[2]);
});
});

Просмотреть файл

@ -0,0 +1,35 @@
import { IToken } from '@fluentui/react-theming';
export interface ILinkTokens {
// root tokens
backgroundColor: IToken;
backgroundColorHovered: IToken;
color: IToken;
colorHovered: IToken;
fontFamily: IToken;
fontSize: IToken;
fontWeight: IToken;
textDecoration: IToken;
textDecorationHovered: IToken;
// Disabled Link tokens
// root
backgroundColorDisabled: IToken;
colorDisabled: IToken;
}
const LinkTokens = {
// root tokens
color: '#0078d4',
colorHovered: '#201f1e',
fontSize: 'inherit',
fontWeight: 'inherit',
textDecoration: 'none',
textDecorationHovered: 'underline',
// Disabled Link tokens
// root
colorDisabled: '#a19f9d',
};
export default LinkTokens;

Просмотреть файл

@ -0,0 +1,10 @@
import { compose } from '@fluentui/react-theming';
import { LinkBase } from './Link.base';
import styles from './Link.styles';
import tokens from './Link.tokens';
export const Link = compose(LinkBase, {
name: 'Link',
styles,
tokens,
});

Просмотреть файл

@ -0,0 +1,24 @@
import { IClasses, ISlotProps, ISlottableProps } from '@fluentui/react-theming';
export interface ILinkSlots {
/** Intended to contain the link. */
root: React.ReactType;
}
export type ILinkSlotProps = ISlotProps<ILinkSlots>;
export interface ILinkClasses extends IClasses<ILinkSlots> {
/** Defines the classname that is passed to the root when the Link is disabled. */
rootDisabled: string;
}
export interface ILinkProps extends ISlottableProps<ILinkSlots, ILinkClasses> {
/** Defines the children of the Link component. */
children?: React.ReactNode;
/** Defines whether the Link is in an enabled or disabled state. */
disabled?: boolean;
/** Defines an href that serves as the navigation destination when clicking on the Link. */
href?: string;
}

Просмотреть файл

@ -0,0 +1,419 @@
# Link component specification
The `Link` component is a clickable control primarily used for navigation, providing an interactive reference to a resource. It is usually displayed as an inline element by default that can wrap text if it goes past the edges of its parent.
## Related variant considerations
The following section documents variants of the component that currently exist in Fabric and identifies variants that exist in other component libraries but don't currently exist in Fabric, documenting which component libraries have those variants.
### Variants existing in Fabric today
- `Link rendered as an anchor`
- `Link rendered as a button`
- Removing this variant by default from Fluent UI, if people want it they can use the `slots` or the `as` prop.
### Variants not in Fabric but that exist in other component libraries
- `Block/Non-inline link`
- In Carbon Design
- In Gestalt
- `External link`
- In Chakra UI
## Reference implementations
The following section documents links to different UI libraries implementations of Links, while also providing a code sandbox with a side by side implementation of them for comparison.
- [Side-by-side implementations](https://codesandbox.io/s/link-implementations-utdpb)
- [Base Web Link docs](https://baseweb.design/components/link/)
- [Carbon Design Link docs](https://www.carbondesignsystem.com/components/link/code)
- [Chakra UI Link docs](https://chakra-ui.com/link)
- [Fabric Link docs](https://developer.microsoft.com/en-us/fabric#/controls/web/link)
- FastDNA Link (Hypertext)
- [Docs](https://github.com/microsoft/fast-dna/tree/master/packages/fast-components-react-base/src/hypertext)
- [Example](https://explore.fast.design/components/hypertext)
- [Gestalt Link docs](https://pinterest.github.io/gestalt/?ref=designrevision.com#/Link)
- [Material-UI Link docs](https://material-ui.com/components/links/)
## Props
The following section documents the properties that will become part of the new component, as well as the process for mitigating all changes when moving from Fabric and Stardust to Fluent UI.
### Recommended component props
| Name | Type | Default value | Required? | Description |
| ----------------- | :------------------------: | :-----------: | :-------: | -------------------------------------------------------------------------------------- |
| `ariaDescribedBy` | `string` | | No | Identifies the element (or elements) that describes the object. |
| `ariaHidden` | `boolean` | `false` | No | Indicates whether the element is exposed to an accessibility API. |
| `ariaLabel` | `string` | | No | Defines a string value that labels the current element. |
| `ariaLabelledBy` | `string` | | No | Identifies the element (or elements) that labels the current element. |
| `className` | `string` | | No | Defines an additional classname to provide on the root of the `Link`. |
| `componentRef` | `IRefObject<ILink>` | | No | Defines an optional reference to access the imperative interface of the `Link`. |
| `disabled` | `boolean` | `false` | No | Defines whether the `Link` is in an enabled or disabled state. |
| `href` | `string` | | Yes | Defines an href that serves as the navigation destination when clicking on the `Link`. |
| `onClick` | `(ev: MouseEvent) => void` | | No | Defines a callback that handles the processing of click events on the `Link`. |
| `role` | `string` | | No | Defines the accessibility role of the `Link`. |
Props no outlined above are not handled and should be spread in the `root` slot of the component.
### Recommended interface props
| Name | Type | Default value | Description |
| ------- | :----------: | ------------- | ------------------------- |
| `focus` | `() => void` | | Sets focus on the `Link`. |
### Props to be discussed
None at the moment.
### Fabric Link props
https://developer.microsoft.com/en-us/fabric#/controls/web/link
#### ILink interface
| Name | Type | Notes |
| ------- | :----------: | ----- |
| `focus` | `() => void` | |
#### ILinkProps interface
| Name | Type | Notes |
| -------------- | :----------------------------------------------------------: | ------------------------------------------------------ |
| `as` | `string \| React.ComponentClass \| React.StatelessComponent` | Remove `as` prop in new component. |
| `componentRef` | `IRefObject<ILink>` | |
| `disabled` | `boolean` | |
| `keytipProps` | `IKeytipProps` | Should be removed until we add `Keytips` in Fluent UI. |
| `styles` | `IStyleFunctionOrObject<ILinkStyleProps, ILinkStyles>` | Should be deprecated in favor of recomposition. |
| `theme` | `ITheme` | Should not show up in the public props contract. |
### Stardust Link props
Stardust does not currently have a `Link` component implementation.
### Conversion process from Fabric 7 to Fluent UI Link
#### ILink interface
| Name | Action to take/taken | Property transitioned? | Breaking change? | Codemod/Shim created? |
| ------- | -------------------- | :--------------------: | :--------------: | :-------------------: |
| `focus` | TBD | &#x274C; | &#x274C; | &#x274C; |
#### ILinkProps interface
| Name | Action to take/taken | Property transitioned? | Breaking change? | Codemod/Shim created? |
| -------------- | -------------------- | :--------------------: | :--------------: | :-------------------: |
| `as` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `componentRef` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `disabled` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `keytipProps` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `styles` | TBD | &#x274C; | &#x274C; | &#x274C; |
| `theme` | TBD | &#x274C; | &#x274C; | &#x274C; |
### Conversion process from Stardust to Fluent UI Link
Stardust does not currently have a `Link` component implementation.
## DOM Structure
The following section documents the DOM structure for the component from different component library examples and then suggests a recommended DOM taking into consideration common patterns between the libraries reviewed.
### Base Web Link
#### Example DOM
```html
<a data-baseweb="link" href="https://baseweb.design" class="k5 ah eq bk er es bb bc tf tg th">
Link to Base Web
</a>
```
#### Considerations
None.
### Carbon Design Link
#### Example DOM
```html
<a href="#" class="bx--link some-class">
Link
</a>
```
#### Considerations
- Renders by default as a block, and not an inline, element.
### Chakra UI Link
#### Example DOM
```html
<a href="https=//chakra-ui.com" class="css-u5zpo1">
Chakra UI
</a>
```
#### Considerations
None.
### Fabric Link
#### Example DOM
##### With href
```html
<a href="http://dev.office.com/fabric/components/link" class="ms-Link root-109">
it renders as an anchor tag.
</a>
```
##### Without href
```html
<button type="button" class="ms-Link root-163">
the link is rendered as a button
</button>
```
#### Considerations
- `Links` without an `href` provided render as `buttons`.
### FastDNA Link (Hypertext)
#### Example DOM
```html
<a href="https://www.bing.com" class="c012">
Hypertext
</a>
```
#### Considerations
None.
### Gestalt Link
#### Example DOM
```html
<a class="Wk9 xQ4 WMU iyn ljY kVc" href="https://pinterest.com">
click here
</a>
```
#### Considerations
- Renders by default as a block, and not an inline, element.
### Material-UI Link
#### Example DOM
##### With href
```html
<a class="MuiTypography-root MuiLink-root MuiLink-underlineHover jss243 MuiTypography-colorPrimary" href="#">
Link
</a>
```
##### Without href
```html
<button class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiLink-button MuiTypography-body2 MuiTypography-colorPrimary">
Button Link
</button>
```
#### Considerations
- `Links` without an `href` provided render as `buttons`.
### Recommended DOM
After looking at all the component libraries above and taking into consideration common patterns the following DOM is recommended.
#### For default links
```html
<a class="root" href={href}>
{children}
</a>
```
#### For recomposed links
If the link is recomposed to use another tag that is not `a` for its `root` slot, then `role="link"` should be added to the root. An example using `button` can be read below:
```html
<button class="root" href={href} role="link">
{children}
</button>
```
### Slots
From the recommended DOM above we can indicate which slots are going to be required:
| Name | Considerations |
| ----------- | -------------- |
| `root` | |
### Considerations that need discussion
- What about inline vs block links? Should we provide them as well?
- Maybe different styled variant via recomposition.
## Behaviors
Aria spec:
https://www.w3.org/TR/wai-aria-1.1/#link
https://www.w3.org/TR/wai-aria-practices/#link
Fluent UI HIG:
https://microsoft.sharepoint-df.com/:w:/r/teams/OPGUXLeads/_layouts/15/Doc.aspx?sourcedoc=%7BE585806E-01BF-4F37-BF59-12708E4CE81D%7D&file=Links.docx&action=default&mobileredirect=true
### States
The following section describes the different states in which a `Link` can be throughout the course of interaction with it.
#### Enabled state
An enabled `Link` communicates interaction by having styling that invite the user to click/tap on it to navigate through content.
#### Disabled state
A disabled `Link` is non-interactive, disallowing the user to click/tap on it to navigate through content.
Typically disabled browser elements do now allow focus. This makes the control difficult for a blind user to know about it, or why it's disabled, without scanning the entire page. Therefore it is recommended to allow focus on disabled components and to make them readonly. This means we use `ariaDisabled` attributes, and not `disabled` attributes, for defining a disabled state. This may sometimes require special attention to ignoring input events in the case a browser element might do something. In the past we've introduced an `allowDisabledFocus` prop for component users to control this behavior.
#### Hovered state
A hovered `Link` changes styling to communicate that the user has placed a cursor above it.
#### Focused state
A focused `Link` changes styling to communicate that the user has placed keyboard focus on it. This styling is usually the same to the one in the hovered state plus extra styling on the outline to indicate keyboard focus has been placed on the component.
#### States that need discussion
None.
### Keyboard interaction
The following is a set of keys that interact with the `Link` component:
| Key | Description |
| ------------------------ | --------------------------------------------------------- |
| `Enter` | Executes the `Link` and moves focus to the `Link` target. |
| `Shift + F10` (Optional) | Opens a context menu for the `Link`. |
### Cursor interaction
Test: Possible to use this to capture mouse, though Safari does not have compatibility:
https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture
- `mouseenter`: Should immediately change the styling of the `Link` so that it appears to be hovered.
- `mouseleave`: Should immediately remove the hovered styling of the `Link`.
- `mouseup`: If triggered while cursor is still inside of the `Link's` boundaries, then it should execute the `Link` and move focus to the `Link` target.
### Touch interaction
The same behavior as above translated for touch events. This means that there is no equivalent for `mouseenter` and `mouseleave`, which makes it so that the hovered state cannot be accessed.
### Screen reader accessibility
#### `root`:
- Should default to render a native `a` element unless another `root` slot has been specified.
- Should mix in the native props expected for the `a` native element.
- Should be keyboard tabbable and focusable.
#### Accessibility concerns for the user.
The `ariaLabel`, `ariaLabelledby` and `ariaDescribedBy` properties are surfaced to the component interface but are required to be set by the component user to meet accessibility requirements.
## Themability and customization
### Composition
The `Link` component uses `react-texture` to provide a recomposable implementation that has no runtime performance penalties. The `BaseLink` implementation can be used to provide new `slots` and default `props` without the application of additional styling:
```tsx
const FooLink = BaseLink.compose({
tokens: {},
styles: {},
slots: {}
});
render () {
<FooLink href="https://www.bing.com">
Go to bing!
</FooLink>
}
```
## Class names
1 per slot
1 per state, tagged on root
### Component design tokens
> Tokens represent the general look and feel of the various visual slots. Tokens feed into the styling at the right times in the right slot.
>
> Regarding naming conventions, use a camelCased name following this format:
> `{slot}{property}{state (or none for default)}`. For example: `thumbSizeHovered`.
>
> Common property names: `size`, `background`, `color`, `borderRadius`
>
> Common states: `hovered`, `pressed`, `focused`, `checked`, `checkedHovered`, `disabled`
| Name | Considerations |
| ------------------------ | -------------- |
| `background` | |
| `backgroundDisabled` | |
| `backgroundHovered` | |
| `backgroundPressed` | |
| `backgroundVisited` | |
| `color` | |
| `colorDisabled` | |
| `colorHovered` | |
| `colorPressed` | |
| `colorVisited` | |
| `fontFamily` | |
| `fontSize` | |
| `fontWeight` | |
| `textDecoration` | |
| `textDecorationDisabled` | |
| `textDecorationHovered` | |
| `textDecorationPressed` | |
| `textDecorationVisited` | |
### To be discussed
- What do we do about high contrast? Do we provide additional tokens?
## Use cases
> TODO: Example use cases
## Compatibility with other libraries
> TODO: If this component represents a selected value, how will that be used in an HTML form? Is there a code example to illustrate?
> TODO: Is it possible this component could be rendered in a focus zone? If so, should the focus model change in that case?

Просмотреть файл

@ -0,0 +1,3 @@
export { LinkBase } from './Link.base';
export { ILinkProps, ILinkSlotProps, ILinkSlots } from './Link.types';
export { useLink } from './useLink';

Просмотреть файл

@ -0,0 +1,59 @@
import React from 'react';
import cx from 'classnames';
import { mergeSlotProps, IStateProps } from '@fluentui/react-theming';
import { ILinkProps } from './Link.types';
export interface ILinkState {
onKeyDown: (ev: KeyboardEvent) => void;
rootRef: React.Ref<Element>;
}
const useLinkState = (userProps: IStateProps<ILinkProps>): ILinkState => {
const { componentRef, disabled } = userProps;
const rootRef = React.useRef<HTMLElement>(null);
React.useImperativeHandle(componentRef, () => ({
focus: () => {
rootRef.current && rootRef.current.focus();
},
}));
const onKeyDown = (ev: KeyboardEvent) => {
// If the Link is disabled we need to prevent navigation via 'Enter' key presses.
if (disabled) {
ev.preventDefault();
}
};
return {
onKeyDown,
rootRef,
};
};
export const useLink = (props: IStateProps<ILinkProps>) => {
const { classes = {}, disabled, href } = props;
const { rootDisabled } = classes;
const state = useLinkState(props);
const { onKeyDown, rootRef } = state;
const slotProps = mergeSlotProps(props, {
root: {
'aria-disabled': disabled,
className: cx(disabled && rootDisabled),
href,
onKeyDown,
ref: rootRef,
role: 'link',
tabIndex: 0,
type: 'link',
},
});
return {
slotProps,
state,
};
};

Просмотреть файл

@ -0,0 +1,442 @@
# Slider component specification
The `Slider` component allows a user to slide a single thumb along a horizontal or vertical axis, representing a min/max range.
## TODO List
- For each TODO:
1. Read it
2. Do research
3. Answer questions
4. Remove TODO
- Search for TODO, there should be none! Delete this section even.
- Review with design crew
- Check into repo where code will live
## Related variant considerations
`2DSlider` - allows 2-dimensional sliding, used for color picking or 2d panning.
## Reference implementations
https://codesandbox.io/s/sliders-xi0zw
Fabric Slider [docs](https://developer.microsoft.com/en-us/fabric#/controls/web/slider)
Stardust Slider [docs](http://localhost:8080/components/slider/definition)
Material UI Slider [docs](https://material-ui.com/components/slider/)
BaseUI Slider [docs](https://baseweb.design/components/slider/)
Chakra Slider [docs](https://chakra-ui.com/slider)
Carbon Slider [docs](https://www.carbondesignsystem.com/components/slider/code)
AntD Slider [docs](https://ant.design/components/slider/)
FastDNA Slider [docs](https://github.com/microsoft/fast-dna/tree/master/packages/fast-components-@fluentui/base/src/slider), [example](https://explore.fast.design/components/slider)
## Props
> TODO: Consult the prop wizard to derive consistently defined props.
| Name | Type | Default value |
| ---- | ---- | ------------- |
### Recommended props
| Name | Type |
| -------------- | ----------------------------------------------------------- |
| as | string |
| className | string |
| defaultValue | number \| number[] |
| disabled | boolean |
| marks | { value: number; label: string; size: 's' \| 'm' \| 'l' }[] |
| max | number |
| min | number |
| name | The form name, is injected on the hidden `input` element. |
| onChange | (ev: Event, value: number) => void |
| originFromZero | boolean |
| step | number |
| value | number \| number[] |
| vertical | boolean |
To be discussed:
| Name | Concern |
| ------------------------------------- | ---------------------------------- |
| label/errorDescription | Should be a concern of Form |
| ariaLabel/getA11yValueMessageOnChange | Should this be in `accessibility`? |
### Fabric Slider props
https://developer.microsoft.com/en-us/fabric#/controls/web/slider
| Name | Type | Notes |
| -------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| ariaLabel | string | |
| ariaValueText | (value: number) => string | |
| buttonProps | React.HTMLAttributes<HTMLButtonElement> | |
| className | string | |
| componentRef | RefObject<ISlider> | |
| defaultValue | number | |
| disabled | boolean | |
| label | string | |
| max | number | |
| min | number | |
| onChange | (value: number) => void | This is the wrong signature and should be resolved to be (ev, number) |
| onChanged | (ev, number) => void | This is the correct signature but the wrong prop name. Should be removed |
| originFromZero | boolean | Deprecate in favor of `marks`. |
| showValue | boolean | Note it defaults to true, which should violate a naming convention. `valueHidden` might be a better name |
| snapToStep | boolean | Consider deprecating; this has no value. |
| step | number | The difference between the two adjacent values of the Slider |
| styles | IStyleFunctionOrObject<ISliderStyleProps, ISliderStyles> | Should be deprecated in favor of recomposition. |
| theme | ITheme | Should not show up in the public props contract. |
| value | number | |
| valueFormat | (value: number) => string | Could be depreacted; consider slots override? |
| vertical | boolean | |
### Stardust Slider props
| Name | Type | Notes |
| --------------------------- | -------------------------------------- | ------------------------------------------------------ |
| accessibility | "sliderBehavior" any | Why would a user need this as a prop? |
| animation | AnimationProp | Why would a user need this as a prop on slider? |
| as | React.ElementType | |
| className | string | |
| defaultValue | string \| number | |
| design | ComponentDesign | What is the use case for this? |
| fluid | boolean | Stretching should be the default, this is unneeded. |
| getA11yValueMessageOnChange | "getA11yValueMessageOnChange" function | Unclear what this specifically does; aria-live polite? |
| input | ShorthandValue<BoxProps> | Prop polution. |
| inputRef | Ref | When you can't use an input what then? |
| max | string \| number | |
| min | string \| number | |
| onChange | ComponentEventHandler | Great, this is what we want :) |
| step | string \| number | |
| styles | ComponentSlotStyle | Consider only recomposition |
| value | string \| number | |
| variables | any | Consider only recomposition |
| vertical | boolean | |
### Differences of Fabric/Stardust to resolve
| Name | Fx | Recommendation |
| --------------------------- | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| accessibility | S | Discuss: In its current state it's unclear why a user would ever pass an accessibility behavior in. This is different from every framework. How does this help developers? |
| animation | S | Remove: Why would a user need this as a prop on Slider? Also ambiguous; animation for slider movement or just css on the root? |
| ariaLabel | F | Discuss: Without having the user read aria specs, can we abstract this into `screenReaderDescription` or `accessibility.description`? At minimum we need an example of what should go here. |
| ariaValueText | F | When would a user want to use this? How is it different from `ariaLabel`? |
| buttonProps | F | Replace: Use slotProps/slots. |
| componentRef | F | Discuss: Slider imperative API to set focus and read value. Do we need these? | |
| design | S | Remove: What is the use case for this? |
| fluid | S | Replace: Stretch/fill continer should be the default no? |
| getA11yValueMessageOnChange | S | Discuss: Unclear what this specifically does; aria-live polite? |
| input | S | Replace with slotProps/slots and/or specific props. |
| inputRef | S | Use case unclear. Can we remove this? |
| label | F | Potentially we could remove this if there is a viable alternative; Form, for example. |
| originFromZero | F | Add: valuable use case. |
| showValue | F | Note it defaults to true, which should violate a naming convention. `valueHidden` might be a better name |
| styles | F | Remove: this causes perf problems. Replace with recomposition. |
| theme | F | Remove: makes API surface muddy. |
| valueFormat | F | Discuss: Could be replaced with slot/slot props. |
| variables | S | Remove: this causes perf problems. Replace with recomposition. |
### Conversion process from Fabric 7 to Fluent UI Slider
Props being changed:
> TODO
Props being removed:
> TODO
## Slots
| Name | Considerations |
| --------- | ------------------------------------------ |
| root | |
| thumb | |
| rail | The line behind the slider thumb and rail. |
| track | The selected area of the slider. |
| mark | Optional mark. |
| markLabel | |
| tooltip | The tooltip rendered above a thumb. |
## DOM structure
General considerations:
- Default should fill container horizontally; the track should touch the edges.
- The thumb may extend beyond the boundaries, but should be compensated for in the container to avoid clipping.
- Focus is set on the thumb, allows for mult range slider selection.
### Recommended DOM
```html
<div class="root">
<div class="rail"></div>
<div class="mark">
<label class="markLabel"></label>
</div>
<div class="track"></div>
<div
class="thumb"
tabindex="0"
role="slider"
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="10"
></div>
<input name="{name}" type="hidden" value="0" />
</div>
```
### Fabric Slider example DOM
- Focus is on the container
- Unneeded extra `div` wrapping the rail
- Current impl missing "marks"
- No tooltip support
- no input
```html
<div
aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="10"
aria-label="Controlled example"
aria-disabled="false"
class="ms-Slider-slideBox ms-Slider-showValue ms-Slider-showTransitions"
id="Slider187"
role="slider"
tabindex="0"
data-is-focusable="true"
>
<div class="ms-Slider-line">
<span class="ms-Slider-thumb" style="left: 0%;"></span>
<span class="ms-Slider-active activeSection-243" style="width: 0%;"></span>
<span
class="ms-Slider-inactive inactiveSection-244"
style="width: 100%;"
></span>
</div>
</div>
```
### Stardust Slider example DOM
- Extra wrapper div
- input element to receive focus
- No tooltip on thumb
```html
<div class="ui-slider" aria-disabled="false">
<div class="ui-slider__input-wrapper">
<span class="ui-slider__rail"></span>
<span class="ui-slider__track" style="width: 50%;"></span>
<input
aria-orientation="horizontal"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="50"
aria-valuetext="50"
min="0"
max="100"
step="1"
type="range"
class="ui-box ui-slider__input"
value="50"
/>
<span class="ui-slider__thumb" style="left: 50%;"></span>
</div>
</div>
```
### MUI Slider
- focus on thumb, allows for multi range slider
- input element purpose unclear; (type=hidden?) could be for form support
```html
<span class="MuiSlider-root MuiSlider-colorPrimary">
<span class="MuiSlider-rail"></span>
<span class="MuiSlider-track" style="left: 0%; width: 20%;"></span>
<input type="hidden" value="20" />
<span
class="MuiSlider-thumb MuiSlider-thumbColorPrimary"
tabindex="0"
role="slider"
data-index="0"
aria-label="custom thumb label"
aria-orientation="horizontal"
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="20"
style="left: 20%;"
></span>
</span>
```
## Behaviors
Aria spec:
https://www.w3.org/TR/wai-aria-1.1/#slider
Fluent UI HIG:
https://microsoft.sharepoint-df.com/:w:/t/OPGUXLeads/EWgxpDSGgEVInkHf9qIT8tEB9ukfQaYzXbLqc97M_3wDqw?e=d8rC23
### Disabled state
A disabled slider does not allow the user to change the value. All events will be ignored.
Typically disabled browser elements do now allow focus. This makes the control difficult for a blind user to know about it, or why it's disabled, without scanning the entire page. Therefore it is recommended to allow focus on disabled components and to make them readonly. This means we use `aria-disabled` attributes, and not `disabled` attributes, for defining a disabled state. This may sometimes require special attention to ignoring input events in the case a browser element might do something.
### Focus indicators
Focus indicators should not show in mouse or touch interaction; they should only appear when keyboard tabbing/directional keystrokes are pressed, and should disappear when mouse/touch interactions occur.
### Keyboarding
Assume left/right keyboard handling flips in RTL, unless specified.
| Key | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Up/Right | Increments the value of the slider by the amount specified by the `step` prop. If the `shift` modifier is pressed, increases by 10x the `step` value. |
| Down/Left | Decrements the value of the slider by the amount specified by the `step` prop. If the `shift` modifier is pressed, increases by 10x the `step` value. |
| PageUp | Increments by 10x `step`. Behaves as shift up/right. |
| PageDown | Decrements by 10x `step`. Behaves as shift down/left. |
| Home | Reduces the value to the amount specified by the `min` prop. |
| End | Increases the value to the amount specified in the `max` prop. |
### Mouse input
Test: Possible to use this to capture mouse, though Safari does not have compatibility:
https://developer.mozilla.org/en-US/docs/Web/API/Element/setPointerCapture
- `mousedown` should immediately attempt to set value to the appropriate value.
- `mousemove` should be attached to the window during mousedown to track window-wise move events. Value should be updated appropriately. Note; if a `mousemove` occurs
without the primary button pressed, tracking should cancel and treat the even as a `mouseup`. (Edge case: mouse down, move cursor out of window, release mouse)
- `mouseup` should remove the `mousemove` event.
### Touch
The same behavior as above, except that the events should concern
### Screen reader accessibility
#### `root`:
- should render the native element using the `as` prop, defaulting to `div`
- should mix in native props expected for the element type defined in `as`.
The `thumb` slot:
- should be focusable via `tabindex=0`
- role set to `slider`
- receives `aria-valuemin` and `aria-valuemax` representing min/max
- receives `aria-valuenow` representing the current value
### Accessibility concerns for the user
`aria-label` or `aria-labeledby`: Describe when and why.
## Themability and customization
### Composition
The `Slider` uses `react-texture` to provide a recomposable implementation that has no runtime performance penalties. The `BaseSlider` implementation can be used to provide new `slots` and default `props`:
```tsx
const FooSlider = BaseSlider.compose({
tokens: {},
styles: {},
slots: {}
});
render() {
<FooSlider min={-100} max={100} centerValue={0}>
}
```
## Class names
1 per slot
1 per state, tagged on root
### Component design tokens
> Tokens represent the general look and feel of the various visual slots. Tokens feed into the styling at the right times in the right slot.
>
> Regarding naming conventions, use a camelCased name following this format:
> `{slot}{property}{state (or none for default)}`. For example: `thumbSizeHovered`.
>
> Common property names: `size`, `background`, `color`, `borderRadius`
>
> Common states: `hovered`, `pressed`, `focused`, `checked`, `checkedHovered`, `disabled`
| Name | Considerations |
| ------------------ | -------------- |
| railBorderColor | |
| railBorderRadius | |
| railBorderWidth | |
| railColor | |
| railColorDisabled | |
| railColorFocused | |
| railColorHovered | |
| railColorPressed | |
| railSize | |
| thumbBorderColor | |
| thumbBorderRadius | |
| thumbBorderWidth | |
| thumbColor | |
| thumbColorDisabled | |
| thumbColorFocused | |
| thumbColorHovered | |
| thumbColorPressed | |
| thumbSize | |
| trackBorderColor | |
| trackBorderRadius | |
| trackBorderWidth | |
| trackColor | |
| trackColorDisabled | |
| trackColorFocused | |
| trackColorHovered | |
| trackColorPressed | |
| trackSize | |
NOTE! Stardust does not follow this convention. Slider currently uses these tokens:
```
activeThumbColor: string
activeThumbHeight: string
activeThumbWidth: string
disabledRailColor: string
disabledThumbColor: string
disabledTrackColor: string
height: string
length: string
railColor: string
railHeight: string
thumbBorderPadding: string
thumbColor: string
thumbHeight: string
thumbWidth: string
trackColor: string
```
## Use cases
> TODO: Example use cases
## Compatibility with other libraries
> TODO: If this component represents a selected value, how will that be used in an HTML form? Is there a code example to illustrate?
> TODO: Is it possible this component could be rendered in a focus zone? If so, should the focus model change in that case?

Просмотреть файл

@ -0,0 +1,17 @@
import React from 'react';
import { ISliderProps, ISliderSlots } from './Slider.types';
import { useSlider } from './useSlider';
export const SliderBase: React.FunctionComponent<ISliderProps> = (props: ISliderProps) => {
const { root: Root = 'div', rail: Rail = 'div', thumb: Thumb = 'div', track: Track = 'div' } =
props.slots || ({} as ISliderSlots);
const { slotProps = {} } = useSlider(props);
return (
<Root {...slotProps.root}>
<Rail {...slotProps.rail} />
<Track {...slotProps.track} />
<Thumb {...slotProps.thumb} />
</Root>
);
};

Просмотреть файл

@ -0,0 +1,34 @@
describe('Slider', () => {
beforeEach(async () => {
await jestPuppeteer.resetPage();
await page.goto('http://localhost:3456/iframe.html?id=slider--fluent-slider');
});
it('should move slider', async () => {
const sliderThumb = await page.mainFrame().waitForSelector('[role="slider"]');
await sliderThumb.hover();
await page.mouse.down();
await page.mouse.move(14, 28);
await page.mouse.up();
const boundingBox = (await sliderThumb.boundingBox())!;
expect(boundingBox).not.toBeNull();
// expect(boundingBox.x).toBe(14);
});
it('should move slider, again (to test scenario framework)', async () => {
const sliderThumb = await page.mainFrame().waitForSelector('[role="slider"]');
await sliderThumb.hover();
await page.mouse.down();
await page.mouse.move(14, 28);
await page.mouse.up();
const boundingBox = (await sliderThumb.boundingBox())!;
expect(boundingBox).not.toBeNull();
// expect(boundingBox.x).toBe(14);
});
});

Просмотреть файл

@ -0,0 +1,127 @@
import React from 'react';
import { Slider } from './Slider';
import { ISliderTokens } from './Slider.tokens';
import { ThemeProvider, ITheme, createTheme } from '@fluentui/react-theming';
import { SliderBase } from './Slider.base';
export default {
component: 'Slider',
title: 'Slider',
};
const defaultColorRamp = {
values: [],
index: -1,
};
const fluentLight: ITheme = createTheme({
direction: 'ltr',
colors: {
background: 'white',
bodyText: 'black',
subText: '#333',
disabledText: '#ccc',
brand: defaultColorRamp,
accent: defaultColorRamp,
neutral: defaultColorRamp,
success: defaultColorRamp,
warning: defaultColorRamp,
danger: defaultColorRamp,
text: defaultColorRamp,
},
components: {},
icons: {},
radius: {
base: 0,
scale: 0,
unit: 'px',
},
fonts: {
default: '',
userContent: '',
mono: '',
},
fontSizes: {
base: 0,
scale: 0,
unit: 'px',
},
animations: {
fadeIn: {},
fadeOut: {},
},
spacing: {
base: 0,
scale: 0,
unit: 'px',
},
schemes: {
header: {
colors: {
background: 'black',
bodyText: 'white',
},
},
},
});
const teamsLight: ITheme = createTheme(fluentLight, {
colors: {},
components: {
Slider: {
tokens: {
railSize: '0.1429rem',
trackSize: '0.1429rem',
railColor: 'rgb(225, 223, 221)',
railColorHovered: { dependsOn: 'railColor' },
trackColor: 'rgb(98, 100, 167)',
trackColorHovered: { dependsOn: 'trackColor' },
thumbSize: '0.7143rem',
thumbBorderWidth: 0,
thumbColor: 'rgb(96, 94, 92)',
thumbSizePressed: '1rem',
} as ISliderTokens,
},
},
});
const Wrapper = (p: React.HTMLAttributes<any>) => <ThemeProvider theme={fluentLight} {...p} />;
export const fluentSlider = () => (
<ThemeProvider theme={fluentLight}>
Default (ltr):
<Slider defaultValue={50} slotProps={{ thumb: { 'aria-label': 'I am a slider' } }} />
Disabled:
<Slider disabled defaultValue={50} slotProps={{ thumb: { 'aria-label': 'I am a slider' } }} />
Header scheme:
<ThemeProvider scheme="header">
Default:
<Slider defaultValue={50} slotProps={{ thumb: { 'aria-label': 'I am a slider' } }} />
Disabled:
<Slider disabled defaultValue={50} slotProps={{ thumb: { 'aria-label': 'I am a slider' } }} />
</ThemeProvider>
</ThemeProvider>
);
export const fluentSliderDisabled = () => (
<Wrapper>
<Slider disabled defaultValue={50} />
</Wrapper>
);
export const fluentVerticalSlider = () => (
<Wrapper style={{ display: 'flex', height: 200 }}>
<Slider vertical defaultValue={50} />
<Slider vertical defaultValue={50} />
<Slider vertical defaultValue={50} />
</Wrapper>
);
export const teamsLightSlider = (p: React.HTMLAttributes<any>) => (
<ThemeProvider theme={teamsLight}>
<Slider defaultValue={50} />
</ThemeProvider>
);
export const aTypicalSlider = () => <SliderBase min={0} max={10} defaultValue={5} />;

Просмотреть файл

@ -0,0 +1,126 @@
import { IResolvedTokens } from '@fluentui/react-theming';
import { ISliderTokens } from './Slider.tokens';
const styles = (t: IResolvedTokens<ISliderTokens>) => ({
rootDisabled: {},
rootVertical: {},
rootFocused: {},
root: {
position: 'relative',
height: t.size,
'&$rootVertical': {
width: t.size,
height: '100%',
},
},
rail: {
position: 'absolute',
left: 0,
right: 0,
top: '50%',
transform: 'translateY(-50%)',
width: 'auto',
height: t.railSize,
borderRadius: t.railBorderRadius,
backgroundColor: t.railColor,
'$rootVertical &': {
left: '50%',
right: 'auto',
top: 0,
bottom: 0,
transform: 'translateX(-50%)',
width: t.railSize,
height: '100%',
},
'$root:hover &, $rootFocused &': {
backgroundColor: t.railColorHovered,
},
'$root:active &': {
backgroundColor: t.railColorPressed,
},
'$root$rootDisabled &': {
backgroundColor: t.railColorDisabled,
},
},
track: {
position: 'absolute',
left: 0,
top: '50%',
transform: 'translateY(-50%)',
height: t.trackSize,
backgroundColor: t.trackColor,
borderRadius: t.trackBorderRadius,
'$rootVertical &': {
left: '50%',
top: 'auto',
bottom: 0,
transform: 'translateX(-50%)',
width: t.trackSize,
},
'$root:hover &, $rootFocused &': {
backgroundColor: t.trackColorHovered,
},
'$root:active &': {
backgroundColor: t.trackColorPressed,
},
'$root$rootDisabled &': {
backgroundColor: t.trackColorDisabled,
},
},
thumb: {
position: 'absolute',
transform: 'translateX(-50%)',
boxSizing: 'border-box',
width: t.size,
height: t.size,
outline: 'none',
'$rootVertical &': {
transform: 'translateY(50%)',
},
'&:after': {
content: '""',
position: 'absolute',
height: t.thumbSize,
width: t.thumbSize,
borderRadius: t.thumbBorderRadius,
left: '50%',
top: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: t.thumbColor,
borderWidth: t.thumbBorderWidth,
borderStyle: 'solid',
borderColor: t.thumbBorderColor,
},
'&:focus': {
borderRadius: 2,
border: '1px solid black',
},
'$root:active &:after': {
width: t.thumbSizePressed,
height: t.thumbSizePressed,
},
'$rootDisabled &:after': {
borderColor: t.thumbBorderColorDisabled,
},
},
});
export default styles;

Просмотреть файл

@ -0,0 +1,10 @@
import React from 'react';
import { compose } from '@fluentui/react-theming';
describe('Fluent Slider', () => {
it('can slide', () => {
const Button = () => <div>hi</div>;
compose(Button, {});
expect(true).toBeTruthy();
});
});

Просмотреть файл

@ -0,0 +1,58 @@
import { IToken } from '@fluentui/react-theming';
export interface ISliderTokens {
railBorderRadius: IToken;
railColor: IToken;
railColorDisabled: IToken;
railColorHovered: IToken;
railColorPressed: IToken;
railSize: IToken;
size: IToken;
thumbBorderColor: IToken;
thumbBorderColorDisabled: IToken;
thumbBorderRadius: IToken;
thumbBorderWidth: IToken;
thumbColor: IToken;
thumbColorDisabled: IToken;
thumbColorFocused: IToken;
thumbColorHovered: IToken;
thumbColorPressed: IToken;
thumbSize: IToken;
thumbSizePressed: IToken;
trackBorderRadius: IToken;
trackColor: IToken;
trackColorDisabled: IToken;
trackColorHovered: IToken;
trackColorPressed: IToken;
trackSize: IToken;
}
const SliderTokens = {
size: 28,
railBorderRadius: 4,
railColor: 'rgb(200, 198, 196)',
railColorDisabled: 'rgb(243, 242, 241)',
railColorHovered: 'rgb(222, 236, 249)',
railColorPressed: { dependsOn: 'railColorHovered' },
railSize: 4,
thumbBorderColor: 'rgb(96, 94, 92)',
thumbBorderColorHovered: 'rgb(0, 90, 158)',
thumbBorderColorDisabled: 'rgb(200, 198, 196)',
thumbBorderRadius: 50,
thumbBorderWidth: 2,
thumbColor: '#FFF',
thumbColorDisabled: '#FFF',
thumbColorFocused: '#FFF',
thumbColorHovered: '#FFF',
thumbColorPressed: '#FFF',
thumbSize: 16,
thumbSizePressed: { dependsOn: 'thumbSize' },
trackBorderRadius: 4,
trackColor: 'rgb(96, 94, 92)',
trackColorDisabled: 'rgb(161, 159, 157)',
trackColorHovered: 'rgb(0, 120, 212)',
trackColorPressed: { dependsOn: 'trackColorHovered' },
trackSize: 4,
};
export default SliderTokens;

Просмотреть файл

@ -0,0 +1,30 @@
import { compose } from '@fluentui/react-theming';
import { SliderBase } from './Slider.base';
import styles from './Slider.styles';
import tokens from './Slider.tokens';
/*
What if items-view uses sliderbase, or something like it, that can receive class map from context.
We could technically just compose a slider which has a themeName of "Slider" and export it.
If items-view want a DetailsSlider specialized slider component, they could:
<ThemeProvider theme={ [itemsViewTheme] }>
<BaseSlider themeName="DetaisSlider"/>
</ThemeProvider>
or:
applyDefaultStyling(ItemsViewThemeLight)
or:
none of this! They have css. The site can apply style overrides and tokens.
*/
export const Slider = compose(SliderBase, {
name: 'Slider',
styles,
tokens,
});

Просмотреть файл

@ -0,0 +1,49 @@
import { IClasses, ISlotProps, ISlottableProps } from '@fluentui/react-theming';
export interface ISliderSlots {
/** Intended to contain the slider */
root: React.ReactType;
/** Intended to provide a track space for the thumb to slide on */
rail: React.ReactType;
/** Intended to provide a selected track section from left to thumb. */
track: React.ReactType;
/** Intended to be a child of the track, where left represents a percentage */
thumb: React.ReactType;
}
export type ISliderSlotProps = ISlotProps<ISliderSlots>;
export interface ISliderClasses extends IClasses<ISliderSlots> {
rootFocused: string;
rootVertical: string;
rootDisabled: string;
}
export interface ISliderProps extends ISlottableProps<ISliderSlots, ISliderClasses> {
/** Sets the disabled flag, causing the control to be inactive. */
disabled?: boolean;
/** min value */
min?: number;
/** max value */
max?: number;
/** step */
step?: number;
/** should snap to step */
snapToStep?: boolean;
/** whether this is vertical or not */
vertical?: boolean;
/** current value (controlled) */
value?: number;
/** default value (uncontrolled) */
defaultValue?: number;
/** on change handler (controlled) */
onChange?: (ev: MouseEvent | KeyboardEvent, value: number) => void;
}

Просмотреть файл

@ -0,0 +1,3 @@
export { SliderBase } from './Slider.base';
export { ISliderProps, ISliderSlotProps, ISliderSlots } from './Slider.types';
export { useSlider } from './useSlider';

Просмотреть файл

@ -0,0 +1,10 @@
/**
* The Slider Fluent theme system can be applied to the slider:
*
* import { applyDefaultTheme } from '@fluentui/theming';
* import {FluentLightTheme, Fluent}
*
* applyDefaultTheme(FluentSlider);
*
* <ThemeProvider theme={ appTheme
*/

Просмотреть файл

@ -0,0 +1,244 @@
import * as React from 'react';
import { useControlledState } from '../../hooks/useControlledState';
import { useWindowEvent } from '../../hooks/useWindowEvent';
import { ISliderProps } from './Slider.types';
import { mergeSlotProps } from '@fluentui/react-theming';
import cx from 'classnames';
function _getDragValues(
ev: React.MouseEvent,
containerRect: any,
min: number,
max: number,
step: number,
snapToStep: boolean,
vertical: boolean,
) {
const range = max - min;
const percentage = Math.min(
1,
Math.max(
0,
vertical
? 1 - (ev.clientY - containerRect.top) / containerRect.height
: (ev.clientX - containerRect.left) / containerRect.width,
),
);
const value = Math.round(min + (percentage * range) / step) * step;
return {
percentage: snapToStep ? (100 * value) / (max - min) : 100 * percentage,
value,
};
}
export interface ISliderState {
focused: boolean;
min: number;
max: number;
value: number;
rootRef: React.Ref<Element>;
thumbRef: React.Ref<Element>;
onMouseDown?: (ev: React.MouseEvent) => void;
onKeyDown?: (ev: React.KeyboardEvent) => void;
onFocus: () => void;
onBlur: () => void;
percentage: number;
}
/**
* Slider hook for building an accessible slider.
*
* https://www.w3.org/TR/2017/REC-wai-aria-1.1-20171214/#slider
*/
const useSliderState = (userProps: ISliderProps): ISliderState => {
const {
disabled = false,
vertical = false,
min = 0,
max = 100,
step = 1,
value: controlledValue,
snapToStep = false,
onChange,
defaultValue,
} = userProps;
const [focused, setFocused] = React.useState(false);
const [dragging, setDragging] = React.useState(false);
const [value, setValue] = useControlledState(controlledValue, defaultValue);
const [dragState, setDragState] = React.useState<{
rootRect: DOMRect | null;
}>({
rootRect: null,
});
const rootRef = React.useRef<HTMLElement>(null);
const thumbRef = React.useRef<HTMLElement>(null);
const percentage = (100 * (value - min)) / (max - min);
const _updateValue = React.useCallback(
(ev, val) => {
if (onChange) {
onChange(ev, val);
}
setValue(val);
return val;
},
[onChange, setValue],
);
const onMouseMove = React.useCallback(
(ev: any, allowDefault: any) => {
if (dragState && dragState.rootRect) {
const drag = _getDragValues(ev, dragState.rootRect, min, max, step, snapToStep, vertical);
_updateValue(ev, drag.value);
}
if (!allowDefault) {
ev.preventDefault();
ev.stopPropagation();
}
},
[dragState, min, max, step, snapToStep, _updateValue, vertical],
);
const onMouseDown = React.useCallback(
(ev: any) => {
const rootRect = rootRef.current!.getBoundingClientRect();
setDragState({ rootRect });
setDragging(true);
const drag = _getDragValues(ev, rootRect, min, max, step, snapToStep, vertical);
setImmediate(() => thumbRef.current?.focus());
_updateValue(ev, drag.value);
},
[_updateValue, max, min, snapToStep, step, setDragging, setDragState, rootRef, vertical],
);
const onMouseUp = React.useCallback(
(ev: any) => {
setDragging(false);
ev.preventDefault();
ev.stopPropagation();
},
[setDragging],
);
const onFocus = () => setFocused(true);
const onBlur = () => setFocused(false);
useWindowEvent('mousemove', dragging && onMouseMove);
useWindowEvent('mouseup', dragging && onMouseUp);
const onKeyDown = (ev: React.KeyboardEvent) => {
let newValue;
const increment = (ev.shiftKey ? 10 : 1) * step;
switch (ev.which) {
case 36: // home
newValue = min;
break;
case 35: // end
newValue = max;
break;
case 37: // left
case 40: // down
newValue = ev.metaKey ? min : Math.max(min, value - increment);
break;
case 38: // up
case 39: // right
newValue = ev.metaKey ? max : Math.min(max, value + increment);
break;
default:
return;
}
_updateValue(ev, newValue);
ev.preventDefault();
ev.stopPropagation();
};
return {
min,
max,
value,
rootRef,
thumbRef,
onMouseDown: disabled ? undefined : onMouseDown,
onKeyDown: disabled ? undefined : onKeyDown,
onFocus,
onBlur,
percentage,
focused,
};
};
export const useSlider = (props: ISliderProps) => {
const { classes = {}, disabled, vertical } = props;
const state = useSliderState(props);
const {
min,
max,
value,
rootRef,
thumbRef,
onMouseDown,
onKeyDown,
onFocus,
onBlur,
percentage,
focused,
} = state;
const { rootFocused, rootDisabled, rootVertical } = classes;
const slotProps = mergeSlotProps(props, {
root: {
ref: rootRef,
onMouseDown,
onKeyDown,
className: cx(focused && rootFocused, disabled && rootDisabled, vertical && rootVertical),
},
rail: {},
track: {
style: vertical
? {
height: `${percentage}%`,
}
: {
width: `${percentage}%`,
},
},
thumb: {
ref: thumbRef,
tabIndex: 0,
role: 'slider',
'aria-disabled': disabled,
'aria-valuemin': min,
'aria-valuemax': max,
'aria-valuenow': value,
onFocus,
onBlur,
style: vertical
? {
bottom: `${percentage}%`,
}
: {
left: `${percentage}%`,
},
},
});
return {
state,
slotProps,
};
};

Просмотреть файл

Просмотреть файл

@ -0,0 +1,6 @@
import { useState } from 'react';
export const useControlledState = (controlledValue: any, defaultValue: any) => {
const [value, setValue] = useState(defaultValue);
return [controlledValue === undefined ? value : controlledValue, setValue];
};

Просмотреть файл

@ -0,0 +1,15 @@
import { useEffect } from 'react';
/* eslint-disable no-undef */
export const useWindowEvent = (eventName: any, onEvent: any) => {
useEffect(() => {
if (onEvent) {
window.addEventListener(eventName, onEvent, true);
}
return () => {
if (onEvent) {
window.removeEventListener(eventName, onEvent, true);
}
};
}, [eventName, onEvent]);
};

Просмотреть файл

@ -0,0 +1,5 @@
export * from './components/Button/Button.base';
export * from './components/Link/Link.base';
export * from './components/Link/Link';
export * from './components/Slider/Slider.base';
export * from './components/Slider/Slider';

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -0,0 +1,15 @@
{
"extends": "@fluentui/scripts/config/typescript/tsconfig.common.json",
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "lib",
"types": ["node", "jest", "expect-puppeteer", "jest-environment-puppeteer"]
},
"include": ["src"],
"references": [
{
"path": "../react-theming/tsconfig.json"
}
]
}

Просмотреть файл

@ -12,11 +12,15 @@ export {
ITokenResolver,
} from './theme.types';
export { mergeSlotProps } from './utilities/mergeSlotProps';
export { compose } from './compose';
// Workaround for webpack warnings
import { IStandardProps as P } from './utilities/mergeSlotProps';
import { ForwardRefComponent as ForwardRefComponentInternal } from './compose';
export type IStandardProps = P;
export { compose, ForwardRefComponent } from './compose';
export type ForwardRefComponent<TProps, TElement> = ForwardRefComponentInternal<TProps, TElement>;
export { ThemeContext } from './themeContext';
export { ThemeProvider } from './components/ThemeProvider/ThemeProvider';
export { Box } from './components/Box/Box';

Просмотреть файл

@ -1,6 +1,7 @@
{
"extends": "@fluentui/scripts/config/typescript/tsconfig.common.json",
"compilerOptions": {
"composite": true,
"rootDir": "src",
"outDir": "lib"
},

1025
yarn.lock

Разница между файлами не показана из-за своего большого размера Загрузить разницу