diff --git a/.npmignore b/.npmignore index 72a6b833758ccc..b0359213c475d6 100644 --- a/.npmignore +++ b/.npmignore @@ -9,4 +9,4 @@ tsconfig.json jsconfig.json webpack.config.js typings - +examples diff --git a/.vscode/settings.json b/.vscode/settings.json index e71cf1cd095f29..d6a1e0b062b296 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,14 +8,14 @@ // When enabled, will trim trailing whitespace when you save a file. "files.trimTrailingWhitespace": true, - // Controls if the editor should automatically close brackets after opening them - "editor.autoClosingBrackets": false, + // Controls if the editor should automatically close brackets after opening them + "editor.autoClosingBrackets": false, // Controls whether the editor should render whitespace characters "editor.renderWhitespace": true, - // Controls if the editor will insert spaces for tabs. Accepted values: "auto", true, false. If set to "auto", the value will be guessed when a file is opened. - "editor.insertSpaces": true, + // Controls if the editor will insert spaces for tabs. Accepted values: "auto", true, false. If set to "auto", the value will be guessed when a file is opened. + "editor.insertSpaces": true, "files.exclude": { "**/.git": true, diff --git a/examples/todo-app/gulpfile.js b/examples/todo-app/gulpfile.js new file mode 100644 index 00000000000000..5e39ea431c63be --- /dev/null +++ b/examples/todo-app/gulpfile.js @@ -0,0 +1,8 @@ +'use strict'; + +let build = require('web-library-build'); + +build.tslint.isEnabled = () => false; +build.sass.isEnabled =() => true; + +build.initialize(require('gulp')); diff --git a/examples/todo-app/index.html b/examples/todo-app/index.html new file mode 100644 index 00000000000000..d9315a40400cf5 --- /dev/null +++ b/examples/todo-app/index.html @@ -0,0 +1,17 @@ + + + + + + + + Test Page + + + + + + + + + diff --git a/examples/todo-app/package.json b/examples/todo-app/package.json new file mode 100644 index 00000000000000..38ff083fb860fe --- /dev/null +++ b/examples/todo-app/package.json @@ -0,0 +1,23 @@ +{ + "name": "fabric-react-example", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "react-addons-update": "^0.14.0", + "web-library-build": "^0.1.3" + }, + "dependencies": { + "es6-promise": "^3.2.1", + "load-themed-styles": "^1.2.0", + "office-ui-fabric": "^2.6.1", + "office-ui-fabric-react": "^0.33.0", + "react": "^0.14.0", + "react-dom": "^0.14.0" + } +} diff --git a/examples/todo-app/src/DataProvider.ts b/examples/todo-app/src/DataProvider.ts new file mode 100644 index 00000000000000..9ee39e125b9007 --- /dev/null +++ b/examples/todo-app/src/DataProvider.ts @@ -0,0 +1,166 @@ +import update = require('react-addons-update'); +import { findIndex } from 'office-ui-fabric-react/lib/utilities/array'; +import { ITodoItem, IDataProvider } from './types/index'; + +const ADD_ITEMS_DELAY = 500; + +/** + * DataProvider the class used to maintain the client side data. + * + * It interact with data provider to sync data items with sharepoint list. + * It also maintain the loading state to tell React Component once the action is + * started of finished. + */ +export default class DataProvider implements IDataProvider { + /** + * The counter used to identify the latest server call. + * If the current call is not the latest one, we don't want to update the list + * to mess up with the out of date data with data in this store. + */ + private _items: ITodoItem[]; + private _isLoading: boolean; + private _listeners: Array<() => void>; + + /** + * The items store in the local. It only contains the data recently fetched from server. + */ + public get items(): Array { return this._items; } + public set items(value: Array) { + this._items = value; + this._emitChange(); + } + + /** + * Whether there is unfinished server request currently. + */ + public get isLoading(): boolean { return this._isLoading; } + public set isLoading(value: boolean) { + this._isLoading = value; + this._emitChange(); + } + + constructor() { + this._items = [ + { + id: '61b59681-2a82-4a51-b221-8c35e333ae89', + title: 'Finish Sample Todo web part before dev kitchen', + isComplete: false + }, + { + id: '94a844ae-0c6a-4820-8042-dbc386bdf930', + title: 'Finish All the work in Todo web part before dev kitchen', + isComplete: false + }, + { + id: '5fa55618-90f9-4b5f-b12d-60c9fb1fc7f0', + title: 'SharePoint API investigation for Todo web part', + isComplete: true + }, + { + id: '2ae54c74-1395-4a49-8dd2-4857efdd0e5e', + title: 'Bug fixing of Pivot Control', + isComplete: true + } + ]; + this._isLoading = false; + + this._listeners = []; + this.createItem = this.createItem.bind(this); + this.deleteItem = this.deleteItem.bind(this); + this.toggleComplete = this.toggleComplete.bind(this); + } + + /** + * Create a new item and add it to the list through data provider. + */ + public createItem(title: string): Promise { + this.isLoading = true; + + return new Promise((resolve) => { + const newItem: ITodoItem = { + id: this._generateGuid(), + title: title, + isComplete: false + }; + + setTimeout(() => { + this.items = this.items.concat(newItem); + this.isLoading = false; + resolve(this.items); + }, ADD_ITEMS_DELAY); + }); + } + + /** + * Delete a item from the list through data provider. + */ + public deleteItem(delItem: ITodoItem): Promise { + return new Promise((resolve) => { + this.items = + this.items.filter((item: ITodoItem) => item.id !== delItem.id); + resolve(this.items); + }); + } + + /** + * Toggle the complete state of an item by. + * + * Will call updateItem function to update complete state of this item. + */ + public toggleComplete(item: ITodoItem): Promise { + // Create a new Item in which the PercentComplete value has been changed. + const newItem: ITodoItem = update(item, { + isComplete: { $set: item.isComplete === true ? false : true } + }); + + return new Promise((resolve, reject) => { + const index: number = + findIndex( + this.items, + (item: ITodoItem) => item.id === newItem.id + ); + if (index !== -1) { + this.items[index] = newItem; + this.items = this.items.slice(0); + resolve(this.items); + } else { + reject(new Error(`Item to update doesn't exist.`)); + } + }); + } + + /** + * Add listener to the provider. + * + * Once the store has a change of state and emit that change, the listeners will be called. + */ + public addListener(listener: () => void): void { + this._listeners.push(listener); + } + + /** + * Remove the registered listener. + * + * You must remove the listener registered by addListener method when you are not going to use it anymore. + */ + public removeListener(listener: () => void): void { + const listenerIdx: number = this._listeners.indexOf(listener); + if (listenerIdx > -1) { + this._listeners.splice(listenerIdx, 1); + } + } + + /** + * Emit the changes in the store to all listeners. + */ + private _emitChange(): void { + this._listeners.forEach((listener: () => void) => listener()); + } + + private _generateGuid(): string { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); + return v.toString(16); + }); + } +} diff --git a/examples/todo-app/src/components/Todo.module.scss b/examples/todo-app/src/components/Todo.module.scss new file mode 100644 index 00000000000000..35fd4fc8dd9c91 --- /dev/null +++ b/examples/todo-app/src/components/Todo.module.scss @@ -0,0 +1,93 @@ +@import "../../node_modules/office-ui-fabric/dist/sass/Fabric.Common"; + +.todo { + padding: 28px 40px 60px 40px; + max-width: 640px; + margin: 0 auto; + border: 2px $ms-color-neutralLighter solid; + @include ms-font-m; + + .topRow { + position: relative; + } + + .todoHeading { + display: inline-block; + } + + .todoPivot { + padding-top: 24px; + } + + .todoForm { + display: table-row; + + .textField { + display: table-cell; + width: 100%; + } + .addButton { + display: table-cell; + @include margin-left(16px); + white-space: nowrap; + } + } + + .todoList { + margin-top: 20px; + border-top: 1px $ms-color-neutralLight solid; + + .todoItem { + border: 1px $ms-color-neutralLight solid; + border-top: none; + + .itemTaskRow { + padding: 16px 20px; + + .deleteButton { + position: absolute; + top: 50%; + transform: translateY(-50%); + right: 0px; + @include margin-right(16px); + + &:hover, &:focus { + color: $ms-color-themePrimary; + } + } + } + + &.isHidden { + display: none; + } + + &.isCompleted { + border-bottom: 1px $ms-color-white solid; + border-left: 1px $ms-color-neutralLighter solid; + border-right: 1px $ms-color-neutralLighter solid; + @include ms-bgColor-neutralLighter; + } + } + } + + .workingOnItSpinner { + display: inline-block; + position: absolute; + top: 0; + bottom: 0; + margin: auto; + height: 33.33333%; + line-height: 1.5em; + padding: 0px 24px; + } + + .fetchingTasksSpinner { + display: flex; + justify-content: center; + padding: 24px 0px; + } + + @media only screen and (max-width:640px) { + padding: 20px 20px; + } +} \ No newline at end of file diff --git a/examples/todo-app/src/components/Todo.tsx b/examples/todo-app/src/components/Todo.tsx new file mode 100644 index 00000000000000..bc4911c16caf85 --- /dev/null +++ b/examples/todo-app/src/components/Todo.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import { Spinner, SpinnerType } from 'office-ui-fabric-react/lib/Spinner'; +import DataProvider from '../DataProvider'; +import { ITodoProps, ITodoState, ITodoItem, ITodoItemProps } from '../types/index'; + +import TodoForm from './TodoForm'; +import TodoTabs from './TodoTabs'; + +import styles from './Todo.module.scss'; +import strings from './../strings'; + +/** + * Todo component is the top level react component of this web part. + * It uses fabric-react component + * + * Link of Spinner: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/spinner + */ +export default class Todo extends React.Component { + constructor(props: ITodoProps) { + super(props); + + this._onProviderChange = this._onProviderChange.bind(this); + this.state = { + items: this.props.dataProvider.items + }; + } + + public componentDidMount(): void { + this.props.dataProvider.addListener(this._onProviderChange); + } + + public componentWillUnmount(): void { + this.props.dataProvider.removeListener(this._onProviderChange); + } + + public render(): React.ReactElement { + return ( +
+
+

