diff --git a/src/features/newTab/default/index.ts b/src/features/newTab/default/index.ts index 3bf3c3745..8bff05565 100644 --- a/src/features/newTab/default/index.ts +++ b/src/features/newTab/default/index.ts @@ -4,8 +4,9 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ import { StatsContainer, StatsItem } from './stats' -import { Page, DynamicBackground, Gradient, Link, Navigation, IconLink, PhotoName } from './page' +import { Page, DynamicBackground, Link, Navigation, IconLink, PhotoName } from './page' import { Header, Main, Footer } from './grid' +import { SettingsMenu, SettingsRow, SettingsText, SettingsTitle } from './settings' import { List, Tile, TileActionsContainer, TileAction, TileFavicon } from './topSites' import { SiteRemovalNotification, SiteRemovalText, SiteRemovalAction } from './notification' import { Clock } from './clock' @@ -15,7 +16,6 @@ export { StatsItem, Page, DynamicBackground, - Gradient, Link, Navigation, IconLink, @@ -31,5 +31,9 @@ export { SiteRemovalNotification, SiteRemovalText, SiteRemovalAction, - Clock + Clock, + SettingsMenu, + SettingsRow, + SettingsText, + SettingsTitle } diff --git a/src/features/newTab/default/page/index.ts b/src/features/newTab/default/page/index.ts index 73805d411..d76b5d364 100644 --- a/src/features/newTab/default/page/index.ts +++ b/src/features/newTab/default/page/index.ts @@ -30,6 +30,7 @@ export const Page = styled<{}, 'div'>('div')` interface DynamicBackgroundProps { background: string + showBackgroundImage: boolean } export const DynamicBackground = styled('div')` @@ -37,7 +38,15 @@ export const DynamicBackground = styled('div')` background-position: top center; background-repeat: no-repeat; background-size: cover; - background-image: url(${(p) => p.background}); + ${(p) => p.showBackgroundImage + ? ` background-image: url(${p.background});` + : ` background: linear-gradient( + to bottom right, + #4D54D1, + #A51C7B 50%, + #EE4A37 100%); + ` + } display: flex; flex: 1; opacity: 0; @@ -45,20 +54,6 @@ export const DynamicBackground = styled('div')` animation-fill-mode: forwards; ` -export const Gradient = styled<{}, 'div'>('div')` - position: absolute; - top: 0; - left: 0; - width: 100%; - background: linear-gradient( - rgba(0, 0, 0, 0.8), - rgba(0, 0, 0, 0) 35%, - rgba(0, 0, 0, 0) 80%, - rgba(0, 0, 0, 0.6) 100% - ); - height: 100vh; -` - export const Link = styled<{}, 'a'>('a')` text-decoration: none; transition: color 0.15s ease, filter 0.15s ease; @@ -81,7 +76,12 @@ export const Navigation = styled<{}, 'nav'>('nav')` display: flex; ` -export const IconLink = styled<{}, 'a'>('a')` +interface IconLinkProps { + disabled?: boolean +} + +export const IconLink = styled('a')` + pointer-events: ${p => p.disabled && 'none'}; display: flex; width: 24px; height: 24px; diff --git a/src/features/newTab/default/settings/index.ts b/src/features/newTab/default/settings/index.ts new file mode 100644 index 000000000..f78618c0e --- /dev/null +++ b/src/features/newTab/default/settings/index.ts @@ -0,0 +1,50 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this file, +* You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import styled from 'styled-components' + +export const SettingsMenu = styled<{}, 'div'>('div')` + background-color: ${p => p.theme.color.contextMenuBackground}; + color: ${p => p.theme.color.contextMenuForeground}; + border-radius: 8px; + padding: 24px; + box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.24); + font-family: ${p => p.theme.fontFamily.body}; +` + +export const SettingsTitle = styled<{}, 'div'>('div')` + font-family: ${p => p.theme.fontFamily.body}; + font-size: 18px; + font-weight: bold; + letter-spacing: 0px; + margin-bottom: 16px; +` + +export const SettingsRow = styled<{}, 'div'>('div')` + box-sizing: border-box; + display: grid; + grid-template-columns: 1fr 36px; + height: 30px; + width: 320px; +` + +export const SettingsText = styled<{}, 'span'>('span')` + display: flex; + align-items: center; + font-size: 14px; + font-weight: normal; +` + +export const SettingsWrapper = styled<{}, 'div'>('div')` + position: absolute; + bottom: 118px; + padding: 0 222px; + display: flex; + width: 100%; + justify-content: flex-end; + + @media screen and (max-width: 904px) { + padding: 0 192px; + } +` diff --git a/src/features/newTab/toggle/index.tsx b/src/features/newTab/toggle/index.tsx new file mode 100644 index 000000000..92dfe40cd --- /dev/null +++ b/src/features/newTab/toggle/index.tsx @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' +import { StyledWrapper, StyledSlider, StyledBullet, StyleToggle, StyledCheckbox } from './style' + +export interface Props { + testId?: string + checked?: boolean + disabled?: boolean + onChange?: (e: any) => void + id?: string + readOnly?: boolean + autoFocus?: boolean + size: 'large' | 'small' + colorType?: 'dark' | 'light' | 'default' +} + +export interface ToggleState { + checked?: boolean +} + +export class Toggle extends React.PureComponent { + static defaultProps = { + checked: false, + size: 'small', + type: 'default', + disabled: false + } + + constructor (props: Props) { + super(props) + this.state = { checked: props.checked } + this.handleChange = this.handleChange.bind(this) + } + + componentWillReceiveProps (nextProps: Props) { + if ('checked' in nextProps) { + this.setState({ checked: nextProps.checked }) + } + } + + handleChange (e: any) { + const { props } = this + if (props.disabled) { + return + } + if (!('checked' in props)) { + this.setState({ checked: e.target.checked }) + } + + if (props.onChange) { + props.onChange({ target: { checked: e.target.checked } }) + } + } + + render () { + const { id, testId, readOnly, disabled, autoFocus, size, colorType } = this.props + const { checked } = this.state + + return ( + + + + + + + + ) + } +} diff --git a/src/features/newTab/toggle/style.ts b/src/features/newTab/toggle/style.ts new file mode 100644 index 000000000..0dc1e3166 --- /dev/null +++ b/src/features/newTab/toggle/style.ts @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License. v. 2.0. If a copy of the MPL was not distributed with this file. + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import styled, { css } from '../../../theme' +import { Props } from './index' +import palette from '../../../theme/colors' + +export const StyledCheckbox = styled<{}, 'input'>('input')` + -webkit-appearance: none; + position: absolute; + z-index: 99999999; + width: 100%; + height: 100%; + top: 0; + left: 0; + outline-offset: -3px; + outline-color: ${palette.orange400}; + outline-width: 2px; +` + +export const StyledWrapper = styled('div')` + box-sizing: border-box; + display: flex; + position: relative; + height: 100%; + justify-content: center; + align-items: center; +` + +export const StyleToggle = styled('div')` + box-sizing: border-box; + position: relative; + display: block; + height: ${(p) => p.size === 'small' ? '16px' : '24px'}; + width: ${(p) => p.size === 'small' ? '28px' : '40px'}; + + ${(p) => p.disabled + ? css` + pointer-events: none; + animation: none; + ` : '' + }; +` + +export const StyledSlider = styled('label')` + box-sizing: border-box; + background: ${(p) => p.disabled ? 'rgba(246,246,250,0.1)' : '#C4C7C9'}; + height: ${(p) => p.size === 'small' ? '6px' : '8px'}; + margin-top: ${(p) => p.size === 'small' ? '5px' : '8px'}; + width: 100%; + border-radius: 3px; + display: block; +` + +const transform = (p: Props) => { + let x = p.size === 'small' ? '12px' : '20px' + let y = p.size === 'small' ? '3px' : '4px' + + if (!p.checked) { + x = '-1px' + } + + return { x, y } +} + +const transformBullet = (p: Props) => `${transform(p).x}, calc(-50% - ${transform(p).y})` + +export const StyledBullet = styled('label')` + box-sizing: border-box; + position: relative; + border-radius: 50%; + transition: all .4s ease; + transform: ${p => `translate(${transformBullet(p)})`}; + width: ${p => p.size === 'small' ? '16px' : '20px'}; + height: ${p => p.size === 'small' ? '16px' : '20px'}; + background-color: ${p => p.disabled && 'rgba(235,236,240,0.8)' || p.checked ? '#fb542b' : '#ebecf0'}; + display: block; + box-shadow: 0 3px 3px rgba(0,0,0,0.05); +` diff --git a/src/theme/brave-dark.ts b/src/theme/brave-dark.ts index 533973f4e..21a8f3468 100644 --- a/src/theme/brave-dark.ts +++ b/src/theme/brave-dark.ts @@ -1,9 +1,15 @@ import ITheme from './theme-interface' +import colors from './colors' import defaultTheme from './brave-default' const darkTheme: ITheme = { ...defaultTheme, - name: 'Brave Dark' + name: 'Brave Dark', + color: { + ...defaultTheme.color, + contextMenuBackground: colors.black, + contextMenuForeground: colors.white + } } export default darkTheme diff --git a/src/theme/brave-default.ts b/src/theme/brave-default.ts index 7911b0a2e..3a68b76e6 100644 --- a/src/theme/brave-default.ts +++ b/src/theme/brave-default.ts @@ -40,9 +40,11 @@ const theme: ITheme = { panelBackground: colors.white, panelBackgroundSecondary: colors.neutral000, primaryBackground: colors.white, + contextMenuBackground: colors.white, secondaryBackground: colors.grey400, modalOverlayBackground: 'rgba(36,37,54,0.85)', // text + contextMenuForeground: colors.grey800, detailDescription: colors.grey500, text: colors.grey700, // form controls diff --git a/src/theme/theme-interface.ts b/src/theme/theme-interface.ts index 8dc65da33..a759e2fee 100644 --- a/src/theme/theme-interface.ts +++ b/src/theme/theme-interface.ts @@ -4,6 +4,8 @@ export default interface IThemeProps { name: string, palette: { [key: string]: string } color: { + contextMenuBackground: string + contextMenuForeground: string brandBrave: string brandBraveInteracting: string brandBraveActive: string diff --git a/stories/features/newTab/default.tsx b/stories/features/newTab/default.tsx index 26b46b4e2..d2c307e90 100644 --- a/stories/features/newTab/default.tsx +++ b/stories/features/newTab/default.tsx @@ -4,13 +4,11 @@ import * as React from 'react' import { storiesOf } from '@storybook/react' -// import { withKnobs } from '@storybook/addon-knobs' // Components import NewTabPage from './default/index' storiesOf('Feature Components/New Tab/Default', module) - // .addDecorator(withKnobs) .add('Page', () => { return ( diff --git a/stories/features/newTab/default/footerInfo.tsx b/stories/features/newTab/default/footerInfo.tsx index 372f9dc23..d14a55935 100644 --- a/stories/features/newTab/default/footerInfo.tsx +++ b/stories/features/newTab/default/footerInfo.tsx @@ -8,29 +8,40 @@ import * as React from 'react' import { Link, Navigation, IconLink, PhotoName } from '../../../../src/features/newTab/default' // Icons -import { SettingsAdvancedIcon, BookmarkBook, HistoryIcon } from '../../../../src/components/icons' +import { SettingsAdvancedIcon, BookmarkBook, HistoryIcon, SettingsIcon } from '../../../../src/components/icons' // Helpers import { getLocale } from '../fakeLocale' interface Props { backgroundImageInfo: any + onClickSettings: () => void + isSettingsMenuOpen: boolean + showPhotoInfo: boolean } export default class FooterInfo extends React.PureComponent { render () { - const { backgroundImageInfo } = this.props + const { + backgroundImageInfo, + onClickSettings, + isSettingsMenuOpen, + showPhotoInfo + } = this.props + return ( <> -
- - {`${getLocale('photoBy')} `} - - {backgroundImageInfo.author} - - -
+
+ {showPhotoInfo && + + {`${getLocale('photoBy')} `} + + {backgroundImageInfo.author} + + } +
+ diff --git a/stories/features/newTab/default/index.tsx b/stories/features/newTab/default/index.tsx index cf159ea4b..27686fa91 100644 --- a/stories/features/newTab/default/index.tsx +++ b/stories/features/newTab/default/index.tsx @@ -6,8 +6,9 @@ import * as React from 'react' // Feature-specific components import { Clock } from '../../../../src/features/newTab/default/clock' -import { Page, Header, Main, Footer, DynamicBackground, Gradient } from '../../../../src/features/newTab/default' +import { Page, Header, Main, Footer, DynamicBackground } from '../../../../src/features/newTab/default' +import Settings from './settings' import TopSitesList from './topSites/topSitesList' import Stats from './stats' import SiteRemovalNotification from './siteRemovalNotification' @@ -21,12 +22,36 @@ import '../../../assets/fonts/muli.css' import '../../../assets/fonts/poppins.css' const generateRandomBackgroundData = getRandomBackgroundData(images) +interface State { + showSettings: boolean + showBackgroundImage: boolean +} + +export default class NewTabPage extends React.PureComponent<{}, State> { + constructor (props: {}) { + super(props) + this.state = { + showBackgroundImage: true, + showSettings: false + } + } + + toggleShowBackgroundImage = () => { + this.setState({ showBackgroundImage: !this.state.showBackgroundImage }) + } + + showSettings = () => { + this.setState({ showSettings: true }) + } + + closeSettings = () => { + this.setState({ showSettings: false }) + } -export default class NewTabPage extends React.PureComponent<{}, {}> { render () { + const { showSettings, showBackgroundImage } = this.state return ( - - +
@@ -36,8 +61,21 @@ export default class NewTabPage extends React.PureComponent<{}, {}> {
+ { + showSettings && + + }
- +
diff --git a/stories/features/newTab/default/settings.tsx b/stories/features/newTab/default/settings.tsx new file mode 100644 index 000000000..5a29c8f4d --- /dev/null +++ b/stories/features/newTab/default/settings.tsx @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this file, +* You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react' + +import { SettingsMenu, SettingsRow, SettingsText, SettingsTitle, SettingsWrapper } from '../../../../src/features/newTab/default/settings' + +import { Toggle } from '../../../../src/features/newTab/toggle' + +import { getLocale } from '../fakeLocale' + +interface Props { + onClickOutside: () => void + toggleShowBackgroundImage: () => void + showBackgroundImage: boolean +} + +export default class Settings extends React.PureComponent { + settingsMenuRef: React.RefObject + constructor (props: Props) { + super(props) + this.settingsMenuRef = React.createRef() + } + + handleClickOutside = (event: Event) => { + if (this.settingsMenuRef && !this.settingsMenuRef.current.contains(event.target)) { + this.props.onClickOutside() + } + } + + componentDidMount () { + document.addEventListener('mousedown', this.handleClickOutside) + } + + componentWillUnmount () { + document.removeEventListener('mousedown', this.handleClickOutside) + } + + render () { + const { toggleShowBackgroundImage, showBackgroundImage } = this.props + return ( + + + {getLocale('dashboardSettings')} + + {getLocale('showBackgroundImg')} + + + + + ) + } +} diff --git a/stories/features/newTab/fakeLocale.ts b/stories/features/newTab/fakeLocale.ts index 6797373e6..ab136f0da 100644 --- a/stories/features/newTab/fakeLocale.ts +++ b/stories/features/newTab/fakeLocale.ts @@ -15,7 +15,10 @@ const locale: any = { thumbRemoved: 'Top site removed', undoRemoved: 'Undo', restoreAll: 'Restore All', - close: 'Close' + close: 'Close', + // settings + dashboardSettings: 'Dashboard Settings', + showBackgroundImg: 'Show background image' } export default locale