{ strings.todoListTitle }

+ { this._renderWorkingOnItSpinner() } +
+ + + { this._renderFetchingTasksSpinner() } +
+ ); + } + + private _renderWorkingOnItSpinner(): React.ReactElement> { + return this.props.dataProvider.isLoading && this.state.items.length > 0 + ?
+ +
+ : undefined; + } + + private _renderFetchingTasksSpinner(): React.ReactElement> { + return this.props.dataProvider.isLoading && this.state.items.length === 0 + ?
+ +
+ : undefined; + } + + private _onProviderChange(): void { + this.setState({ + items: this.props.dataProvider.items + }); + } +} diff --git a/examples/todo-app/src/components/TodoForm.tsx b/examples/todo-app/src/components/TodoForm.tsx new file mode 100644 index 00000000000000..5d8843420bfe84 --- /dev/null +++ b/examples/todo-app/src/components/TodoForm.tsx @@ -0,0 +1,85 @@ +import * as React from 'react'; +import { Button, ButtonType } from 'office-ui-fabric-react/lib/Button'; +import { TextField } from 'office-ui-fabric-react/lib/TextField'; +import { ITodoFormProps, ITodoFormState } from '../types/index'; +import styles from './Todo.module.scss'; +import strings from './../strings'; + +/** + * The form component used for adding new item to the list. + * + * It uses fabric-react component + + ); + } + + private _onSubmit(event: React.FormEvent): void { + event.preventDefault(); + + if (!this._getTitleErrorMessage(this._textField.value)) { + this.setState({ + inputValue: '' + } as ITodoFormState); + + this.props.onSubmit(this._textField.value); + } else { + this.setState({ + errorMessage: this._getTitleErrorMessage(this.state.inputValue) + } as ITodoFormState); + + this._textField.focus(); + } + } + + private _onBeforeTextFieldChange(newValue: string): void { + this.setState({ + inputValue: newValue, + errorMessage: '' + }); + } + + private _getTitleErrorMessage(title: string): string { + if (title.trim() === '' ) { + return strings.titleEmptyErrorMessage; + } else { + return ''; + } + } +} diff --git a/examples/todo-app/src/components/TodoItem.tsx b/examples/todo-app/src/components/TodoItem.tsx new file mode 100644 index 00000000000000..ca6e51cfaf2c6a --- /dev/null +++ b/examples/todo-app/src/components/TodoItem.tsx @@ -0,0 +1,102 @@ +import * as React from 'react'; +import { Button, ButtonType } from 'office-ui-fabric-react/lib/Button'; +import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox'; +import { FocusZone, FocusZoneDirection } from 'office-ui-fabric-react/lib/FocusZone'; +import { DocumentCardActivity } from 'office-ui-fabric-react/lib/DocumentCard'; +import { css } from 'office-ui-fabric-react/lib/utilities/css'; +import { ITodoItem, ITodoItemProps } from '../types/index'; + +import styles from './Todo.module.scss'; +import strings from './../strings'; + +/** + * TodoItem component using fabric-react component + { this._renderAnchorLink(link) } + + ); + } + + private _renderLink(link: INavLink, linkIndex: number, level: number): React.ReactElement<{}> { return (
  • - - { (link.iconClassName ? - - : '') } - { this.props.onRenderLink(link)} - { this._renderLinks(link.links) } -
  • + {(level === 0 && link.links && link.links.length > 0 ? + this._renderCompositeLink(link, linkIndex) : this._renderAnchorLink(link) + )} + { (link.isExpanded ? this._renderLinks(link.links, ++level) : null) } + ); } - private _renderLinks(links: INavLink[]): React.ReactElement<{}> { + private _renderLinks(links: INavLink[], level: number): React.ReactElement<{}> { if (!links || !links.length) { return null; } const linkElements: React.ReactElement<{}>[] = links.map( - (link: INavLink, linkIndex: number) => this._renderLink(link, linkIndex)); + (link: INavLink, linkIndex: number) => this._renderLink(link, linkIndex, level)); return (
      @@ -80,19 +98,19 @@ export class Nav extends React.Component { const isGroupExpanded: boolean = this.state.isGroupExpanded[groupIndex] !== false; return ( -
      +
      { (group.name ? : null) }
      - { this._renderLinks(group.links) } + { this._renderLinks(group.links, 0) }
      ); @@ -107,6 +125,14 @@ export class Nav extends React.Component { ev.preventDefault(); ev.stopPropagation(); } + + private _onLinkExpandClicked(link: INavLink, ev: React.MouseEvent): void { + link.isExpanded = !link.isExpanded; + this.forceUpdate(); + + ev.preventDefault(); + ev.stopPropagation(); + } } // A tag used for resolving links. diff --git a/src/components/Overlay/Overlay.scss b/src/components/Overlay/Overlay.scss index f6f3469b6caf09..445f6baee4fbfe 100644 --- a/src/components/Overlay/Overlay.scss +++ b/src/components/Overlay/Overlay.scss @@ -1,2 +1,30 @@ @import '../../common/common'; -@import '~office-ui-fabric/src/components/Overlay/Overlay'; + +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information. + +// +// Office UI Fabric +// -------------------------------------------------- +// Modal overlay styles + +.ms-Overlay { + background-color: $ms-color-whiteTranslucent40; + position: absolute; + bottom: 0; + left: 0; + right: 0; + top: 0; + z-index: $ms-zIndex-Overlay; + + //== Modifier: Hidden overlay + // + &.ms-Overlay--none { + visibility: hidden; + } + + //== Modifier: Dark overlay + // + &.ms-Overlay--dark { + background-color: $ms-color-blackTranslucent40; + } +} diff --git a/src/components/PeoplePicker/PeoplePicker.scss b/src/components/PeoplePicker/PeoplePicker.scss index 46eaf9033655c4..1cbf4d85879635 100644 --- a/src/components/PeoplePicker/PeoplePicker.scss +++ b/src/components/PeoplePicker/PeoplePicker.scss @@ -15,7 +15,643 @@ } } -@import "~office-ui-fabric/src/components/PeoplePicker/PeoplePicker"; +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information. + +// +// Office UI Fabric +// -------------------------------------------------- +// People Picker styles + +$personaItemHeight: 42px; + + +.ms-PeoplePicker { + @include ms-font-m; + @include ms-u-normalize; + background-color: $ms-color-white; + margin-bottom: 10px; +} + +// Box that contains the search field and selected people. +.ms-PeoplePicker-searchBox { + @include ms-u-clearfix; + border: 1px solid $ms-color-neutralTertiaryAlt; + box-sizing: border-box; + min-height: 40px; + width: 100%; + + &:hover { + border-color: $ms-color-neutralSecondaryAlt; + } +} + +// Highlight the search box when the people picker is active +.ms-PeoplePicker.is-active .ms-PeoplePicker-searchBox { + border-color: $ms-color-themePrimary; +} + +// The search field. +.ms-PeoplePicker-searchField { + border: 0; + box-sizing: border-box; + display: inline-block; + float: left; + height: 38px; + outline: none; + padding-left: 8px; + width: 100%; +} + +// A selected persona, which appears within the search field. +.ms-PeoplePicker-persona { + display: inline-block; + float: left; + margin: 4px; + outline: 1px solid transparent; + + //TODO: Avoid styling child components like this. + .ms-Persona { + background-color: $ms-color-neutralLighter; + float: left; + min-height: 30px; + } +} + +// The selected persona may be in an error state. +.ms-PeoplePicker-persona.has-error { + .ms-Persona-primaryText { + color: $ms-color-error; + } +} + +// Button to remove a selected person. +.ms-PeoplePicker-personaRemove { + @include button-reset(); + background-color: $ms-color-neutralLighter; + color: $ms-color-neutralSecondary; + display: inline-block; + float: left; + text-align: center; + height: 32px; + width: 32px; + + &:hover { + background-color: $ms-color-neutralLight; + color: $ms-color-neutralPrimary; + cursor: pointer; + } + + &:focus { + background-color: $ms-color-neutralLight; + color: $ms-color-neutralPrimary; + border: 1px solid $ms-color-themePrimary; + outline: none; + } +} + +// Results area, hidden by default +.ms-PeoplePicker-results { + @include drop-shadow(); + background-color: $ms-color-white; + border: 1px solid $ms-color-neutralTertiaryAlt; + display: none; + margin-bottom: -1px; + max-width: 340px; + padding-top: 9px; + position: absolute; + z-index: ($ms-zIndex-PeoplePicker + $ms-zIndex-middle); +} + +// Show the results area when the people picker is active +.ms-PeoplePicker.is-active .ms-PeoplePicker-results { + display: block; + opacity: 1; +} + + +// One or more groups of results (ms-PeoplePicker-resultGroup) are contained in this scrollable area. +// This is limited to five results for both regular and compact sizes. +.ms-PeoplePicker-resultGroups { + max-height: 309px; + overflow-y: scroll; +} + +// A group of results +.ms-PeoplePicker-resultGroup { + border-top: 1px solid $ms-color-neutralLight; + + // The first result group needs to be bumped up 1px to account for border on ms-PeoplePicker-results + &:first-child { + border-top: 0; + } +} + +// Title for a group of results (optional) +.ms-PeoplePicker-resultGroupTitle { + color: $ms-color-themePrimary; + font-family: $ms-font-family-semilight; + font-size: $ms-font-size-s; + padding: 17px 0 0 12px; + text-transform: uppercase; + height: 40px; +} + +// List of results +.ms-PeoplePicker-resultList { + @include ms-u-normalize; + margin-bottom: -1px; + list-style-type: none; // Browser default override. +} + +// A single result in the result list +.ms-PeoplePicker-result { + position: relative; + + .ms-Persona { + &:hover { + @extend .ms-Persona.ms-Persona--darkText; + cursor: pointer; + } + + // TODO: Active style is being blocked by the inner content on IE + // http://stackoverflow.com/questions/5594102/active-css-selector-not-working-for-ie8-and-ie9?rq=1 + &:active { + background-color: $ms-color-themeLight; + } + } + + // Ensure the width is 100%. + .ms-Persona-details { + width: 100%; + } +} + +// Result buttons +.ms-PeoplePicker-resultBtn, +.ms-PeoplePicker-peopleListBtn { + @include button-reset(); + position: relative; + box-sizing: border-box; + height: 34px; + width: 100%; + background: none; + border: 0; + text-align: left; + margin: 0 0 10px 0; + padding: 0 0 0 9px; + + @media (min-width: $ms-screen-md-min) { + height: 48px; + } + + &:hover { + background-color: $ms-color-neutralLight; + outline: 1px solid transparent; + } + + &:focus { + outline: 1; + } + + &.ms-PeoplePicker-resultBtn--compact { + height: 32px; + } +} + +.ms-PeoplePicker-peopleListBtn { + margin-bottom: 0; + padding: 0; + + &:hover { + background-color: transparent; + } +} + +// Actionable icon on a result +.ms-PeoplePicker-resultAction { + @include button-reset(); + display: block; + height: 34px; + transition: background-color 0.367s $ms-ease1; + position: absolute; + right: 0; + top: 0; + width: 30px; + text-align: center; + + @media (min-width: $ms-screen-md-min) { + height: 48px; + } + + .ms-Icon { + color: $ms-color-neutralSecondary; + font-size: $ms-font-size-m-plus; + } + + &:hover { + background-color: $ms-color-neutralTertiaryAlt; + outline: 1px solid transparent; + } + + &:active { + background-color: $ms-color-themeTertiary; + } +} + +// A result can contain additional content (usually a ms-PeoplePicker-resultList of Persona components) that is hidden initially +.ms-PeoplePicker-resultAdditionalContent { + display: none; +} + +// Use the .is-expanded state to reveal the additional content +.ms-PeoplePicker-result.is-expanded { + background-color: $ms-color-neutralLighter; + margin-bottom: 11px; + + // Switch the toggle icon + .ms-PeoplePicker-resultAction .ms-Icon { + transform: rotate(180deg); + } + + // Show the content + .ms-PeoplePicker-resultAdditionalContent { + display: block; + } +} + +// After the result groups we have an area to trigger additional searches +.ms-PeoplePicker-searchMore { + border-top: 1px solid $ms-color-neutralLight; + height: 69px; + position: relative; + overflow: hidden; + + .ms-Spinner { + position: absolute; + width: 32px; + height: 32px; + top: 20px; + left: 20px; + display: none; + + .ms-Spinner-circle { + background-color: $ms-color-themePrimary; + } + } +} + +// Searching state +.ms-PeoplePicker-searchMore.is-searching { + + .ms-Spinner { + display: block; + } + + .ms-PeoplePicker-searchMoreIcon { + .ms-Icon { + display: none; + } + } + + .ms-PeoplePicker-searchMorePrimary { + color: $ms-color-themePrimary; + } + + &:hover { + background-color: transparent; + cursor: default; + } +} + +.ms-PeoplePicker-searchMoreBtn { + @include button-reset(); + position: relative; + height: 69px; + width: 100%; + padding: 0; + margin: 0; + padding-left: 70px; + text-align: left; + + &:hover { + background-color: $ms-color-neutralLight; + cursor: pointer; + } + + // TODO: Works in Chrome, but not working in IE + &:focus, + &:active { + background-color: $ms-color-themeLight; + } +} + +.ms-PeoplePicker-searchMoreBtn.ms-PeoplePicker-searchMoreBtn--compact { + height: 49px; + padding-left: 50px; +} + +// Default search icon +.ms-PeoplePicker-searchMoreIcon { + height: 70px; + position: absolute; + top: 0; + left: 0; + width: 70px; + + .ms-Icon { + color: $ms-color-neutralPrimary; + font-size: $ms-font-size-m + 2; + position: absolute; + text-align: center; + top: 27px; + width: 100%; + } +} + +// Primary text +.ms-PeoplePicker-searchMorePrimary { + padding-top: 2px; + font-family: $ms-font-family-regular; +} + +// Secondary text +.ms-PeoplePicker-searchMoreSecondary { + font-family: $ms-font-family-semilight; + font-size: $ms-font-size-xs; + color: $ms-color-neutralSecondary; +} + +// The search more area may be in a disconnected state. +.ms-PeoplePicker-searchMore.ms-PeoplePicker-searchMore--disconnected { + + // Do nothing on hover + &:hover { + background-color: inherit; + cursor: default; + } + + // Alert icon + .ms-PeoplePicker-searchMoreIcon .ms-Icon { + color: $ms-color-neutralSecondary; + } + + // Primary text + .ms-PeoplePicker-searchMorePrimary { + color: $ms-color-neutralSecondary; + font-family: $ms-font-family-semilight; + font-size: $ms-font-size-xs; + line-height: 20px; + position: relative; + top: 12px; + } +} + +// Compact size +.ms-PeoplePicker.ms-PeoplePicker--compact { + + // Limit to 5 results before scrolling. + .ms-PeoplePicker-resultGroups { + max-height: 209px; + } + + .ms-PeoplePicker-resultAction { + height: 32px; + + .ms-Icon { + margin-top: -8px; + } + } + + .ms-PeoplePicker-searchMore { + height: 49px; + + .ms-Spinner { + width: 28px; + height: 28px; + top: 12px; + left: 12px; + } + + } + + .ms-PeoplePicker-searchMore.is-searching .ms-PeoplePicker-searchMoreIcon { + background-size: 16px; + } + + .ms-PeoplePicker-searchMoreIcon { + height: 50px; + width: 50px; + + .ms-Icon { + font-size: $ms-font-size-l; + top: 0; + margin-top: 0; + line-height: 50px; + } + } + + .ms-PeoplePicker-searchMorePrimary { + font-size: $ms-font-size-s; + line-height: 45px; + } + + .ms-PeoplePicker-searchMoreSecondary { + display: none; + } +} + + +//== Modifier: Facepile and Members list +// +.ms-PeoplePicker.ms-PeoplePicker--Facepile, +.ms-PeoplePicker.ms-PeoplePicker--membersList { + .ms-PeoplePicker-searchBox { + height: 30px; + min-height: 30px; + } + + .ms-PeoplePicker-searchField { + height: 28px; + } + + .ms-Persona { + cursor: pointer; + } +} + +.ms-PeoplePicker-selected { + margin-bottom: 20px; + display: none; + + &.is-active { + display: block; + } +} + +.ms-PeoplePicker.ms-PeoplePicker--Facepile { + //= State: Searching in peoplepicker search field + &.is-searching { + .ms-PeoplePicker-results { + border-bottom: 0; + padding: 20px 0 0; + } + + .ms-PeoplePicker-peopleListHeader { + display: none; + } + } + + .ms-PeoplePicker-results { + position: relative; + border: 0; + box-shadow: none; + margin: 0; + max-width: 100%; + padding: 0; + padding-bottom: 10px; + border-bottom: 1px solid $ms-color-neutralLight; + } + + // Personas are size xs on mobile, sm on md screens and above + .ms-PeoplePicker-results, + .ms-PeoplePicker-selectedPeople { + @media (max-width: $ms-screen-sm-max) { + .ms-Persona-imageArea, + .ms-Persona-image { + width: 32px; + height: 32px; + } + + .ms-Persona-placeholder { + font-size: 28px; + top: 6px; + } + + .ms-Persona-initials { + font-size: $ms-font-size-s; + line-height: 32px; + } + + .ms-Persona-presence { + left: 19px; + } + + .ms-Persona-details { + padding-left: 8px; + } + + .ms-Persona-primaryText { + font-size: $ms-font-size-m; + padding-top: 3px; + } + + .ms-Persona-secondaryText { + display: none; + } + } + + @media (min-width: $ms-screen-md-min) { + .ms-Persona .ms-Persona-secondaryText { + display: block; + } + } + } + + .ms-PeoplePicker-resultBtn, + .ms-PeoplePicker-peopleListBtn { + @media (min-width: $ms-screen-md-min) { + height: $personaItemHeight; + } + } + + .ms-PeoplePicker-resultAction { + @media (min-width: $ms-screen-md-min) { + height: $personaItemHeight; + } + } + + .ms-Persona.ms-Persona--selectable { + padding: 0; + } + + .ms-PeoplePicker-searchMore { + display: none; + + &.is-active { + display: block; + } + } + + .ms-PeoplePicker-searchMore, + .ms-PeoplePicker-searchMoreBtn, + .ms-PeoplePicker-searchMoreIcon { + height: 48px; + } + + .ms-PeoplePicker-searchMoreBtn { + padding-left: 48px; + } + + .ms-PeoplePicker-searchMoreIcon { + width: 48px; + } + + .ms-PeoplePicker-searchMorePrimary { + font-size: $ms-font-size-s; + line-height: 48px; + } + + .ms-PeoplePicker-searchMoreIcon .ms-Icon { + top: 0; + line-height: 48px; + } + + .ms-Spinner { + top: 16px; + left: 14px; + height: 20px; + width: 20px; + } +} + +.ms-PeoplePicker.ms-PeoplePicker--Facepile { + .ms-PersonaCard { + display: none; + position: absolute; + height: 200px; + + &.is-active { + display: block; + } + } +} + +.ms-PeoplePicker-selectedHeader, +.ms-PeoplePicker-peopleListHeader { + color: $ms-color-themePrimary; + font-size: $ms-font-size-s; + font-family: $ms-font-family-regular; + height: 50px; + line-height: 50px; +} + +.ms-PeoplePicker-selectedPeople, +.ms-PeoplePicker-peopleList { + @include ms-u-normalize; + list-style: none; +} + +.ms-PeoplePicker-selectedPerson { + margin-bottom: 8px; + position: relative; +} + +.ms-PeoplePicker-peopleListItem { + margin-bottom: 6px; + position: relative; +} + +// TODO: remove overides below. .ms-PeoplePicker-searchBox { min-height: 42px; // this is accounting for the border. Actual inner height is 40px; diff --git a/src/components/Persona/Persona.scss b/src/components/Persona/Persona.scss index 3494001641955b..2c1c9530766efc 100644 --- a/src/components/Persona/Persona.scss +++ b/src/components/Persona/Persona.scss @@ -1,5 +1,602 @@ @import '../../common/common'; -@import "~office-ui-fabric/src/components/Persona/Persona"; + +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information. + +// +// Office UI Fabric +// -------------------------------------------------- +// Persona styles + + +//= Colors used in the initials color block +$ms-color-initials-lightBlue: #6ba5e7; +$ms-color-initials-blue: #2d89ef; +$ms-color-initials-darkBlue: #2b5797; +$ms-color-initials-teal: #00aba9; +$ms-color-initials-lightGreen: #99b433; +$ms-color-initials-green: #00a300; +$ms-color-initials-darkGreen: #1e7145; +$ms-color-initials-lightPink: #e773bd; +$ms-color-initials-pink: #ff0097; +$ms-color-initials-magenta: #7e3878; +$ms-color-initials-purple: #603cba; +$ms-color-initials-black: #1d1d1d; +$ms-color-initials-orange: #da532c; +$ms-color-initials-red: #ee1111; +$ms-color-initials-darkRed: #b91d47; + + +.ms-Persona { + @include ms-font-m; + @include ms-u-normalize; + display: table; + line-height: 1; + position: relative; +} + +.ms-Persona-imageArea { + position: relative; + display: block; + overflow: hidden; + text-align: center; + width: 48px; + height: 48px; + border-radius: 50%; + z-index: $ms-zIndex-back; + + @media screen and (-ms-high-contrast: active) { + border: 1px solid $ms-color-white; + } + + @media screen and (-ms-high-contrast: black-on-white) { + border: 1px solid $ms-color-black; + } +} + +//= Note: The doughboy placeholder is being deprecated. +// The initials color block (.ms-Persona-initials) will be used going forward +// as a fallback when the persona does not have an image. +.ms-Persona-placeholder { + color: $ms-color-white; + position: absolute; + right: 0; + left: 0; + font-size: 47px; + top: 9px; +} + +.ms-Persona-initials { + color: $ms-color-white; + font-size: $ms-font-size-l; + font-family: $ms-font-family-light; + line-height: 48px; + + &.ms-Persona-initials--lightBlue { + background-color: $ms-color-initials-lightBlue; + } + &.ms-Persona-initials--blue { + background-color: $ms-color-initials-blue; + } + &.ms-Persona-initials--darkBlue { + background-color: $ms-color-initials-darkBlue; + } + &.ms-Persona-initials--teal { + background-color: $ms-color-initials-teal; + } + &.ms-Persona-initials--lightGreen { + background-color: $ms-color-initials-lightGreen; + } + &.ms-Persona-initials--green { + background-color: $ms-color-initials-green; + } + &.ms-Persona-initials--darkGreen { + background-color: $ms-color-initials-darkGreen; + } + &.ms-Persona-initials--lightPink { + background-color: $ms-color-initials-lightPink; + } + &.ms-Persona-initials--pink { + background-color: $ms-color-initials-pink; + } + &.ms-Persona-initials--magenta { + background-color: $ms-color-initials-magenta; + } + &.ms-Persona-initials--purple { + background-color: $ms-color-initials-purple; + } + &.ms-Persona-initials--black { + background-color: $ms-color-initials-black; + } + &.ms-Persona-initials--orange { + background-color: $ms-color-initials-orange; + } + &.ms-Persona-initials--red { + background-color: $ms-color-initials-red; + } + &.ms-Persona-initials--darkRed { + background-color: $ms-color-initials-darkRed; + } +} + +.ms-Persona-image { + display: table-cell; + margin-right: 10px; + position: absolute; + top: 0; + left: 0; + width: 48px; + height: 48px; +} + +.ms-Persona-image[src=""] { + display: none; +} + +.ms-Persona-presence { + background-color: $ms-color-presence-available; + position: absolute; + height: 12px; + width: 12px; + border-radius: 50%; + top: auto; + left: 34px; + bottom: -1px; + border: 2px solid $ms-color-white; +} + +.ms-Persona-details { + display: table-cell; + padding: 0 12px; + vertical-align: middle; + overflow: hidden; +} + +.ms-Persona-primaryText, +.ms-Persona-secondaryText, +.ms-Persona-tertiaryText, +.ms-Persona-optionalText { + @include noWrap(); + width: 190px; + overflow: hidden; + text-overflow: ellipsis; +} + +.ms-Persona-primaryText { + color: $ms-color-neutralPrimary; + font-family: $ms-font-family-regular; + font-size: $ms-font-size-l; + margin-top: -3px; + line-height: 1.4; +} + +.ms-Persona-secondaryText, +.ms-Persona-tertiaryText, +.ms-Persona-optionalText { + color: $ms-color-neutralSecondary; + font-family: $ms-font-family-regular; + font-size: $ms-font-size-s; + white-space: nowrap; + line-height: 1.3; +} + +.ms-Persona-secondaryText { + padding-top: 3px; +} + +.ms-Persona-tertiaryText, +.ms-Persona-optionalText { + padding-top: 5px; + display: none; // Hidden on default persona +} + + +//== Modifier: Persona with square images +// +.ms-Persona.ms-Persona--square { + .ms-Persona-imageArea { + background-color: $ms-color-neutralTertiary; + border-radius: 0; + } + + .ms-Persona-presence { + top: 0; + left: 0; + bottom: auto; + right: auto; + height: 48px; + width: 5px; + border-radius: 0; + border: 0; + + @media screen and (-ms-high-contrast: active) { + border: 1px solid $ms-color-white; + } + + @media screen and (-ms-high-contrast: black-on-white) { + border: 1px solid $ms-color-black; + } + } +} + + +//== Modifier: Tiny Persona +// +.ms-Persona.ms-Persona--tiny { + height: 30px; + display: inline-block; + + .ms-Persona-imageArea { + overflow: visible; + background: transparent; + height: 0; + width: 0; + } + + .ms-Persona-presence { + right: auto; + top: 10px; + left: 0; + border: 0; + + @media screen and (-ms-high-contrast: active) { + top: 9px; + border: 1px solid $ms-color-white; + } + + @media screen and (-ms-high-contrast: black-on-white) { + border: 1px solid $ms-color-black; + } + } + + .ms-Persona-details { + padding-left: 20px; + } + + .ms-Persona-primaryText { + font-size: $ms-font-size-m; + padding-top: 9px; + } + + .ms-Persona-secondaryText { + display: none; + } +} + + +//== Modifier: Tiny Persona with read only state +// +// This variant includes a semicolon, and is +// most often presented within a People Picker. +.ms-Persona.ms-Persona--tiny.ms-Persona--readonly { + padding: 0; + background-color: transparent; + + .ms-Persona-primaryText:after { + content: ';'; + } +} + + +//== Modifier: Tiny Square Persona +// +.ms-Persona.ms-Persona--square.ms-Persona--tiny { + .ms-Persona-presence { + height: 12px; + width: 12px; + top: 10px; + } +} + + +//== Modifier: Extra Small Persona +// +.ms-Persona.ms-Persona--xs { + .ms-Persona-imageArea, + .ms-Persona-image { + width: 32px; + height: 32px; + } + + .ms-Persona-placeholder { + font-size: 28px; + top: 6px; + } + + .ms-Persona-initials { + font-size: $ms-font-size-s; + line-height: 32px; + } + + .ms-Persona-presence { + left: 19px; + } + + .ms-Persona-details { + padding-left: 8px; + } + + .ms-Persona-primaryText { + font-size: $ms-font-size-m; + padding-top: 3px; + } + + .ms-Persona-secondaryText { + display: none; + } +} + + +//== Modifier: Extra Small Square Persona +// +.ms-Persona.ms-Persona--square.ms-Persona--xs { + .ms-Persona-presence { + height: 32px; + width: 4px; + left: 0; + } +} + + +//== Modifier: Small Persona +// +.ms-Persona.ms-Persona--sm { + .ms-Persona-imageArea, + .ms-Persona-image { + width: 40px; + height: 40px; + } + + .ms-Persona-placeholder { + font-size: 38px; + top: 5px; + } + + .ms-Persona-initials { + font-size: $ms-font-size-m; + line-height: 40px; + } + + .ms-Persona-presence { + left: 27px; + } + + .ms-Persona-details { + padding-left: 8px; + } + + .ms-Persona-primaryText { + font-size: $ms-font-size-m; + } + + .ms-Persona-primaryText, + .ms-Persona-secondaryText { + padding-top: 1px; + } +} + + +//== Modifier: Small Square Persona +// +.ms-Persona.ms-Persona--square.ms-Persona--sm { + .ms-Persona-presence { + height: 40px; + width: 4px; + left: 0; + } +} + + +//== Modifier: Large Persona +// +.ms-Persona.ms-Persona--lg { + .ms-Persona-imageArea, + .ms-Persona-image { + width: 72px; + height: 72px; + } + + .ms-Persona-placeholder { + font-size: 67px; + top: 10px; + } + + .ms-Persona-initials { + font-size: $ms-font-size-xxl; + line-height: 72px; + } + + .ms-Persona-presence { + left: 49px; + } + + .ms-Persona-secondaryText { + padding-top: 3px; + } + + .ms-Persona-tertiaryText { + padding-top: 5px; + } + + .ms-Persona-tertiaryText { + display: block; // Show tertiary text + } +} + + +//== Modifier: Large Square Persona +// +.ms-Persona.ms-Persona--square.ms-Persona--lg { + .ms-Persona-presence { + height: 72px; + width: 7px; + left: 0; + } +} + + +//== Modifier: Extra Large Persona +// +.ms-Persona.ms-Persona--xl { + .ms-Persona-imageArea, + .ms-Persona-image { + width: 100px; + height: 100px; + } + + .ms-Persona-placeholder { + font-size: 95px; + top: 12px; + } + + .ms-Persona-initials { + font-size: $ms-font-size-su; + line-height: 100px; + } + + .ms-Persona-presence { + height: 20px; + width: 20px; + left: 71px; + } + + .ms-Persona-details { + padding-left: 20px; + } + + .ms-Persona-primaryText { + font-size: $ms-font-size-xl; + font-family: $ms-font-family-semilight; + margin-top: 0; + } + + .ms-Persona-secondaryText { + padding-top: 2px; + } + + .ms-Persona-tertiaryText, + .ms-Persona-optionalText { + padding-top: 5px; + display: block; // Show tertiary and optional text + } +} + + +//== Modifier: Extra Large Square Persona +// +.ms-Persona.ms-Persona--square.ms-Persona--xl { + .ms-Persona-presence { + height: 100px; + width: 9px; + left: 0; + } +} + + +//== Modifier: Persona with darker text +// +// Note: Typically applied when the component has a colored background. +.ms-Persona.ms-Persona--darkText { + .ms-Persona-primaryText { + color: $ms-color-neutralDark; + } + + .ms-Persona-secondaryText, + .ms-Persona-tertiaryText, + .ms-Persona-optionalText { + color: $ms-color-neutralPrimary; + } +} + + +//== Modifier: Selectable Persona +// +.ms-Persona.ms-Persona--selectable { + cursor: pointer; + padding: 0 10px; + + &:not(.ms-Persona--xl) { + &:hover, + &:focus { + background-color: $ms-color-themeLighter; + outline: 1px solid transparent; + } + } +} + + +//== Presence indicator variants. + +//== Modifier: Persona with available presence +// +.ms-Persona.ms-Persona--available { + .ms-Persona-presence { + background-color: $ms-color-presence-available; + } +} + + +//== Modifier: Persona with away presence +// +.ms-Persona.ms-Persona--away { + .ms-Persona-presence { + background-color: $ms-color-presence-away; + } +} + + +//== Modifier: Persona with blocked presence +// +.ms-Persona.ms-Persona--blocked { + .ms-Persona-presence { + background-color: $ms-color-presence-blocked-background; + // Use a gradient to include the stripe on modern browsers. + background-image: linear-gradient( to bottom, $ms-color-presence-blocked-background 0%, $ms-color-presence-blocked-background 48%, $ms-color-presence-blocked-line 40%, $ms-color-presence-blocked-line 58%, $ms-color-presence-blocked-background 52%, $ms-color-presence-blocked-background 100% ); + } +} + + +//== Modifier: Persona with busy presence +// +.ms-Persona.ms-Persona--busy { + .ms-Persona-presence { + background-color: $ms-color-presence-busy-average; + // Replace solid background with stripes on modern browsers. + background: repeating-linear-gradient( -45deg, $ms-color-presence-busy-stripe-light, $ms-color-presence-busy-stripe-light 1px, $ms-color-presence-busy-stripe-dark 0px, $ms-color-presence-busy-stripe-dark 2px ); + } +} + + +//== Modifier: Square Persona with busy presence +// +.ms-Persona.ms-Persona--busy.ms-Persona--square { + .ms-Persona-presence { + background-color: $ms-color-presence-busy-average; + // Replace solid background with stripes on modern browsers. + background: repeating-linear-gradient( -45deg, $ms-color-presence-busy-stripe-light, $ms-color-presence-busy-stripe-light 3px, $ms-color-presence-busy-stripe-dark 3px, $ms-color-presence-busy-stripe-dark 6px ); + } +} + + +//== Modifier: Persona with do not disturb presence +// +.ms-Persona.ms-Persona--dnd { + .ms-Persona-presence { + background-color: $ms-color-presence-dnd-background; + // Use a gradient to include the stripe on modern browsers. + background-image: linear-gradient( to bottom, $ms-color-presence-dnd-background 0%, $ms-color-presence-dnd-background 48%, $ms-color-presence-dnd-line 48%, $ms-color-presence-dnd-line 52%, $ms-color-presence-dnd-background 52%, $ms-color-presence-dnd-background 100% ); + } +} + + +//== Modifier: Persona with offline presence +// +.ms-Persona.ms-Persona--offline { + .ms-Persona-presence { + background-color: $ms-color-presence-offline; + } +} + +// TODO: Remove overrides below // div.ms-Persona-image to overwrite Fabric's ms-Persona-image div.ms-Persona-image { diff --git a/src/components/Pivot/Pivot.scss b/src/components/Pivot/Pivot.scss index 3358bd61f62fc8..5b373f4aceaf3c 100644 --- a/src/components/Pivot/Pivot.scss +++ b/src/components/Pivot/Pivot.scss @@ -1,5 +1,249 @@ @import '../../common/common'; -@import "~office-ui-fabric/src/components/Pivot/Pivot"; + +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information. + +// +// Office UI Fabric +// -------------------------------------------------- +// Pivot and tab styles + + +.ms-Pivot { + @include ms-font-m; + @include ms-u-normalize; + height: 40px; + list-style-type: none; + overflow-x: hidden; + white-space: nowrap; +} + +.ms-Pivot-link { + color: $ms-color-neutralPrimary; + display: inline-block; + position: relative; + font-family: $ms-font-family-regular; + font-size: $ms-font-size-m-plus; + line-height: 40px; + margin-right: 8px; + + &:after { + content: ''; + width: 100%; + position: absolute; + display: none; + bottom: 0; + left: 0; + height: 2px; + background-color: $ms-color-themePrimary; + + @media screen and (-ms-high-contrast: active) { + background-color: $ms-color-contrastBlackSelected; + } + + @media screen and (-ms-high-contrast: black-on-white) { + background-color: $ms-color-contrastWhiteSelected; + } + } + + &:hover, + &:focus, + &:active { + color: $ms-color-black; + cursor: pointer; + + + .ms-Pivot-dropdownIcon { + color: $ms-color-neutralDark; + } + } + + &:active { + font-family: $ms-font-family-semibold; + + @media screen and (-ms-high-contrast: active) { + color: $ms-color-contrastBlackSelected; + } + + @media screen and (-ms-high-contrast: black-on-white) { + color: $ms-color-contrastWhiteSelected; + } + + &:after { + display: block; + } + } + + //== State: Selected + &.is-selected { + color: $ms-color-black; + font-family: $ms-font-family-semibold; + + @media screen and (-ms-high-contrast: active) { + color: $ms-color-contrastBlackSelected; + } + + @media screen and (-ms-high-contrast: black-on-white) { + color: $ms-color-contrastWhiteSelected; + } + + &:after { + display: block; + } + + + .ms-Pivot-dropdownIcon { + color: $ms-color-neutralDark; + } + } +} + +.ms-Pivot-dropdownIcon { + font-size: $ms-font-size-m-plus + 1; + position: relative; + top: 2px; +} + +// Overflow (ellipsis) +.ms-Pivot-link.ms-Pivot-link--overflow { + color: $ms-color-neutralSecondary; + + &.is-selected { + color: $ms-color-themePrimary; + } + + &:hover:not(.is-selected), + &:focus:not(.is-selected) { + color: $ms-color-neutralDark; + } + + &:active { + &:after { + display: none; + } + } +} + +// Ellipsis icon +.ms-Pivot-ellipsis { + font-size: $ms-font-size-m-plus; + position: relative; + top: 0; +} + + +//== Modifier: Large Pivots +// +.ms-Pivot.ms-Pivot--large { + .ms-Pivot-link { + font-size: $ms-font-size-l; + + &:active { + font-family: $ms-font-family-regular; + } + + &.is-selected { + font-family: $ms-font-family-regular; + } + } + + .ms-Pivot-link.ms-Pivot-link--overflow { + &:after { + font-size: $ms-font-size-l; + } + } +} + + +//== Modifier: Tabs +// +.ms-Pivot.ms-Pivot--tabs { + height: 40px; + + .ms-Pivot-link { + height: 40px; + background-color: $ms-color-neutralLighter; + line-height: 40px; + margin-right: -2px; // Remove space next to inline-block element + padding: 0 10px; + font-family: $ms-font-family-semilight !important; + + &:hover:not(.is-selected):not(.ms-Pivot-link--overflow), + &:focus:not(.is-selected):not(.ms-Pivot-link--overflow) { + color: $ms-color-black; + } + + &:active { + color: $ms-color-white !important; + background-color: $ms-color-themePrimary; + font-family: $ms-font-family-semilight; + + @media screen and (-ms-high-contrast: active) { + background-color: $ms-color-contrastBlackSelected; + color: $ms-color-black; + } + + @media screen and (-ms-high-contrast: black-on-white) { + background-color: $ms-color-contrastWhiteSelected; + color: $ms-color-white; + } + } + + //== State: Selected + &.is-selected { + background-color: $ms-color-themePrimary; + color: $ms-color-white; + font-family: $ms-font-family-semilight; + + @media screen and (-ms-high-contrast: active) { + background-color: $ms-color-contrastBlackSelected; + color: $ms-color-black; + } + + @media screen and (-ms-high-contrast: black-on-white) { + background-color: $ms-color-contrastWhiteSelected; + color: $ms-color-white; + } + } + } + + .ms-Pivot-link.ms-Pivot-link--overflow { + &:hover:not(.is-selected), + &:focus:not(.is-selected) { + background-color: $ms-color-white; + } + + &:active { + background-color: $ms-color-themePrimary !important; + } + } +} + + + +@media (min-width: $ms-screen-lg-min) { + .ms-Pivot-link { + font-size: $ms-font-size-m; + } + + .ms-Pivot-link.ms-Pivot-link--overflow { + &:after { + font-size: $ms-font-size-m; + } + } +} + + + +// All high contrast styling rules +@media screen and (-ms-high-contrast: active) { + .ms-Pivot.ms-Pivot--tabs { + .ms-Pivot-link { + &.is-selected { + font-family: $ms-font-family-semibold; + } + } + } +} + +// TODO: Remove override below. .ms-Pivot { position: relative; diff --git a/src/components/ProgressIndicator/ProgressIndicator.scss b/src/components/ProgressIndicator/ProgressIndicator.scss index 4445ff91e60fd2..c9ce0334236ad0 100644 --- a/src/components/ProgressIndicator/ProgressIndicator.scss +++ b/src/components/ProgressIndicator/ProgressIndicator.scss @@ -1,5 +1,63 @@ @import "~office-ui-fabric/src/sass/Fabric.Common"; -@import "~office-ui-fabric/src/components/ProgressIndicator/ProgressIndicator"; + +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information. + +// +// Office UI Fabric +// -------------------------------------------------- +// ProgressIndicator Styles + + +$ProgressIndicatorMarginBetweenText: 8px; +$ProgressIndicatorButtonsWidth: 218px; +$ProgressIndicatorTextHeight: 18px; + +.ms-ProgressIndicator-itemName { + @include ms-font-m(); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + padding-top: $ProgressIndicatorMarginBetweenText / 2; + line-height: $ProgressIndicatorTextHeight + 2; +} + +.ms-ProgressIndicator-itemDescription { + @include ms-font-m(); + color: $ms-color-neutralSecondaryAlt; + font-size: 11px; + line-height: $ProgressIndicatorTextHeight; +} + +.ms-ProgressIndicator-itemProgress { + position: relative; + width: 180px; + height: 2px; + padding: $ProgressIndicatorMarginBetweenText 0; +} + +.ms-ProgressIndicator-progressTrack { + position: absolute; + width: 100%; + height: 2px; + background-color: $ms-color-neutralLight; + outline: 1px solid transparent; +} + +.ms-ProgressIndicator-progressBar { + position: absolute; + height: 2px; + background-color: $ms-color-themePrimary; + + @media screen and (-ms-high-contrast: active) { + background-color: $ms-color-white; + } + + @media screen and (-ms-high-contrast: black-on-white) { + background-color: $ms-color-black; + } +} + +// TODO: Remove override below. .ms-ProgressIndicator-progressBar.smoothTransition { transition-property: width; diff --git a/src/components/SearchBox/SearchBox.scss b/src/components/SearchBox/SearchBox.scss index ea2aafb647cf11..9d96eeb44ad672 100644 --- a/src/components/SearchBox/SearchBox.scss +++ b/src/components/SearchBox/SearchBox.scss @@ -1,6 +1,114 @@ @import '../../common/common'; -@import "~office-ui-fabric/src/components/Label/Label"; -@import "~office-ui-fabric/src/components/SearchBox/SearchBox"; + +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information. + +// +// Office UI Fabric +// -------------------------------------------------- +// Search box styles + + +.ms-SearchBox { + @include ms-font-m; + @include ms-u-normalize; + position: relative; + margin-bottom: 10px; + display: inline-block; + + // State: Disabled searchbox + &.is-disabled { + + .ms-SearchBox-icon { + color: $ms-color-neutralTertiaryAlt; + } + .ms-SearchBox-field { + background-color: $ms-color-neutralLighter; + border-color: $ms-color-neutralLighter; + pointer-events: none; + cursor: default; + } + } + + // State: Active searchbox + &.is-active { + .ms-SearchBox-closeButton { + display: block; + outline: transparent 1px solid; + } + } +} + +.ms-SearchBox-field { + position: relative; + @include ms-u-normalize; + border: 1px solid $ms-color-themeTertiary; + outline: transparent 1px solid; + border-radius: 0; + font-family: $ms-font-family-semilight; + font-size: $ms-font-size-m; + color: $ms-color-black; + height: 32px; + padding: 6px 3px 7px 10px; + width: 180px; + background-color: transparent; + z-index: $ms-zIndex-middle; + + &.hovering { + border-color: $ms-color-themePrimary; + background-color: $ms-color-themeLighter; + + & + .ms-SearchBox-label { + color: $ms-color-black; + + .ms-Icon { + color: $ms-color-neutralPrimary; + } + } + } + + &:focus { + padding: 6px 32px 7px 10px; + border-color: $ms-color-themePrimary; + background-color: $ms-color-themeLighter; + } + + &::-ms-clear { + display: none; + } +} + +.ms-SearchBox-closeButton { + border: none; + cursor: pointer; + position: absolute; + right: 0; + top: 0; + height: 32px; + width: 32px; + background-color: $ms-color-themePrimary; + text-align: center; + display: none; + font-size: $ms-font-size-l; + color: $ms-color-white; + z-index: $ms-zIndex-front; +} + +.ms-SearchBox-label { + position: absolute; + top: 0; + left: 0; + padding-left: 8px; + line-height: 32px; + color: $ms-color-neutralSecondary; +} + +.ms-SearchBox-icon { + margin-right: 7px; + font-size: $ms-font-size-l; + color: $ms-color-neutralSecondaryAlt; +} + +// TODO: Remove overrides below. // Override Fabric so that the SearchBox takes up 100% of the parent // container's width diff --git a/src/components/Slider/Slider.tsx b/src/components/Slider/Slider.tsx index 15dc626bc23006..ca6a3e0d5ed9ac 100644 --- a/src/components/Slider/Slider.tsx +++ b/src/components/Slider/Slider.tsx @@ -97,6 +97,11 @@ export class Slider extends BaseComponent implements 'ms-Slider-showTransitions': ( renderedValue === value ) })} { ...disabled ? { } : { 'tabIndex': 0 } } + id={ this._id } + role='slider' + aria-valuenow={ value } + aria-valuemin={ min } + aria-valuemax={ max } { ...onMouseDownProp } { ...onTouchStartProp } { ...onKeyDownProp } @@ -107,13 +112,8 @@ export class Slider extends BaseComponent implements >

      diff --git a/src/demo/pages/ContextualMenuPage/examples/ContextualMenu.Basic.Example.tsx b/src/demo/pages/ContextualMenuPage/examples/ContextualMenu.Basic.Example.tsx index bdf436efe4c2fd..2f24a49662a4f8 100644 --- a/src/demo/pages/ContextualMenuPage/examples/ContextualMenu.Basic.Example.tsx +++ b/src/demo/pages/ContextualMenuPage/examples/ContextualMenu.Basic.Example.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { ContextualMenu, DirectionalHint, Button } from '../../../../index'; +import { ContextualMenu, DirectionalHint, Button, getRTL } from '../../../../index'; import './ContextualMenuExample.scss'; export class ContextualMenuBasicExample extends React.Component { @@ -21,7 +21,7 @@ export class ContextualMenuBasicExample extends React.Component { targetPoint={this.state.target} useTargetPoint={true} onDismiss={this._onDismiss} - directionalHint={DirectionalHint.bottomLeftEdge} + directionalHint={ getRTL() ? DirectionalHint.bottomRightEdge : DirectionalHint.bottomLeftEdge} items={ [ { diff --git a/src/demo/pages/ContextualMenuPage/examples/ContextualMenuExample.scss b/src/demo/pages/ContextualMenuPage/examples/ContextualMenuExample.scss index e251638b8487cc..83762b9af212f2 100644 --- a/src/demo/pages/ContextualMenuPage/examples/ContextualMenuExample.scss +++ b/src/demo/pages/ContextualMenuPage/examples/ContextualMenuExample.scss @@ -27,8 +27,12 @@ .ms-ContextualMenu-customizationExample-item { display: inline-block; - padding: 3px; - margin: 2px; + width: 39px; + height: 39px; + line-height: 39px; + text-align: center; + vertical-align: middle; + margin-bottom: 8px; &:hover { background-color: #eaeaea; @@ -36,16 +40,16 @@ } .ms-ContextualMenu-customizationExample-categoriesList { - max-height: 200px; - overflow-y: auto; margin: 0px; padding: 0; list-style-type: none; } .ms-ContextualMenu-customizationExample-categorySwatch { - @include margin-left(-10px); - @include margin-right(10px); + margin: 8px; + width: 24px; + height: 24px; + vertical-align: top; } .ms-ContextualMenu-example-clickableArea { diff --git a/src/demo/pages/DetailsListPage/DetailsListPage.tsx b/src/demo/pages/DetailsListPage/DetailsListPage.tsx index f1c9582db481f5..890cebf2e90ba4 100644 --- a/src/demo/pages/DetailsListPage/DetailsListPage.tsx +++ b/src/demo/pages/DetailsListPage/DetailsListPage.tsx @@ -26,7 +26,7 @@ export class DetailsListPage extends React.Component { DetailsList is a derivative of List - and provides a sortable, filterable, groupable, justified table for rendering large sets of items. This component replaces the Table Component. + and provides a sortable, filterable, justified table for rendering large sets of items. This component replaces the Table Component.

      Examples

      diff --git a/src/demo/pages/GroupedListPage/GroupedListPage.tsx b/src/demo/pages/GroupedListPage/GroupedListPage.tsx new file mode 100644 index 00000000000000..06bfd558a151e2 --- /dev/null +++ b/src/demo/pages/GroupedListPage/GroupedListPage.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { + ExampleCard, + PropertiesTableSet +} from '../../components/index'; + +import { GroupedListBasicExample } from './examples/GroupedList.Basic.Example'; +import { GroupedListCustomExample } from './examples/GroupedList.Custom.Example'; + +const GroupedListBasicExampleCode = require('./examples/GroupedList.Basic.Example.tsx'); +const GroupedListCustomExampleCode = require('./examples/GroupedList.Custom.Example.tsx'); + +export class GroupedListPage extends React.Component { + + public render() { + return ( +
      +

      GroupedList

      + +

      Allows you to render a set of items as multiple lists with various grouping properties.

      + +

      Examples

      + + + + + + + +
      + ); + } + +} diff --git a/src/demo/pages/GroupedListPage/examples/GroupedList.Basic.Example.tsx b/src/demo/pages/GroupedListPage/examples/GroupedList.Basic.Example.tsx new file mode 100644 index 00000000000000..8988d66399c23f --- /dev/null +++ b/src/demo/pages/GroupedListPage/examples/GroupedList.Basic.Example.tsx @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { + GroupedList, + IGroup +} from '../../../../components/GroupedList/index'; +import { IColumn } from '../../../../DetailsList'; +import { DetailsRow } from '../../../../components/DetailsList/DetailsRow'; +import { + FocusZone +} from '../../../../FocusZone'; +import { + Selection, + SelectionMode, + SelectionZone +} from '../../../../utilities/selection/index'; + +import { + createListItems, + createGroups +} from '../../../utilities/data'; + +const groupCount = 15; +const groupDepth = 3; +const items = createListItems(Math.pow(groupCount, groupDepth + 1)); + +export class GroupedListBasicExample extends React.Component { + private _selection: Selection; + private _groups: IGroup[]; + + constructor() { + super(); + this._onRenderCell = this._onRenderCell.bind(this); + this._selection = new Selection; + this._selection.setItems(items); + + this._groups = createGroups(groupCount, groupDepth, 0, groupCount); + } + + public render() { + return ( + + + + + + ); + } + + private _onRenderCell(nestingDepth: number, item: any, itemIndex: number) { + let { + _selection: selection + } = this; + return ( + { + return { + key: value, + name: value, + fieldName: value, + minWidth: 300 + }; + }) + } + groupNestingDepth={ nestingDepth } + item={ item } + itemIndex={ itemIndex } + selection={ selection } + selectionMode={ SelectionMode.multiple } + canSelectItem={ () => true } + /> + ); + } +} diff --git a/src/demo/pages/GroupedListPage/examples/GroupedList.Custom.Example.scss b/src/demo/pages/GroupedListPage/examples/GroupedList.Custom.Example.scss new file mode 100644 index 00000000000000..76006b885832d1 --- /dev/null +++ b/src/demo/pages/GroupedListPage/examples/GroupedList.Custom.Example.scss @@ -0,0 +1,20 @@ +.ms-GroupedListExample-header, +.ms-GroupedListExample-footer { + min-width: 300px; + min-height: 40px; + line-height: 40px; + padding-left: 16px; +} + +.ms-GroupedListExample-name { + display: inline-block; + overflow: hidden; + height: 24px; + cursor: default; + padding: 8px; + box-sizing: border-box; + vertical-align: top; + background: none; + border: none; + padding-left: 32px; +} diff --git a/src/demo/pages/GroupedListPage/examples/GroupedList.Custom.Example.tsx b/src/demo/pages/GroupedListPage/examples/GroupedList.Custom.Example.tsx new file mode 100644 index 00000000000000..750363ccfbc2ac --- /dev/null +++ b/src/demo/pages/GroupedListPage/examples/GroupedList.Custom.Example.tsx @@ -0,0 +1,67 @@ +import * as React from 'react'; +import { + GroupedList, + IGroup +} from '../../../../components/GroupedList/index'; + +import { createListItems } from '../../../utilities/data'; +import './GroupedList.Custom.Example.scss'; + +export class GroupedListCustomExample extends React.Component { + private _items: any[]; + private _groups: IGroup[]; + + constructor() { + super(); + + this._items = createListItems(20); + this._groups = Array.apply(null, Array(4)).map((value, index): IGroup => { + return { + count: 5, + key: 'group' + index, + name: 'group ' + index, + startIndex: 5 * index, + level: 0, + onRenderFooter: this._onRenderFooter, + onRenderHeader: this._onRenderHeader + }; + }); + } + + public render() { + return ( + + ); + } + + private _onRenderCell(nestingDepth: number, item: any, itemIndex: number) { + return ( +
      + + { item.name } + +
      + ); + } + + private _onRenderHeader(group: IGroup): React.ReactNode { + return ( +
      + This is a custom header for { group.name } +
      + ); + } + + private _onRenderFooter(group: IGroup): React.ReactNode { + return ( +
      + This is a custom footer for { group.name } +
      + ); + } +} diff --git a/src/demo/pages/ListPage/examples/List.Mail.Example.scss b/src/demo/pages/ListPage/examples/List.Mail.Example.scss index 0eefed8343fe89..59dbfd77ba7a44 100644 --- a/src/demo/pages/ListPage/examples/List.Mail.Example.scss +++ b/src/demo/pages/ListPage/examples/List.Mail.Example.scss @@ -1,5 +1,235 @@ @import '../../../../common/common'; -@import '~office-ui-fabric/src/components/ListItem/ListItem.scss'; + +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information. + +// +// Office UI Fabric +// -------------------------------------------------- +// List item styles + + +.ms-ListItem { + @include ms-font-m; + @include ms-u-normalize; + @include ms-u-clearfix; + padding: 9px 28px 3px; + position: relative; + display: block; +} + +.ms-ListItem-primaryText, +.ms-ListItem-secondaryText, +.ms-ListItem-tertiaryText { + @include noWrap; + display: block; +} + +.ms-ListItem-primaryText { + color: $ms-color-neutralDark; + font-family: $ms-font-family-semilight; + font-size: $ms-font-size-xl; + padding-right: 80px; // Prevent overlap with up to three actions. + position: relative; + top: -4px; +} + +.ms-ListItem-secondaryText { + color: $ms-color-neutralPrimary; + font-family: $ms-font-family-regular; + font-size: $ms-font-size-m; + line-height: 25px; + position: relative; + top: -7px; + padding-right: 30px; +} + +.ms-ListItem-tertiaryText { + color: $ms-color-neutralSecondaryAlt; + font-family: $ms-font-family-semilight; + font-size: $ms-font-size-m; + position: relative; + top: -9px; + margin-bottom: -4px; + padding-right: 30px; +} + +.ms-ListItem-metaText { + color: $ms-color-neutralPrimary; + font-family: $ms-font-family-semilight; + font-size: $ms-font-size-xs; + position: absolute; + right: 30px; + top: 39px; +} + +.ms-ListItem-image { + float: left; + height: 70px; + margin-left: -8px; // Images sit closer to the edge than text. + margin-right: 10px; + width: 70px; +} + +.ms-ListItem-selectionTarget { + display: none; +} + +.ms-ListItem-actions { + max-width: 80px; // Up to three actions. + position: absolute; + right: 30px; + text-align: right; + top: 10px; +} + +.ms-ListItem-action { + color: $ms-color-neutralTertiary; + display: inline-block; + font-size: 15px; + position: relative; + text-align: center; + top: 3px; + cursor: pointer; + height: 16px; + width: 16px; + + .ms-Icon { + vertical-align: top; + } + + &:hover { + color: $ms-color-neutralSecondary; + outline: 1px solid transparent; + } +} + + +//== State: Unread list item +// +.ms-ListItem.is-unread { + border-left: 3px solid $ms-color-themePrimary; + padding-left: 27px; // Reduce padding to allow room for border. + + .ms-ListItem-secondaryText, .ms-ListItem-metaText { + color: $ms-color-themePrimary; + font-family: $ms-font-family-semibold; + } +} + + +//== State: Unseen list item +// +.ms-ListItem.is-unseen { + &:after { + border-right: 10px solid transparent; + border-top: 10px solid $ms-color-themePrimary; + left: 0; + position: absolute; + top: 0; + } +} + + +//== State: Selectable list item +// +.ms-ListItem.is-selectable { + .ms-ListItem-selectionTarget { + display: block; + height: 20px; + left: 6px; + position: absolute; + top: 13px; + width: 20px; + } + + .ms-ListItem-image { + margin-left: 0; + } + + &:hover { + background-color: $ms-color-neutralLight; + cursor: pointer; + outline: 1px solid transparent; + + // Insert the empty box. + &:before { + @include ms-Icon; + position: absolute; + top: 12px; + left: 6px; + height: 15px; + width: 15px; + border: 1px solid $ms-color-neutralSecondaryAlt; + } + } +} + + +//== State: Selected list item +// +.ms-ListItem.is-selected { + // Insert the checkmark. + &:before { + border: 1px solid transparent; + } + + &:before, + &:hover:before { + @include ms-Icon; + content: '\e041'; + font-size: $ms-font-size-m-plus; + color: $ms-color-neutralSecondaryAlt; + position: absolute; + top: 12px; + left: 6px; + } + + &:hover { + background-color: $ms-color-themeLight; + outline: 1px solid transparent; + } +} + + +//== Modifier: Document list item +// +.ms-ListItem.ms-ListItem--document { + padding: 0; + + // The icon for a file or folder in the items list. This may + // be an .ms-Icon or a specific modifier that loads an image. + .ms-ListItem-itemIcon { + width: 70px; + height: 70px; + float: left; + text-align: center; + } + + // If the item icon is an .ms-Icon, position and color it appropriately. + .ms-ListItem-itemIcon .ms-Icon { + font-size: 38px; + line-height: 70px; + color: $ms-color-neutralSecondary; + } + + // Primary text, typically the name. + .ms-ListItem-primaryText { + @include noWrap; + font-size: $ms-font-size-m; + padding-top: 15px; + padding-right: 0; + position: static; + } + + // Secondary text, typically the modified date or some other metadata. + .ms-ListItem-secondaryText { + @include noWrap; + color: $ms-color-neutralSecondary; + font-family: $ms-font-family-regular; + font-size: $ms-font-size-xs; + padding-top: 6px; + } +} .MailList { overflow-y: auto; diff --git a/src/demo/pages/NavPage/examples/Nav.Basic.Example.scss b/src/demo/pages/NavPage/examples/Nav.Basic.Example.scss new file mode 100644 index 00000000000000..6552f9d869556e --- /dev/null +++ b/src/demo/pages/NavPage/examples/Nav.Basic.Example.scss @@ -0,0 +1,9 @@ +@import '../../../../common/common'; + +.ms-NavExample-LeftPane { + width: 207px; + height: 400px; + box-sizing: border-box; + @include border-right(1px, solid, #EEE); + overflow-y: auto; +} \ No newline at end of file diff --git a/src/demo/pages/NavPage/examples/Nav.Basic.Example.tsx b/src/demo/pages/NavPage/examples/Nav.Basic.Example.tsx index 46d2059bf4dc18..294933e5eb7923 100644 --- a/src/demo/pages/NavPage/examples/Nav.Basic.Example.tsx +++ b/src/demo/pages/NavPage/examples/Nav.Basic.Example.tsx @@ -2,13 +2,18 @@ import * as React from 'react'; import { Nav } from '../../../../index'; +import './Nav.Basic.Example.scss'; export class NavBasicExample extends React.Component { public render() { return ( -