convert to emotion

Summary:
My benchmarks have shown react-emotion to be faster than the current implementation of `styled`. For this reason, I am converting all styling to [emotion](https://emotion.sh).

Benchmark results:
{F136839093}

The syntax is very similar between the two libraries. The main difference is that emotion only allows a single function for the whole style attribute, whereas the old implementation had functions for every style-attirbute.

Before:
```
{
  color: props => props.color,
  fontSize: props => props.size,
}
```

After:
```
props => ({
  color: props.color,
  fontSize: props.size,
})
```

Reviewed By: jknoxville

Differential Revision: D9479893

fbshipit-source-id: 2c39e4618f7e52ceacb67bbec8ae26114025723f
This commit is contained in:
Daniel Büchele
2018-08-23 09:32:12 -07:00
committed by Facebook Github Bot
parent 4151c73409
commit 726966fdc0
88 changed files with 886 additions and 4068 deletions

60
flow-typed/npm/react-emotion_vx.x.x.js vendored Normal file
View File

@@ -0,0 +1,60 @@
// flow-typed signature: 69f0585b3bbc433c0c0eb3d242636c6d
// flow-typed version: <<STUB>>/react-emotion_v9.2.6/flow_v0.76.0
/**
* This is an autogenerated libdef stub for:
*
* 'react-emotion'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module 'react-emotion' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module 'react-emotion/dist/emotion.umd.min' {
declare module.exports: any;
}
declare module 'react-emotion/dist/index.cjs' {
declare module.exports: any;
}
declare module 'react-emotion/dist/index.esm' {
declare module.exports: any;
}
declare module 'react-emotion/macro' {
declare module.exports: any;
}
declare module 'react-emotion/src/index' {
declare module.exports: any;
}
// Filename aliases
declare module 'react-emotion/dist/emotion.umd.min.js' {
declare module.exports: $Exports<'react-emotion/dist/emotion.umd.min'>;
}
declare module 'react-emotion/dist/index.cjs.js' {
declare module.exports: $Exports<'react-emotion/dist/index.cjs'>;
}
declare module 'react-emotion/dist/index.esm.js' {
declare module.exports: $Exports<'react-emotion/dist/index.esm'>;
}
declare module 'react-emotion/macro.js' {
declare module.exports: $Exports<'react-emotion/macro'>;
}
declare module 'react-emotion/src/index.js' {
declare module.exports: $Exports<'react-emotion/src/index'>;
}

View File

@@ -10,7 +10,14 @@ import type Client from './Client.js';
import type BaseDevice from './devices/BaseDevice.js';
import {SonarDevicePlugin} from './plugin.js';
import {ErrorBoundary, Component, FlexColumn, FlexRow, colors} from 'sonar';
import {
ErrorBoundary,
Component,
FlexColumn,
FlexRow,
colors,
styled,
} from 'sonar';
import React from 'react';
import {connect} from 'react-redux';
import {setPluginState} from './reducers/pluginStates.js';
@@ -18,14 +25,14 @@ import {devicePlugins} from './device-plugins/index.js';
import plugins from './plugins/index.js';
import {activateMenuItems} from './MenuBar.js';
const Container = FlexColumn.extends({
const Container = styled(FlexColumn)({
width: 0,
flexGrow: 1,
flexShrink: 1,
backgroundColor: colors.white,
});
const SidebarContainer = FlexRow.extends({
const SidebarContainer = styled(FlexRow)({
backgroundColor: colors.light02,
height: '100%',
overflow: 'scroll',

View File

@@ -5,19 +5,27 @@
* @format
*/
import {FlexRow, Text, colors, LoadingIndicator, Glyph, Component} from 'sonar';
import {
FlexRow,
Text,
colors,
LoadingIndicator,
Glyph,
Component,
styled,
} from 'sonar';
import {remote} from 'electron';
import isProduction from '../utils/isProduction.js';
import config from '../fb-stubs/config.js';
const version = remote.app.getVersion();
const VersionText = Text.extends({
const VersionText = styled(Text)({
color: colors.light50,
marginLeft: 4,
marginTop: 2,
});
const Container = FlexRow.extends({
const Container = styled(FlexRow)({
alignItems: 'center',
});

View File

@@ -20,7 +20,7 @@ import {
styled,
} from 'sonar';
const Container = FlexColumn.extends({
const Container = styled(FlexColumn)({
padding: 10,
});
@@ -29,7 +29,7 @@ const textareaStyle = {
marginBottom: 10,
};
const DialogContainer = styled.view({
const DialogContainer = styled('div')({
width: 400,
height: 300,
position: 'absolute',
@@ -45,25 +45,25 @@ const DialogContainer = styled.view({
boxShadow: '0 1px 10px rgba(0, 0, 0, 0.1)',
});
const TitleInput = Input.extends({
const TitleInput = styled(Input)({
...textareaStyle,
height: 30,
});
const DescriptionTextarea = Textarea.extends({
const DescriptionTextarea = styled(Textarea)({
...textareaStyle,
flexGrow: 1,
});
const SubmitButtonContainer = styled.view({
const SubmitButtonContainer = styled('div')({
marginLeft: 'auto',
});
const Footer = FlexRow.extends({
const Footer = styled(FlexRow)({
lineHeight: '24px',
});
const CloseDoneButton = Button.extends({
const CloseDoneButton = styled(Button)({
width: 50,
margin: '10px auto',
});

View File

@@ -16,7 +16,7 @@ import {
colors,
} from 'sonar';
const Heading = Text.extends({
const Heading = styled(Text)({
display: 'block',
backgroundColor: colors.white,
color: colors.light30,
@@ -26,7 +26,7 @@ const Heading = Text.extends({
padding: '4px 8px 0',
});
const PopoverItem = FlexRow.extends({
const PopoverItem = styled(FlexRow)({
alignItems: 'center',
borderBottom: `1px solid ${colors.light05}`,
height: 50,
@@ -35,7 +35,7 @@ const PopoverItem = FlexRow.extends({
},
});
const ItemTitle = Text.extends({
const ItemTitle = styled(Text)({
display: 'block',
fontSize: 14,
fontWeight: 400,
@@ -46,7 +46,7 @@ const ItemTitle = Text.extends({
marginBottom: 1,
});
const ItemSubtitle = Text.extends({
const ItemSubtitle = styled(Text)({
display: 'block',
fontWeight: 400,
fontSize: 11,
@@ -57,20 +57,20 @@ const ItemSubtitle = Text.extends({
whiteSpace: 'nowrap',
});
const ItemImage = FlexBox.extends({
const ItemImage = styled(FlexBox)({
alignItems: 'center',
justifyContent: 'center',
width: 40,
flexShrink: 0,
});
const ItemContent = styled.view({
const ItemContent = styled('div')({
minWidth: 0,
paddingRight: 5,
flexGrow: 1,
});
const Section = styled.view({
const Section = styled('div')({
maxWidth: 260,
borderBottom: `1px solid ${colors.light05}`,
'&:last-child': {
@@ -78,7 +78,7 @@ const Section = styled.view({
},
});
const Action = Button.extends({
const Action = styled(Button)({
border: `1px solid ${colors.macOSTitleBarButtonBorder}`,
background: 'transparent',
color: colors.macOSTitleBarIconSelected,

View File

@@ -7,7 +7,7 @@
import {styled, colors} from 'sonar';
const ErrorBarContainer = styled.view({
const ErrorBarContainer = styled('div')({
backgroundColor: colors.cherry,
bottom: 0,
color: '#fff',

View File

@@ -17,11 +17,11 @@ import {
Component,
Sidebar,
FlexBox,
ClickableListItem,
colors,
brandColors,
Text,
Glyph,
styled,
} from 'sonar';
import React from 'react';
import {devicePlugins} from '../device-plugins/index.js';
@@ -29,15 +29,22 @@ import plugins from '../plugins/index.js';
import {selectPlugin} from '../reducers/connections.js';
import {connect} from 'react-redux';
const CustomClickableListItem = ClickableListItem.extends({
const ListItem = styled('div')(({active}) => ({
paddingLeft: 10,
display: 'flex',
alignItems: 'center',
marginBottom: 2,
flexShrink: 0,
});
backgroundColor: active ? colors.macOSTitleBarIconSelected : 'none',
color: active ? colors.white : colors.macOSSidebarSectionItem,
lineHeight: '25px',
padding: '0 10px',
'&[disabled]': {
color: 'rgba(0, 0, 0, 0.5)',
},
}));
const SidebarHeader = FlexBox.extends({
const SidebarHeader = styled(FlexBox)({
display: 'block',
alignItems: 'center',
padding: 3,
@@ -51,23 +58,18 @@ const SidebarHeader = FlexBox.extends({
flexShrink: 0,
});
const PluginShape = FlexBox.extends(
{
marginRight: 5,
backgroundColor: props => props.backgroundColor,
borderRadius: 3,
flexShrink: 0,
width: 18,
height: 18,
justifyContent: 'center',
alignItems: 'center',
},
{
ignoreAttributes: ['backgroundColor'],
},
);
const PluginShape = styled(FlexBox)(({backgroundColor}) => ({
marginRight: 5,
backgroundColor,
borderRadius: 3,
flexShrink: 0,
width: 18,
height: 18,
justifyContent: 'center',
alignItems: 'center',
}));
const PluginName = Text.extends({
const PluginName = styled(Text)({
minWidth: 0,
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
@@ -75,17 +77,19 @@ const PluginName = Text.extends({
});
function PluginIcon({
isActive,
backgroundColor,
name,
color,
}: {
isActive: boolean,
backgroundColor: string,
name: string,
color: string,
}) {
return (
<PluginShape backgroundColor={backgroundColor}>
<Glyph size={12} name={name} color={color} />
<Glyph size={12} name={name} color={isActive ? colors.white : color} />
</PluginShape>
);
}
@@ -118,14 +122,15 @@ class PluginSidebarListItem extends Component<{
}
return (
<CustomClickableListItem active={isActive} onClick={this.props.onClick}>
<ListItem active={isActive} onClick={this.props.onClick}>
<PluginIcon
isActive={isActive}
name={plugin.icon}
backgroundColor={iconColor}
color={colors.white}
/>
<PluginName>{plugin.title}</PluginName>
</CustomClickableListItem>
</ListItem>
);
}
}

View File

@@ -63,18 +63,18 @@ type State = {
searchCompleted: boolean,
};
const Container = FlexBox.extends({
const Container = styled(FlexBox)({
width: '100%',
flexGrow: 1,
background: colors.light02,
overflowY: 'scroll',
});
const Title = Text.extends({
const Title = styled(Text)({
fontWeight: 500,
});
const Plugin = FlexColumn.extends({
const Plugin = styled(FlexColumn)({
backgroundColor: colors.white,
borderRadius: 4,
padding: 15,
@@ -82,20 +82,20 @@ const Plugin = FlexColumn.extends({
boxShadow: '0 1px 2px rgba(0,0,0,0.05)',
});
const SectionTitle = styled.text({
const SectionTitle = styled('span')({
fontWeight: 'bold',
fontSize: 24,
margin: 15,
marginLeft: 20,
});
const Loading = FlexBox.extends({
const Loading = styled(FlexBox)({
padding: 50,
alignItems: 'center',
justifyContent: 'center',
});
const RestartRequired = FlexBox.extends({
const RestartRequired = styled(FlexBox)({
textAlign: 'center',
justifyContent: 'center',
fontWeight: 500,
@@ -105,22 +105,22 @@ const RestartRequired = FlexBox.extends({
cursor: 'pointer',
});
const TitleRow = FlexRow.extends({
const TitleRow = styled(FlexRow)({
alignItems: 'center',
marginBottom: 10,
fontSize: '1.1em',
});
const Description = FlexRow.extends({
const Description = styled(FlexRow)({
marginBottom: 15,
lineHeight: '130%',
});
const PluginGlyph = Glyph.extends({
const PluginGlyph = styled(Glyph)({
marginRight: 5,
});
const PluginLoading = LoadingIndicator.extends({
const PluginLoading = styled(LoadingIndicator)({
marginLeft: 5,
marginTop: 5,
});

View File

@@ -16,7 +16,7 @@ import {
colors,
} from 'sonar';
const Anchor = styled.image({
const Anchor = styled('img')({
zIndex: 6,
position: 'absolute',
bottom: 0,
@@ -24,7 +24,7 @@ const Anchor = styled.image({
transform: 'translate(-50%, calc(100% + 2px))',
});
const PopoverContainer = FlexColumn.extends({
const PopoverContainer = styled(FlexColumn)({
backgroundColor: colors.white,
borderRadius: 7,
border: '1px solid rgba(0,0,0,0.3)',
@@ -50,7 +50,7 @@ const PopoverContainer = FlexColumn.extends({
},
});
const Heading = Text.extends({
const Heading = styled(Text)({
display: 'block',
backgroundColor: colors.white,
color: colors.light30,
@@ -60,7 +60,7 @@ const Heading = Text.extends({
padding: '4px 8px 0',
});
const PopoverItem = FlexRow.extends({
const PopoverItem = styled(FlexRow)({
alignItems: 'center',
borderBottom: `1px solid ${colors.light05}`,
height: 50,
@@ -69,7 +69,7 @@ const PopoverItem = FlexRow.extends({
},
});
const ItemTitle = Text.extends({
const ItemTitle = styled(Text)({
display: 'block',
fontSize: 14,
fontWeight: 400,
@@ -80,7 +80,7 @@ const ItemTitle = Text.extends({
marginBottom: 1,
});
const ItemSubtitle = Text.extends({
const ItemSubtitle = styled(Text)({
display: 'block',
fontWeight: 400,
fontSize: 11,
@@ -91,27 +91,27 @@ const ItemSubtitle = Text.extends({
whiteSpace: 'nowrap',
});
const ItemImage = FlexBox.extends({
const ItemImage = styled(FlexBox)({
alignItems: 'center',
justifyContent: 'center',
width: 40,
flexShrink: 0,
});
const ItemContent = styled.view({
const ItemContent = styled('div')({
minWidth: 0,
paddingRight: 5,
flexGrow: 1,
});
const Section = styled.view({
const Section = styled('div')({
borderBottom: `1px solid ${colors.light05}`,
'&:last-child': {
borderBottom: 'none',
},
});
const Action = Button.extends({
const Action = styled(Button)({
border: `1px solid ${colors.macOSTitleBarButtonBorder}`,
background: 'transparent',
color: colors.macOSTitleBarIconSelected,

View File

@@ -13,6 +13,7 @@ import {
Component,
Spacer,
GK,
styled,
} from 'sonar';
import {connect} from 'react-redux';
import {
@@ -26,34 +27,24 @@ import ScreenCaptureButtons from './ScreenCaptureButtons.js';
import AutoUpdateVersion from './AutoUpdateVersion.js';
import config from '../fb-stubs/config.js';
const TitleBar = FlexRow.extends(
{
background: props =>
props.focused
? `linear-gradient(to bottom, ${
colors.macOSTitleBarBackgroundTop
} 0%, ${colors.macOSTitleBarBackgroundBottom} 100%)`
: colors.macOSTitleBarBackgroundBlur,
borderBottom: props =>
`1px solid ${
props.focused
? colors.macOSTitleBarBorder
: colors.macOSTitleBarBorderBlur
}`,
height: 38,
flexShrink: 0,
width: '100%',
alignItems: 'center',
paddingLeft: 80,
paddingRight: 10,
justifyContent: 'space-between',
// $FlowFixMe
WebkitAppRegion: 'drag',
},
{
ignoreAttributes: ['focused'],
},
);
const TitleBar = styled(FlexRow)(({focused}) => ({
background: focused
? `linear-gradient(to bottom, ${colors.macOSTitleBarBackgroundTop} 0%, ${
colors.macOSTitleBarBackgroundBottom
} 100%)`
: colors.macOSTitleBarBackgroundBlur,
borderBottom: `1px solid ${
focused ? colors.macOSTitleBarBorder : colors.macOSTitleBarBorderBlur
}`,
height: 38,
flexShrink: 0,
width: '100%',
alignItems: 'center',
paddingLeft: 80,
paddingRight: 10,
justifyContent: 'space-between',
WebkitAppRegion: 'drag',
}));
type Props = {|
windowIsFocused: boolean,

View File

@@ -5,7 +5,7 @@
* @format
*/
import {Component, FlexRow, colors, LoadingIndicator} from 'sonar';
import {Component, FlexRow, colors, LoadingIndicator, styled} from 'sonar';
import {version} from '../../package.json';
import {remote} from 'electron';
import * as path from 'path';
@@ -24,7 +24,7 @@ export default class Version extends Component<{}, VersionState> {
status: 'unknown',
};
static Container = FlexRow.extends({
static Container = styled(FlexRow)({
alignItems: 'center',
marginRight: 7,
marginLeft: 7,
@@ -32,7 +32,7 @@ export default class Version extends Component<{}, VersionState> {
color: colors.light50,
});
static UpdatedContainer = FlexRow.extends({
static UpdatedContainer = styled(FlexRow)({
backgroundColor: colors.blackAlpha10,
borderRadius: '999em',
padding: '2px 6px',

View File

@@ -18,7 +18,7 @@ import {
import isProduction from '../utils/isProduction.js';
import {shell, remote} from 'electron';
const Container = FlexColumn.extends({
const Container = styled(FlexColumn)({
height: '100%',
width: '100%',
justifyContent: 'center',
@@ -26,23 +26,18 @@ const Container = FlexColumn.extends({
backgroundColor: colors.light02,
});
const Welcome = FlexColumn.extends(
{
width: 460,
background: colors.white,
borderRadius: 10,
boxShadow: '0 1px 3px rgba(0,0,0,0.25)',
overflow: 'hidden',
opacity: props => (props.isMounted ? 1 : 0),
transform: props => `translateY(${props.isMounted ? 0 : 20}px)`,
transition: '0.6s all ease-out',
},
{
ignoreAttributes: ['isMounted'],
},
);
const Welcome = styled(FlexColumn)(({isMounted}) => ({
width: 460,
background: colors.white,
borderRadius: 10,
boxShadow: '0 1px 3px rgba(0,0,0,0.25)',
overflow: 'hidden',
opacity: isMounted ? 1 : 0,
transform: `translateY(${isMounted ? 0 : 20}px)`,
transition: '0.6s all ease-out',
}));
const Title = Text.extends({
const Title = styled(Text)({
fontSize: 24,
fontWeight: 300,
textAlign: 'center',
@@ -50,7 +45,7 @@ const Title = Text.extends({
marginBottom: 16,
});
const Version = Text.extends({
const Version = styled(Text)({
textAlign: 'center',
fontSize: 11,
fontWeight: 300,
@@ -58,7 +53,7 @@ const Version = Text.extends({
marginBottom: 60,
});
const Item = FlexRow.extends({
const Item = styled(FlexRow)({
padding: 10,
cursor: 'pointer',
alignItems: 'center',
@@ -69,23 +64,23 @@ const Item = FlexRow.extends({
},
});
const ItemTitle = Text.extends({
const ItemTitle = styled(Text)({
color: colors.light50,
fontSize: 15,
});
const ItemSubTitle = Text.extends({
const ItemSubTitle = styled(Text)({
color: colors.light30,
fontSize: 11,
marginTop: 2,
});
const Icon = Glyph.extends({
const Icon = styled(Glyph)({
marginRight: 11,
marginLeft: 6,
});
const Logo = styled.image({
const Logo = styled('img')({
width: 128,
height: 128,
alignSelf: 'center',

View File

@@ -15,6 +15,7 @@ import {
ManagedTable,
Button,
colors,
styled,
} from 'sonar';
export type Counter = {
@@ -55,7 +56,7 @@ const Columns = {
},
};
const Count = Text.extends({
const Count = styled(Text)({
alignSelf: 'center',
background: colors.macOSHighlightActive,
color: colors.white,
@@ -68,7 +69,7 @@ const Count = Text.extends({
marginLeft: 'auto',
});
const Checkbox = Input.extends({
const Checkbox = styled(Input)({
lineHeight: '100%',
padding: 0,
margin: 0,
@@ -76,11 +77,11 @@ const Checkbox = Input.extends({
alignSelf: 'center',
});
const ExpressionInput = Input.extends({
const ExpressionInput = styled(Input)({
flexGrow: 1,
});
const WatcherPanel = Panel.extends({
const WatcherPanel = styled(Panel)({
minHeight: 200,
});

View File

@@ -25,6 +25,7 @@ import {
FlexColumn,
Glyph,
SonarSidebar,
styled,
} from 'sonar';
import {SonarDevicePlugin, SearchableTable} from 'sonar';
import textContent from '../../utils/textContent.js';
@@ -47,7 +48,7 @@ type LogsState = {|
counters: Array<Counter>,
|};
const Icon = Glyph.extends({
const Icon = styled(Glyph)({
marginTop: 5,
});
@@ -202,7 +203,7 @@ const DEFAULT_FILTERS = [
},
];
const HiddenScrollText = Text.extends({
const HiddenScrollText = styled(Text)({
alignSelf: 'baseline',
userSelect: 'none',
lineHeight: '130%',
@@ -212,23 +213,18 @@ const HiddenScrollText = Text.extends({
},
});
const LogCount = HiddenScrollText.extends(
{
backgroundColor: props => props.color,
borderRadius: '999em',
fontSize: 11,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginTop: 4,
width: 16,
height: 16,
color: colors.white,
},
{
ignoreAttributes: ['color'],
},
);
const LogCount = styled(HiddenScrollText)(({color}) => ({
backgroundColor: color,
borderRadius: '999em',
fontSize: 11,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
marginTop: 4,
width: 16,
height: 16,
color: colors.white,
}));
function pad(chunk: mixed, len: number): string {
let str = String(chunk);
@@ -529,7 +525,7 @@ export default class LogTable extends SonarDevicePlugin<LogsState> {
);
};
static ContextMenu = ContextMenu.extends({
static ContextMenu = styled(ContextMenu)({
flex: 1,
});

View File

@@ -5,7 +5,7 @@
* @format
*/
export {default as styled} from './ui/styled/index.js';
export {default as styled} from 'react-emotion';
export * from './ui/index.js';
export * from './utils/index.js';

View File

@@ -106,18 +106,18 @@ type SearchResultTree = {|
axElement: Element,
|};
const LoadingSpinner = LoadingIndicator.extends({
const LoadingSpinner = styled(LoadingIndicator)({
marginRight: 4,
marginLeft: 3,
marginTop: -1,
});
const Center = FlexRow.extends({
const Center = styled(FlexRow)({
alignItems: 'center',
justifyContent: 'center',
});
const SearchIconContainer = styled.view({
const SearchIconContainer = styled('div')({
marginRight: 9,
marginTop: -3,
marginLeft: 4,
@@ -131,7 +131,7 @@ class LayoutSearchInput extends Component<
value: string,
},
> {
static TextInput = styled.textInput({
static TextInput = styled('input')({
width: '100%',
marginLeft: 6,
});

View File

@@ -16,6 +16,7 @@ import {
Checkbox,
SonarPlugin,
Button,
styled,
} from 'sonar';
import type {ElementID, Element} from 'sonar';
import {processLeaks} from './processLeakString';
@@ -42,12 +43,12 @@ export type Leak = {
retainedSize: string,
};
const Window = FlexRow.extends({
const Window = styled(FlexRow)({
height: '100%',
flex: 1,
});
const ToolbarItem = FlexRow.extends({
const ToolbarItem = styled(FlexRow)({
alignItems: 'center',
marginLeft: '8px',
});

View File

@@ -23,7 +23,7 @@ import {getHeaderValue} from './index.js';
import querystring from 'querystring';
const WrappingText = Text.extends({
const WrappingText = styled(Text)({
wordWrap: 'break-word',
width: '100%',
lineHeight: '125%',
@@ -84,7 +84,7 @@ function decompress(body: string): string {
}
export default class RequestDetails extends Component<RequestDetailsProps> {
static Container = FlexColumn.extends({
static Container = styled(FlexColumn)({
height: '100%',
overflow: 'auto',
});
@@ -274,7 +274,7 @@ class HeaderInspector extends Component<
}
}
const BodyContainer = styled.view({
const BodyContainer = styled('div')({
paddingTop: 10,
paddingBottom: 20,
});
@@ -338,7 +338,7 @@ class ResponseBodyInspector extends Component<{
}
}
const MediaContainer = FlexColumn.extends({
const MediaContainer = styled(FlexColumn)({
alignItems: 'center',
justifyContent: 'center',
width: '100%',
@@ -354,14 +354,14 @@ type ImageWithSizeState = {
};
class ImageWithSize extends Component<ImageWithSizeProps, ImageWithSizeState> {
static Image = styled.image({
static Image = styled('img')({
objectFit: 'scale-down',
maxWidth: 500,
maxHeight: 500,
marginBottom: 10,
});
static Text = Text.extends({
static Text = styled(Text)({
color: colors.dark70,
fontSize: 14,
});
@@ -408,7 +408,7 @@ class ImageFormatter {
}
class VideoFormatter {
static Video = styled.customHTMLTag('video', {
static Video = styled('video')({
maxWidth: 500,
maxHeight: 500,
});

View File

@@ -16,6 +16,7 @@ import {
colors,
PureComponent,
SonarSidebar,
styled,
} from 'sonar';
import {SonarPlugin, SearchableTable} from 'sonar';
import RequestDetails from './RequestDetails.js';
@@ -100,7 +101,7 @@ export function formatBytes(count: number): string {
return count + ' B';
}
const TextEllipsis = Text.extends({
const TextEllipsis = styled(Text)({
overflowX: 'hidden',
textOverflow: 'ellipsis',
maxWidth: '100%',
@@ -287,7 +288,7 @@ function calculateState(
}
class NetworkTable extends PureComponent<NetworkTableProps, NetworkTableState> {
static ContextMenu = ContextMenu.extends({
static ContextMenu = styled(ContextMenu)({
flex: 1,
});
@@ -338,7 +339,7 @@ class NetworkTable extends PureComponent<NetworkTableProps, NetworkTableState> {
}
}
const Icon = Glyph.extends({
const Icon = styled(Glyph)({
marginTop: -3,
marginRight: 3,
});
@@ -367,7 +368,7 @@ class DurationColumn extends PureComponent<{
request: Request,
response: ?Response,
}> {
static Text = Text.extends({
static Text = styled(Text)({
flex: 1,
textAlign: 'right',
paddingRight: 10,
@@ -389,7 +390,7 @@ class DurationColumn extends PureComponent<{
class SizeColumn extends PureComponent<{
response: ?Response,
}> {
static Text = Text.extends({
static Text = styled(Text)({
flex: 1,
textAlign: 'right',
paddingRight: 10,

View File

@@ -20,13 +20,13 @@ type SandboxState = {|
showFeedback: boolean,
|};
const BigButton = Button.extends({
const BigButton = styled(Button)({
flexGrow: 1,
fontSize: 24,
padding: 20,
});
const ButtonContainer = FlexColumn.extends({
const ButtonContainer = styled(FlexColumn)({
alignItems: 'center',
padding: 20,
});
@@ -42,7 +42,7 @@ export default class SandboxView extends SonarPlugin<SandboxState> {
static id = 'Sandbox';
static icon = 'translate';
static TextInput = styled.textInput({
static TextInput = styled('input')({
border: `1px solid ${colors.light10}`,
fontSize: '1em',
padding: '0 5px',
@@ -52,13 +52,13 @@ export default class SandboxView extends SonarPlugin<SandboxState> {
flexGrow: 1,
});
static FeedbackMessage = styled.text({
static FeedbackMessage = styled('span')({
fontSize: '1.2em',
paddingTop: '10px',
color: 'green',
});
static TextInputLayout = FlexColumn.extends({
static TextInputLayout = styled(FlexColumn)({
float: 'left',
justifyContent: 'center',
flexGrow: 1,

View File

@@ -7,6 +7,6 @@
import styled from '../styled/index.js';
export default styled.view({
export default styled('div')({
display: 'block',
});

View File

@@ -6,8 +6,9 @@
*/
import FlexBox from './FlexBox.js';
import styled from '../styled/index.js';
export default FlexBox.extends({
export default styled(FlexBox)({
height: '100%',
overflow: 'auto',
position: 'relative',

View File

@@ -7,12 +7,12 @@
import Glyph from './Glyph.js';
import styled from '../styled/index.js';
import type {StyledComponent} from '../styled/index.js';
import {findDOMNode} from 'react-dom';
import PropTypes from 'prop-types';
import {colors} from './colors.js';
import {connect} from 'react-redux';
import electron from 'electron';
import {keyframes} from 'react-emotion';
const borderColor = props => {
if (!props.windowIsFocused) {
@@ -35,129 +35,122 @@ const borderBottomColor = props => {
}
};
const StyledButton = styled.view(
{
backgroundColor: props => {
if (!props.windowIsFocused) {
return colors.macOSTitleBarButtonBackgroundBlur;
} else {
return colors.white;
}
},
backgroundImage: props => {
if (props.windowIsFocused) {
if (props.depressed) {
return `linear-gradient(to bottom, ${
colors.macOSTitleBarBorderBlur
} 1px, ${colors.macOSTitleBarButtonBorderBlur} 0%, ${
colors.macOSTitleBarButtonBackgroundActive
} 100%)`;
} else {
return `linear-gradient(to bottom, transparent 0%,${
colors.macOSTitleBarButtonBackground
} 100%)`;
}
} else {
return 'none';
}
},
const backgroundImage = props => {
if (props.windowIsFocused) {
if (props.depressed) {
return `linear-gradient(to bottom, ${
colors.macOSTitleBarBorderBlur
} 1px, ${colors.macOSTitleBarButtonBorderBlur} 0%, ${
colors.macOSTitleBarButtonBackgroundActive
} 100%)`;
} else {
return `linear-gradient(to bottom, transparent 0%,${
colors.macOSTitleBarButtonBackground
} 100%)`;
}
} else {
return 'none';
}
};
const color = props => {
if (props.type === 'danger' && props.windowIsFocused) {
return colors.red;
} else if (props.disabled) {
return colors.macOSTitleBarIconBlur;
} else {
return colors.light50;
}
};
const pulse = keyframes({
'0%': {
boxShadow: `0 0 4px 0 ${colors.macOSTitleBarIconSelected}`,
},
'70%': {
boxShadow: '0 0 4px 6px transparent',
},
'100%': {
boxShadow: '0 0 4px 0 transparent',
},
});
const StyledButton = styled('div')(props => ({
backgroundColor: !props.windowIsFocused
? colors.macOSTitleBarButtonBackgroundBlur
: colors.white,
backgroundImage: backgroundImage(props),
borderStyle: 'solid',
borderWidth: 1,
borderColor: borderColor(props),
borderBottomColor: borderBottomColor(props),
fontSize: props.compact === true ? 11 : '1em',
color: color(props),
borderRadius: 4,
position: 'relative',
padding: '0 6px',
height: props.compact === true ? 24 : 28,
margin: 0,
marginLeft: props.inButtonGroup === true ? 0 : 10,
minWidth: 34,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
boxShadow:
props.pulse && props.windowIsFocused
? `0 0 0 ${colors.macOSTitleBarIconSelected}`
: '',
animation: props.pulse && props.windowIsFocused ? `${pulse} 1s infinite` : '',
'&:not(:first-child)': {
borderTopLeftRadius: props.inButtonGroup === true ? 0 : 4,
borderBottomLeftRadius: props.inButtonGroup === true ? 0 : 4,
},
'&:not(:last-child)': {
borderTopRightRadius: props.inButtonGroup === true ? 0 : 4,
borderBottomRightRadius: props.inButtonGroup === true ? 0 : 4,
borderRight: props.inButtonGroup === true ? 0 : '',
},
'&:first-of-type': {
marginLeft: 0,
},
'&:active': {
borderColor: colors.macOSTitleBarButtonBorder,
borderBottomColor: colors.macOSTitleBarButtonBorderBottom,
background: `linear-gradient(to bottom, ${
colors.macOSTitleBarButtonBackgroundActiveHighlight
} 1px, ${colors.macOSTitleBarButtonBackgroundActive} 0%, ${
colors.macOSTitleBarButtonBorderBlur
} 100%)`,
},
'&:disabled': {
borderColor: borderColor(props),
borderBottomColor: borderBottomColor(props),
pointerEvents: 'none',
},
'&:hover::before': {
content: props.dropdown ? "''" : '',
position: 'absolute',
bottom: 1,
right: 2,
borderStyle: 'solid',
borderWidth: 1,
borderColor,
borderBottomColor,
fontSize: props => (props.compact === true ? 11 : '1em'),
color: props => {
if (props.type === 'danger' && props.windowIsFocused) {
return colors.red;
} else if (props.disabled) {
return colors.macOSTitleBarIconBlur;
} else {
return colors.light50;
}
},
borderRadius: 4,
position: 'relative',
padding: '0 6px',
height: props => (props.compact === true ? 24 : 28),
margin: 0,
marginLeft: props => (props.inButtonGroup === true ? 0 : 10),
minWidth: 34,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
boxShadow: props =>
props.pulse && props.windowIsFocused
? `0 0 0 ${colors.macOSTitleBarIconSelected}`
: '',
animation: props =>
props.pulse && props.windowIsFocused ? 'pulse 1s infinite' : '',
'&:not(:first-child)': {
borderTopLeftRadius: props => (props.inButtonGroup === true ? 0 : 4),
borderBottomLeftRadius: props => (props.inButtonGroup === true ? 0 : 4),
},
'&:not(:last-child)': {
borderTopRightRadius: props => (props.inButtonGroup === true ? 0 : 4),
borderBottomRightRadius: props => (props.inButtonGroup === true ? 0 : 4),
borderRight: props => (props.inButtonGroup === true ? 0 : ''),
},
'&:first-of-type': {
marginLeft: 0,
},
'&:active': {
borderColor: colors.macOSTitleBarButtonBorder,
borderBottomColor: colors.macOSTitleBarButtonBorderBottom,
background: `linear-gradient(to bottom, ${
colors.macOSTitleBarButtonBackgroundActiveHighlight
} 1px, ${colors.macOSTitleBarButtonBackgroundActive} 0%, ${
colors.macOSTitleBarButtonBorderBlur
} 100%)`,
},
'&:disabled': {
borderColor,
borderBottomColor,
pointerEvents: 'none',
},
'&:hover::before': {
content: props => (props.dropdown ? "''" : ''),
position: 'absolute',
bottom: 1,
right: 2,
borderStyle: 'solid',
borderWidth: '4px 3px 0 3px',
borderColor: props =>
`${colors.macOSTitleBarIcon} transparent transparent transparent`,
},
borderWidth: '4px 3px 0 3px',
borderColor: `${
colors.macOSTitleBarIcon
} transparent transparent transparent`,
},
{
ignoreAttributes: [
'dropdown',
'dispatch',
'compact',
'large',
'windowIsFocused',
'inButtonGroup',
'danger',
'pulse',
],
},
);
}));
const Icon = Glyph.extends(
{
marginRight: props => (props.hasText ? 3 : 0),
},
{
ignoreAttributes: ['hasText', 'type'],
},
);
const Icon = styled(Glyph)(({hasText}) => ({
marginRight: hasText ? 3 : 0,
}));
type Props = {
/**
@@ -252,7 +245,7 @@ type State = {
* @example Disabled button
* <Button disabled={true}>Click me</Button>
*/
class Button extends styled.StylableComponent<
class Button extends React.Component<
Props & {windowIsFocused: boolean},
State,
> {
@@ -350,25 +343,6 @@ class Button extends styled.StylableComponent<
inButtonGroup={this.context.inButtonGroup}>
{iconComponent}
{children}
{this.props.pulse === true && (
<style
dangerouslySetInnerHTML={{
__html: `
@keyframes pulse {
0% {
box-shadow: 0 0 4px 0 ${colors.macOSTitleBarIconSelected};
}
70% {
box-shadow: 0 0 4px 6px transparent;
}
100% {
box-shadow: 0 0 4px 0 transparent;
}
}
`,
}}
/>
)}
</StyledButton>
);
}

View File

@@ -10,7 +10,7 @@ import {Component} from 'react';
const PropTypes = require('prop-types');
const ButtonGroupContainer = styled.view({
const ButtonGroupContainer = styled('div')({
display: 'inline-flex',
marginLeft: 10,
'&:first-child': {

View File

@@ -13,7 +13,7 @@ type CheckboxProps = {
onChange: (checked: boolean) => void,
};
const CheckboxContainer = styled.textInput({
const CheckboxContainer = styled('input')({
display: 'inline-block',
marginRight: 5,
verticalAlign: 'middle',

View File

@@ -1,12 +0,0 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import styled from '../styled/index.js';
export default styled.view({
marginBottom: 10,
});

View File

@@ -1,33 +0,0 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import styled from '../styled/index.js';
import {colors} from './colors.js';
export default styled.view(
{
backgroundColor: ({active, windowFocused}) => {
if (active && windowFocused) {
return colors.macOSTitleBarIconSelected;
} else if (active && !windowFocused) {
return colors.macOSTitleBarBorderBlur;
} else {
return 'none';
}
},
color: ({active, windowFocused}) =>
active && windowFocused ? colors.white : colors.macOSSidebarSectionItem,
lineHeight: '25px',
padding: '0 10px',
'&[disabled]': {
color: 'rgba(0, 0, 0, 0.5)',
},
},
{
ignoreAttributes: ['active', 'windowFocused'],
},
);

View File

@@ -7,6 +7,6 @@
import styled from '../styled/index.js';
export default styled.view({
export default styled('div')({
fontFamily: 'monospace',
});

View File

@@ -6,7 +6,6 @@
*/
import FlexColumn from './FlexColumn.js';
import styled from '../styled/index.js';
import PropTypes from 'prop-types';
type MenuTemplate = Array<Electron$MenuItemOptions>;
@@ -18,7 +17,7 @@ type Props = {
component: React.ComponentType<any> | string,
};
export default class ContextMenu extends styled.StylablePureComponent<Props> {
export default class ContextMenu extends React.Component<Props> {
static defaultProps = {
component: FlexColumn,
};

View File

@@ -13,7 +13,7 @@ const PropTypes = require('prop-types');
type MenuTemplate = Array<Electron$MenuItemOptions>;
const Container = styled.view({
const Container = styled('div')({
display: 'contents',
});

View File

@@ -6,9 +6,10 @@
*/
import styled from '../styled/index.js';
import React from 'react';
import CodeBlock from './CodeBlock.js';
const ErrorBlockContainer = CodeBlock.extends({
const ErrorBlockContainer = styled(CodeBlock)({
backgroundColor: '#f2dede',
border: '1px solid #ebccd1',
borderRadius: 4,
@@ -17,7 +18,7 @@ const ErrorBlockContainer = CodeBlock.extends({
padding: 10,
});
export default class ErrorBlock extends styled.StylableComponent<{
export default class ErrorBlock extends React.Component<{
error: Error | string | void,
className?: string,
}> {

View File

@@ -11,13 +11,14 @@ import Heading from './Heading.js';
import Button from './Button.js';
import View from './View.js';
import LogManager from '../../fb-stubs/Logger.js';
import styled from '../styled/index.js';
const ErrorBoundaryContainer = View.extends({
const ErrorBoundaryContainer = styled(View)({
overflow: 'auto',
padding: 10,
});
const ErrorBoundaryStack = ErrorBlock.extends({
const ErrorBoundaryStack = styled(ErrorBlock)({
marginBottom: 10,
whiteSpace: 'pre',
});

View File

@@ -6,13 +6,9 @@
*/
import View from './View.js';
import styled from '../styled/index.js';
export default View.extends(
{
display: 'flex',
flexShrink: props => (props.shrink == null || props.shrink ? 1 : 0),
},
{
ignoreAttributes: ['shrink'],
},
);
export default styled(View)(({shrink}) => ({
display: 'flex',
flexShrink: shrink == null || shrink ? 1 : 0,
}));

View File

@@ -6,8 +6,9 @@
*/
import View from './View.js';
import styled from '../styled/index.js';
export default View.extends({
export default styled(View)({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',

View File

@@ -6,7 +6,8 @@
*/
import FlexBox from './FlexBox.js';
import styled from '../styled/index.js';
export default FlexBox.extends({
export default styled(FlexBox)({
flexDirection: 'column',
});

View File

@@ -6,7 +6,8 @@
*/
import FlexBox from './FlexBox.js';
import styled from '../styled/index.js';
export default FlexBox.extends({
export default styled(FlexBox)({
flexDirection: 'row',
});

View File

@@ -8,21 +8,17 @@
import {Component} from 'react';
import Box from './Box.js';
import {colors} from './colors';
import styled from '../styled/index.js';
const FocusableBoxBorder = Box.extends(
{
border: `1px solid ${colors.highlight}`,
bottom: '0',
left: '0',
pointerEvents: 'none',
position: 'absolute',
right: '0',
top: '0',
},
{
ignoreAttributes: [],
},
);
const FocusableBoxBorder = styled(Box)({
border: `1px solid ${colors.highlight}`,
bottom: '0',
left: '0',
pointerEvents: 'none',
position: 'absolute',
right: '0',
top: '0',
});
export default class FocusableBox extends Component<
Object,

View File

@@ -5,39 +5,28 @@
* @format
*/
import React from 'react';
import styled from '../styled/index.js';
const PropTypes = require('prop-types');
import {getIconUrl} from '../../utils/icons.js';
const ColoredIconBlack = styled.image(
{
height: props => props.size,
verticalAlign: 'middle',
width: props => props.size,
},
{
ignoreAttributes: ['size'],
},
);
const ColoredIconBlack = styled('img')(({size}) => ({
height: size,
verticalAlign: 'middle',
width: size,
}));
const ColoredIconCustom = styled.view(
{
height: props => props.size,
verticalAlign: 'middle',
width: props => props.size,
backgroundColor: props => props.color,
display: 'inline-block',
maskImage: props => `url('${props.src}')`,
maskSize: '100% 100%',
// $FlowFixMe: prefixed property
WebkitMaskImage: props => `url('${props.src}')`,
// $FlowFixMe: prefixed property
WebkitMaskSize: '100% 100%',
},
{
ignoreAttributes: ['color', 'size', 'src'],
},
);
const ColoredIconCustom = styled('div')(props => ({
height: props.size,
verticalAlign: 'middle',
width: props.size,
backgroundColor: props.color,
display: 'inline-block',
maskImage: `url('${props.src}')`,
maskSize: '100% 100%',
WebkitMaskImage: `url('${props.src}')`,
WebkitMaskSize: '100% 100%',
}));
export function ColoredIcon(
props: {|
@@ -84,7 +73,7 @@ ColoredIcon.contextTypes = {
glyphColor: PropTypes.string,
};
export default class Glyph extends styled.StylablePureComponent<{
export default class Glyph extends React.Component<{
name: string,
size?: 8 | 10 | 12 | 16 | 18 | 20 | 24 | 32,
variant?: 'filled' | 'outline',

View File

@@ -7,7 +7,7 @@
import styled from '../styled/index.js';
const LargeHeading = styled.view({
const LargeHeading = styled('div')({
fontSize: 18,
fontWeight: 'bold',
lineHeight: '20px',
@@ -15,7 +15,7 @@ const LargeHeading = styled.view({
marginBottom: 10,
});
const SmallHeading = styled.view({
const SmallHeading = styled('div')({
fontSize: 12,
color: '#90949c',
fontWeight: 'bold',

View File

@@ -7,7 +7,7 @@
import styled from '../styled/index.js';
export default styled.view({
export default styled('div')({
backgroundColor: '#c9ced4',
height: 1,
margin: '5px 0',

View File

@@ -8,13 +8,13 @@
import styled from '../styled/index.js';
import {colors} from './colors.js';
export const inputStyle = {
export const inputStyle = (compact: boolean) => ({
border: `1px solid ${colors.light15}`,
borderRadius: 4,
font: 'inherit',
fontSize: '1em',
height: (props: Object) => (props.compact ? '17px' : '28px'),
lineHeight: (props: Object) => (props.compact ? '17px' : '28px'),
height: compact ? '17px' : '28px',
lineHeight: compact ? '17px' : '28px',
marginRight: 5,
'&:disabled': {
@@ -22,17 +22,12 @@ export const inputStyle = {
borderColor: '#ccc',
cursor: 'not-allowed',
},
};
});
const Input = styled.textInput(
{
...inputStyle,
padding: props => (props.compact ? '0 5px' : '0 10px'),
},
{
ignoreAttributes: ['compact'],
},
);
const Input = styled('input')(({compact}) => ({
...inputStyle(compact),
padding: compact ? '0 5px' : '0 10px',
}));
Input.defaultProps = {
type: 'text',

View File

@@ -86,11 +86,11 @@ type InteractiveState = {|
resizingInitialCursor: ?CursorState,
|};
const InteractiveContainer = styled.view({
const InteractiveContainer = styled('div')({
willChange: 'transform, height, width, z-index',
});
export default class Interactive extends styled.StylableComponent<
export default class Interactive extends React.Component<
InteractiveProps,
InteractiveState,
> {

View File

@@ -7,7 +7,7 @@
import styled from '../styled/index.js';
export default styled.view({
export default styled('div')({
fontSize: 12,
fontWeight: 'bold',
});

View File

@@ -10,18 +10,13 @@ import {colors} from './colors.js';
import {Component} from 'react';
import {shell} from 'electron';
const StyledLink = styled.text(
{
color: colors.highlight,
'&:hover': {
cursor: 'pointer',
textDecoration: 'underline',
},
const StyledLink = styled('span')({
color: colors.highlight,
'&:hover': {
cursor: 'pointer',
textDecoration: 'underline',
},
{
ignoreAttributes: [],
},
);
});
export default class Link extends Component<{
href: string,

View File

@@ -5,10 +5,10 @@
* @format
*/
import type {StyledComponent} from '../styled/index.js';
import styled from '../styled/index.js';
import {keyframes} from 'react-emotion';
const animation = styled.keyframes({
const animation = keyframes({
'0%': {
transform: 'rotate(0deg)',
},
@@ -17,23 +17,16 @@ const animation = styled.keyframes({
},
});
const LoadingIndicator: StyledComponent<{
size?: number,
}> = styled.view(
{
animation: `${animation} 1s infinite linear`,
width: props => props.size,
height: props => props.size,
minWidth: props => props.size,
minHeight: props => props.size,
borderRadius: '50%',
border: props => `${props.size / 6}px solid rgba(0, 0, 0, 0.2)`,
borderLeftColor: 'rgba(0, 0, 0, 0.4)',
},
{
ignoreAttributes: ['size'],
},
);
const LoadingIndicator = styled('div')(props => ({
animation: `${animation} 1s infinite linear`,
width: props.size,
height: props.size,
minWidth: props.size,
minHeight: props.size,
borderRadius: '50%',
border: `${props.size / 6}px solid rgba(0, 0, 0, 0.2)`,
borderLeftColor: 'rgba(0, 0, 0, 0.4)',
}));
LoadingIndicator.defaultProps = {
size: 50,

View File

@@ -8,7 +8,7 @@
import styled from '../styled/index.js';
import {Component} from 'react';
const Overlay = styled.view({
const Overlay = styled('div')({
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
bottom: 0,

View File

@@ -37,19 +37,13 @@ type TabSizes = {
[key: string]: Rect,
};
const OrderableContainer = styled.view({
const OrderableContainer = styled('div')({
position: 'relative',
});
const OrderableItemContainer = styled.view(
{
display: props =>
props.orientation === 'vertical' ? 'block' : 'inline-block',
},
{
ignoreAttributes: ['orientation'],
},
);
const OrderableItemContainer = styled('div')(props => ({
display: props.orientation === 'vertical' ? 'block' : 'inline-block',
}));
class OrderableItem extends Component<{
orientation: OrderableOrientation,
@@ -79,7 +73,7 @@ class OrderableItem extends Component<{
}
}
export default class Orderable extends styled.StylableComponent<
export default class Orderable extends React.Component<
OrderableProps,
OrderableState,
> {

View File

@@ -5,18 +5,16 @@
* @format
*/
import FlexColumn from './FlexColumn.js';
import React from 'react';
import styled from '../styled/index.js';
import FlexColumn from './FlexColumn.js';
import FlexBox from './FlexBox.js';
import {colors} from './colors.js';
import Glyph from './Glyph.js';
const BORDER = '1px solid #dddfe2';
const ignoreAttributes = ['floating', 'padded'];
const Chevron = Glyph.extends({
const Chevron = styled(Glyph)({
marginRight: 4,
marginLeft: -2,
marginBottom: 1,
@@ -25,7 +23,7 @@ const Chevron = Glyph.extends({
/**
* A Panel component.
*/
export default class Panel extends styled.StylableComponent<
export default class Panel extends React.Component<
{|
/**
* Class name to customise styling.
@@ -84,46 +82,37 @@ export default class Panel extends styled.StylableComponent<
collapsable: true,
};
static PanelContainer = FlexColumn.extends(
{
flexShrink: 0,
padding: props => (props.floating ? 10 : 0),
borderBottom: props => (props.collapsed ? 'none' : BORDER),
},
{ignoreAttributes: ['collapsed', ...ignoreAttributes]},
);
static PanelContainer = styled(FlexColumn)(props => ({
flexShrink: 0,
padding: props.floating ? 10 : 0,
borderBottom: props.collapsed ? 'none' : BORDER,
}));
static PanelHeader = FlexBox.extends(
{
backgroundColor: '#f6f7f9',
border: props => (props.floating ? BORDER : 'none'),
borderBottom: BORDER,
borderTopLeftRadius: 2,
borderTopRightRadius: 2,
justifyContent: 'space-between',
lineHeight: '27px',
fontWeight: 500,
flexShrink: 0,
padding: props => (props.padded ? '0 10px' : 0),
'&:not(:first-child)': {
borderTop: BORDER,
},
static PanelHeader = styled(FlexBox)(props => ({
backgroundColor: '#f6f7f9',
border: props.floating ? BORDER : 'none',
borderBottom: BORDER,
borderTopLeftRadius: 2,
borderTopRightRadius: 2,
justifyContent: 'space-between',
lineHeight: '27px',
fontWeight: 500,
flexShrink: 0,
padding: props.padded ? '0 10px' : 0,
'&:not(:first-child)': {
borderTop: BORDER,
},
{ignoreAttributes},
);
}));
static PanelBody = FlexColumn.extends(
{
backgroundColor: '#fff',
border: props => (props.floating ? BORDER : 'none'),
borderBottomLeftRadius: 2,
borderBottomRightRadius: 2,
borderTop: 'none',
flexGrow: 1,
padding: props => (props.padded ? 10 : 0),
},
{ignoreAttributes},
);
static PanelBody = styled(FlexColumn)(props => ({
backgroundColor: '#fff',
border: props.floating ? BORDER : 'none',
borderBottomLeftRadius: 2,
borderBottomRightRadius: 2,
borderTop: 'none',
flexGrow: 1,
padding: props.padded ? 10 : 0,
}));
state = {
collapsed: this.props.collapsed == null ? false : this.props.collapsed,
};

View File

@@ -10,7 +10,7 @@ import FlexColumn from './FlexColumn.js';
import styled from '../styled/index.js';
import {colors} from './colors.js';
const Anchor = styled.image({
const Anchor = styled('img')({
zIndex: 6,
position: 'absolute',
bottom: 0,
@@ -18,7 +18,7 @@ const Anchor = styled.image({
transform: 'translate(-50%, calc(100% + 2px))',
});
const PopoverContainer = FlexColumn.extends({
const PopoverContainer = styled(FlexColumn)({
backgroundColor: colors.white,
borderRadius: 7,
border: '1px solid rgba(0,0,0,0.3)',

View File

@@ -8,7 +8,7 @@
import styled from '../styled/index.js';
import {Component} from 'react';
const IFrame = styled.customHTMLTag('iframe', {
const IFrame = styled('iframe')({
height: '100%',
width: '100%',
border: 'none',

View File

@@ -5,43 +5,30 @@
* @format
*/
import type {StyledComponent} from '../styled/index.js';
import Interactive from './Interactive.js';
import FlexColumn from './FlexColumn.js';
import {colors} from './colors';
import {Component} from 'react';
import styled from '../styled/index.js';
const SidebarInteractiveContainer = Interactive.extends({
const SidebarInteractiveContainer = styled(Interactive)({
flex: 'none',
});
type SidebarPosition = 'left' | 'top' | 'right' | 'bottom';
const SidebarContainer: StyledComponent<{
position: SidebarPosition,
overflow?: boolean,
}> = FlexColumn.extends(
{
backgroundColor: props =>
props.backgroundColor || colors.macOSTitleBarBackgroundBlur,
borderLeft: props =>
props.position === 'right' ? '1px solid #b3b3b3' : 'none',
borderTop: props =>
props.position === 'bottom' ? '1px solid #b3b3b3' : 'none',
borderRight: props =>
props.position === 'left' ? '1px solid #b3b3b3' : 'none',
borderBottom: props =>
props.position === 'top' ? '1px solid #b3b3b3' : 'none',
height: '100%',
overflowX: 'hidden',
overflowY: 'auto',
textOverflow: props => (props.overflow ? 'ellipsis' : 'auto'),
whiteSpace: props => (props.overflow ? 'nowrap' : 'normal'),
},
{
ignoreAttributes: ['backgroundColor', 'position'],
},
);
const SidebarContainer = styled(FlexColumn)(props => ({
backgroundColor: props.backgroundColor || colors.macOSTitleBarBackgroundBlur,
borderLeft: props.position === 'right' ? '1px solid #b3b3b3' : 'none',
borderTop: props.position === 'bottom' ? '1px solid #b3b3b3' : 'none',
borderRight: props.position === 'left' ? '1px solid #b3b3b3' : 'none',
borderBottom: props.position === 'top' ? '1px solid #b3b3b3' : 'none',
height: '100%',
overflowX: 'hidden',
overflowY: 'auto',
textOverflow: props.overflow ? 'ellipsis' : 'auto',
whiteSpace: props.overflow ? 'nowrap' : 'normal',
}));
type SidebarProps = {
/**

View File

@@ -7,8 +7,9 @@
import {colors} from './colors.js';
import Label from './Label.js';
import styled from '../styled/index.js';
export default Label.extends({
export default styled(Label)({
color: colors.blackAlpha30,
fontSize: 12,
padding: 10,

View File

@@ -12,45 +12,38 @@ import FlexRow from './FlexRow.js';
import {colors} from './colors.js';
import Tab from './Tab.js';
const TabList = FlexRow.extends({
const TabList = styled(FlexRow)({
alignItems: 'stretch',
});
const TabListItem = styled.view(
{
backgroundColor: props => (props.active ? colors.light15 : colors.light02),
borderBottom: '1px solid #dddfe2',
boxShadow: props =>
props.active ? 'inset 0px 0px 3px rgba(0,0,0,0.25)' : 'none',
color: colors.dark80,
flex: 1,
fontSize: 13,
lineHeight: '28px',
overflow: 'hidden',
padding: '0 10px',
position: 'relative',
textAlign: 'center',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
const TabListItem = styled('div')(props => ({
backgroundColor: props.active ? colors.light15 : colors.light02,
borderBottom: '1px solid #dddfe2',
boxShadow: props.active ? 'inset 0px 0px 3px rgba(0,0,0,0.25)' : 'none',
color: colors.dark80,
flex: 1,
fontSize: 13,
lineHeight: '28px',
overflow: 'hidden',
padding: '0 10px',
position: 'relative',
textAlign: 'center',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
'&:hover': {
backgroundColor: props =>
props.active ? colors.light15 : colors.light05,
},
'&:hover': {
backgroundColor: props.active ? colors.light15 : colors.light05,
},
{
ignoreAttributes: ['active'],
},
);
}));
const TabListAddItem = TabListItem.extends({
const TabListAddItem = styled(TabListItem)({
borderRight: 'none',
flex: 0,
flexGrow: 0,
fontWeight: 'bold',
});
const CloseButton = styled.view({
const CloseButton = styled('div')({
color: '#000',
float: 'right',
fontSize: 10,
@@ -69,11 +62,11 @@ const CloseButton = styled.view({
},
});
const OrderableContainer = styled.view({
const OrderableContainer = styled('div')({
display: 'inline-block',
});
const TabContent = styled.view({
const TabContent = styled('div')({
height: '100%',
overflow: 'auto',
width: '100%',

View File

@@ -5,155 +5,31 @@
* @format
*/
import type {StyledComponent} from '../styled/index.js';
import styled from '../styled/index.js';
/**
* A Text component.
*/
const Text: StyledComponent<{
/**
* Color of text.
*/
color?: string,
/**
* Whether this text is bold. Equivalent to the following CSS:
*
* font-weight: bold;
*/
bold?: boolean,
/**
* Whether this text is italic. Equivalent to the following CSS:
*
* font-style: italic;
*/
italic?: boolean,
/**
* Whether to format the text as code. Equivalent to the following CSS:
*
* font-size: Andale Mono, monospace;
* overflow: auto;
* user-select: text;
* white-space: pre-wrap;
* word-wrap: break-word;
*/
code?: boolean,
/**
* Whether this text is underlined. Equivalent to the following CSS:
*
* text-decoration: underline;
*/
underline?: boolean,
/**
* Whether this text is striked. Equivalent to the following CSS:
*
* text-decoration: line-through;
*/
strike?: boolean,
/**
* Whether this text is selectable by the cursor. Equivalent to the following CSS:
*
* user-select: text;
*/
selectable?: boolean,
/**
* Alignment of the text. Equivalent to the `text-align` CSS rule.
*/
align?: 'left' | 'center' | 'right',
/**
* Font size to use. Equivalent to the `font-size` CSS rule.
*/
size?: string | number,
/**
* Font family to use. Equivalent to the `font-family` CSS rule.
*/
family?: string,
/**
* Word wrap to use. Equivalent to the `word-wrap` CSS rule.
*/
wordWrap?: string,
/**
* White space to use. Equivalent to the `white-space` CSS rule.
*/
whiteSpace?: string,
}> = styled.text(
{
color: props => (props.color ? props.color : 'inherit'),
display: 'inline',
fontWeight: props => (props.bold ? 'bold' : 'inherit'),
fontStyle: props => (props.italic ? 'italic' : 'normal'),
textAlign: props => props.align || 'left',
fontSize: props => {
if (props.size == null && props.code) {
return 12;
} else {
return props.size;
}
},
fontFamily: props => {
if (props.code) {
return 'SF Mono, Monaco, Andale Mono, monospace';
} else {
return props.family;
}
},
overflow: props => {
if (props.code) {
return 'auto';
} else {
return 'visible';
}
},
textDecoration: props => {
if (props.underline) {
return 'underline';
} else if (props.strike) {
return 'line-through';
} else {
return 'none';
}
},
userSelect: props => {
if (
props.selectable ||
(props.code && typeof props.selectable === 'undefined')
) {
return 'text';
} else {
return 'none';
}
},
wordWrap: props => {
if (props.code) {
return 'break-word';
} else {
return props.wordWrap;
}
},
whiteSpace: props => {
if (props.code && typeof props.whiteSpace === 'undefined') {
return 'pre';
} else {
return props.whiteSpace;
}
},
},
{
ignoreAttributes: [
'selectable',
'whiteSpace',
'wordWrap',
'align',
'code',
'family',
'size',
'bold',
'italic',
'strike',
'underline',
'color',
],
},
);
const Text = styled('span')(props => ({
color: props.color ? props.color : 'inherit',
display: 'inline',
fontWeight: props.bold ? 'bold' : 'inherit',
fontStyle: props.italic ? 'italic' : 'normal',
textAlign: props.align || 'left',
fontSize: props.size == null && props.code ? 12 : props.size,
fontFamily: props.code
? 'SF Mono, Monaco, Andale Mono, monospace'
: props.family,
overflow: props.code ? 'auto' : 'visible',
userSelect:
props.selectable || (props.code && typeof props.selectable === 'undefined')
? 'text'
: 'none',
wordWrap: props.code ? 'break-word' : props.wordWrap,
whiteSpace:
props.code && typeof props.whiteSpace === 'undefined'
? 'pre'
: props.whiteSpace,
}));
export default Text;

View File

@@ -10,7 +10,7 @@ import styled from '../styled/index.js';
/**
* A TextParagraph component.
*/
const TextParagraph = styled.view({
const TextParagraph = styled('div')({
marginBottom: 10,
'&:last-child': {

View File

@@ -8,15 +8,9 @@
import styled from '../styled/index.js';
import {inputStyle} from './Input.js';
export default styled.customHTMLTag(
'textarea',
{
...inputStyle,
lineHeight: 'normal',
padding: props => (props.compact ? '5px' : '8px'),
resize: 'none',
},
{
ignoreAttributes: ['compact'],
},
);
export default styled('textarea')(({compact}) => ({
...inputStyle(compact),
lineHeight: 'normal',
padding: compact ? '5px' : '8px',
resize: 'none',
}));

View File

@@ -9,11 +9,11 @@ import React from 'react';
import styled from '../styled/index.js';
import {colors} from './colors.js';
export const StyledButton = styled.view({
export const StyledButton = styled('div')(props => ({
cursor: 'pointer',
width: '30px',
height: '16px',
background: props => (props.toggled ? colors.green : colors.grey),
background: props.toggled ? colors.green : colors.grey,
display: 'block',
borderRadius: '100px',
position: 'relative',
@@ -22,14 +22,14 @@ export const StyledButton = styled.view({
content: `''`,
position: 'absolute',
top: '3px',
left: props => (props.toggled ? '18px' : '3px'),
left: props.toggled ? '18px' : '3px',
width: '10px',
height: '10px',
background: 'white',
borderRadius: '100px',
transition: 'all cubic-bezier(0.3, 1.5, 0.7, 1) 0.3s',
},
});
}));
type Props = {
/**
@@ -52,7 +52,7 @@ type Props = {
* <ToggleButton onClick={handler} toggled={boolean}/>
* ```
*/
export default class ToggleButton extends styled.StylableComponent<Props> {
export default class ToggleButton extends React.Component<Props> {
render() {
return (
<StyledButton toggled={this.props.toggled} onClick={this.props.onClick} />

View File

@@ -5,44 +5,33 @@
* @format
*/
import type {StyledComponent} from '../styled/index.js';
import {colors} from './colors.js';
import FlexRow from './FlexRow.js';
import FlexBox from './FlexBox.js';
import styled from '../styled/index.js';
/**
* A toolbar.
*/
const Toolbar: StyledComponent<{
/**
* Position of the toolbar. Dictates the location of the border.
*/
position?: 'top' | 'bottom',
compact?: boolean,
}> = FlexRow.extends(
{
backgroundColor: colors.light02,
borderBottom: props =>
props.position === 'bottom'
? 'none'
: `1px solid ${colors.sectionHeaderBorder}`,
borderTop: props =>
props.position === 'bottom'
? `1px solid ${colors.sectionHeaderBorder}`
: 'none',
flexShrink: 0,
height: props => (props.compact ? 28 : 42),
lineHeight: '32px',
alignItems: 'center',
padding: 6,
width: '100%',
},
{
ignoreAttributes: ['position'],
},
);
const Toolbar = styled(FlexRow)(props => ({
backgroundColor: colors.light02,
borderBottom:
props.position === 'bottom'
? 'none'
: `1px solid ${colors.sectionHeaderBorder}`,
borderTop:
props.position === 'bottom'
? `1px solid ${colors.sectionHeaderBorder}`
: 'none',
flexShrink: 0,
height: props.compact ? 28 : 42,
lineHeight: '32px',
alignItems: 'center',
padding: 6,
width: '100%',
}));
export const Spacer = FlexBox.extends({
export const Spacer = styled(FlexBox)({
flexGrow: 1,
});

View File

@@ -12,7 +12,7 @@ import {Component} from 'react';
const PropTypes = require('prop-types');
const TooltipContainer = styled.view({
const TooltipContainer = styled('div')({
display: 'contents',
});

View File

@@ -10,25 +10,20 @@ import {Component} from 'react';
const PropTypes = require('prop-types');
const TooltipBubble = styled.view(
{
backgroundColor: '#000',
lineHeight: '25px',
padding: '0 6px',
borderRadius: 4,
position: 'absolute',
width: 'auto',
top: props => props.top,
left: props => props.left,
zIndex: 99999999999,
pointerEvents: 'none',
color: '#fff',
marginTop: '-30px',
},
{
ignoreAttributes: ['top', 'left'],
},
);
const TooltipBubble = styled('div')(props => ({
backgroundColor: '#000',
lineHeight: '25px',
padding: '0 6px',
borderRadius: 4,
position: 'absolute',
width: 'auto',
top: props.top,
left: props.left,
zIndex: 99999999999,
pointerEvents: 'none',
color: '#fff',
marginTop: '-30px',
}));
type TooltipProps = {
children: React$Node,

View File

@@ -7,7 +7,7 @@
import styled from '../styled/index.js';
export default styled.view({
export default styled('div')({
backgroundColor: '#c9ced4',
width: 3,
margin: '0',

View File

@@ -7,16 +7,11 @@
import styled from '../styled/index.js';
const View = styled.view(
{
height: props => (props.fill ? '100%' : 'auto'),
overflow: props => (props.scrollable ? 'auto' : 'visible'),
position: 'relative',
width: props => (props.fill ? '100%' : 'auto'),
},
{
ignoreAttributes: ['fill', 'scrollable'],
},
);
const View = styled('div')(props => ({
height: props.fill ? '100%' : 'auto',
overflow: props.scrollable ? 'auto' : 'visible',
position: 'relative',
width: props.fill ? '100%' : 'auto',
}));
export default View;

View File

@@ -8,33 +8,24 @@
import FlexColumn from './FlexColumn.js';
import {Component} from 'react';
import View from './View.js';
import styled from '../styled/index.js';
const Inner = FlexColumn.extends(
{
alignItems: 'flex-start',
height: props => props.height,
minHeight: '100%',
minWidth: '100%',
overflow: 'visible',
width: '100%',
},
{
ignoreAttributes: ['height'],
},
);
const Inner = styled(FlexColumn)(({height}) => ({
alignItems: 'flex-start',
height,
minHeight: '100%',
minWidth: '100%',
overflow: 'visible',
width: '100%',
}));
const Content = FlexColumn.extends(
{
alignItems: 'flex-start',
height: '100%',
marginTop: props => props.top,
minWidth: '100%',
overflow: 'visible',
},
{
ignoreAttributes: ['top'],
},
);
const Content = styled(FlexColumn)(({top}) => ({
alignItems: 'flex-start',
height: '100%',
marginTop: top,
minWidth: '100%',
overflow: 'visible',
}));
type VirtualListProps = {|
data: Array<any>,

View File

@@ -15,6 +15,7 @@ import {
Input,
View,
} from '../index';
import styled from '../styled/index';
import type {TableBodyRow, TableRows} from 'sonar';
import type {PluginClient} from '../../plugin';
@@ -50,7 +51,7 @@ class ConsoleError extends Component<{
error: Error | string | void,
className?: string,
}> {
static Container = CodeBlock.extends({
static Container = styled(CodeBlock)({
backgroundColor: colors.redTint,
color: colors.red,
overflow: 'auto',
@@ -81,11 +82,11 @@ export class Console extends Component<Props, State> {
},
};
static Window = FlexColumn.extends({
static Window = styled(FlexColumn)({
padding: '15px',
flexGrow: 1,
});
static Input = Input.extends({
static Input = styled(Input)({
width: '100%',
});

View File

@@ -14,55 +14,50 @@ import Popover from '../Popover.js';
import {colors} from '../colors.js';
import Input from '../Input.js';
const NullValue = styled.text({
const NullValue = styled('span')({
color: 'rgb(128, 128, 128)',
});
const UndefinedValue = styled.text({
const UndefinedValue = styled('span')({
color: 'rgb(128, 128, 128)',
});
const StringValue = styled.text({
const StringValue = styled('span')({
color: colors.cherryDark1,
});
const ColorValue = styled.text({
const ColorValue = styled('span')({
color: colors.blueGrey,
});
const SymbolValue = styled.text({
const SymbolValue = styled('span')({
color: 'rgb(196, 26, 22)',
});
const NumberValue = styled.text({
const NumberValue = styled('span')({
color: colors.tealDark1,
});
const ColorBox = styled.text(
{
backgroundColor: props => props.color,
boxShadow: 'inset 0 0 1px rgba(0, 0, 0, 1)',
display: 'inline-block',
height: 12,
marginRight: 5,
verticalAlign: 'middle',
width: 12,
},
{
ignoreAttributes: ['color'],
},
);
const ColorBox = styled('span')(props => ({
backgroundColor: props.color,
boxShadow: 'inset 0 0 1px rgba(0, 0, 0, 1)',
display: 'inline-block',
height: 12,
marginRight: 5,
verticalAlign: 'middle',
width: 12,
}));
const FunctionKeyword = styled.text({
const FunctionKeyword = styled('span')({
color: 'rgb(170, 13, 145)',
fontStyle: 'italic',
});
const FunctionName = styled.text({
const FunctionName = styled('span')({
fontStyle: 'italic',
});
const ColorPickerDescription = styled.view({
const ColorPickerDescription = styled('div')({
display: 'inline',
position: 'relative',
});

View File

@@ -18,34 +18,29 @@ import {clipboard} from 'electron';
const deepEqual = require('deep-equal');
const BaseContainer = styled.view(
{
fontFamily: 'Menlo, monospace',
fontSize: 11,
lineHeight: '17px',
filter: props => (props.disabled ? 'grayscale(100%)' : ''),
margin: props => (props.depth === 0 ? '7.5px 0' : '0'),
paddingLeft: 10,
userSelect: 'text',
},
{
ignoreAttributes: ['depth', 'disabled'],
},
);
const BaseContainer = styled('div')(props => ({
fontFamily: 'Menlo, monospace',
fontSize: 11,
lineHeight: '17px',
filter: props.disabled ? 'grayscale(100%)' : '',
margin: props.depth === 0 ? '7.5px 0' : '0',
paddingLeft: 10,
userSelect: 'text',
}));
const RecursiveBaseWrapper = styled.text({
const RecursiveBaseWrapper = styled('span')({
color: colors.red,
});
const Wrapper = styled.text({
const Wrapper = styled('span')({
color: '#555',
});
const PropertyContainer = styled.text({
const PropertyContainer = styled('span')({
paddingTop: '2px',
});
const ExpandControl = styled.text({
const ExpandControl = styled('span')({
color: '#6e6e6e',
fontSize: 10,
marginLeft: -11,
@@ -53,7 +48,7 @@ const ExpandControl = styled.text({
whiteSpace: 'pre',
});
export const InspectorName = styled.text({
export const InspectorName = styled('span')({
color: colors.grapeDark1,
});
@@ -474,10 +469,10 @@ export default class DataInspector extends Component<DataInspectorProps> {
const keys = getSortedKeys({...value, ...diffValue});
const Added = styled.view({
const Added = styled('div')({
backgroundColor: colors.tealTint70,
});
const Removed = styled.view({
const Removed = styled('div')({
backgroundColor: colors.cherryTint70,
});

View File

@@ -12,7 +12,7 @@ import styled from '../../styled/index.js';
import {getSortedKeys} from './utils.js';
import {PureComponent} from 'react';
const PreviewContainer = styled.text({
const PreviewContainer = styled('span')({
fontStyle: 'italic',
});

View File

@@ -10,7 +10,7 @@ import {colors, darkColors} from './colors.js';
const React = require('react');
const DesktopDropdownContainer = styled.view({
const DesktopDropdownContainer = styled('div')({
borderBottom: `1px solid ${darkColors.dividers}`,
lineHeight: '25px',
marginTop: 5,
@@ -45,22 +45,15 @@ export function DesktopDropdown(props: {|
);
}
const DesktopDropdownItemContainer = styled.view(
{
listStyle: 'none',
opacity: props => (props.onClick || props.onHover ? 1 : 0.5),
padding: '0 20px',
'&:hover': {
backgroundColor: props =>
props.onClick || props.onHover ? colors.highlight : '',
color: props => (props.onClick || props.onHover ? '#fff' : 'inherit'),
},
const DesktopDropdownItemContainer = styled('div')(props => ({
listStyle: 'none',
opacity: props.onClick || props.onHover ? 1 : 0.5,
padding: '0 20px',
'&:hover': {
backgroundColor: props.onClick || props.onHover ? colors.highlight : '',
color: props.onClick || props.onHover ? '#fff' : 'inherit',
},
{
ignoreAttributes: [],
},
);
}));
type DesktopDropdownItemState = {|hovered: boolean|};
@@ -71,7 +64,7 @@ type DesktopDropdownItemProps = {
deactivate?: () => void,
};
export class DesktopDropdownItem extends styled.StylableComponent<
export class DesktopDropdownItem extends React.Component<
DesktopDropdownItemProps,
DesktopDropdownItemState,
> {
@@ -121,7 +114,7 @@ export class DesktopDropdownItem extends styled.StylableComponent<
}
}
export const DesktopDropdownSelectedItem = DesktopDropdownItem.extends({
export const DesktopDropdownSelectedItem = styled(DesktopDropdownItem)({
position: 'relative',
'&::before': {

View File

@@ -25,54 +25,51 @@ import {FixedSizeList as List} from 'react-window';
const ROW_HEIGHT = 23;
const ElementsRowContainer = ContextMenu.extends(
{
flexDirection: 'row',
alignItems: 'center',
backgroundColor: props => {
if (props.selected) {
return colors.macOSTitleBarIconSelected;
} else if (props.focused) {
return colors.lime;
} else if (props.even) {
return colors.light02;
} else {
return '';
}
},
color: props =>
props.selected || props.focused ? colors.white : colors.grapeDark3,
flexShrink: 0,
flexWrap: 'nowrap',
height: ROW_HEIGHT,
minWidth: '100%',
paddingLeft: props => (props.level - 1) * 12,
paddingRight: 20,
position: 'relative',
const backgroundColor = props => {
if (props.selected) {
return colors.macOSTitleBarIconSelected;
} else if (props.focused) {
return colors.lime;
} else if (props.even) {
return colors.light02;
} else {
return '';
}
};
'& *': {
color: props =>
props.selected || props.focused ? `${colors.white} !important` : '',
},
const backgroundColorHover = props => {
if (props.selected) {
return colors.macOSTitleBarIconSelected;
} else if (props.focused) {
return colors.lime;
} else {
return '#EBF1FB';
}
};
'&:hover': {
backgroundColor: props => {
if (props.selected) {
return colors.macOSTitleBarIconSelected;
} else if (props.focused) {
return colors.lime;
} else {
return '#EBF1FB';
}
},
},
const ElementsRowContainer = styled(ContextMenu)(props => ({
flexDirection: 'row',
alignItems: 'center',
backgroundColor: backgroundColor(props),
color: props.selected || props.focused ? colors.white : colors.grapeDark3,
flexShrink: 0,
flexWrap: 'nowrap',
height: ROW_HEIGHT,
minWidth: '100%',
paddingLeft: (props.level - 1) * 12,
paddingRight: 20,
position: 'relative',
'& *': {
color: props.selected || props.focused ? `${colors.white} !important` : '',
},
{
ignoreAttributes: ['level', 'selected', 'even', 'focused'],
},
);
const ElementsRowDecoration = FlexRow.extends({
'&:hover': {
backgroundColor: backgroundColorHover(props),
},
}));
const ElementsRowDecoration = styled(FlexRow)({
flexShrink: 0,
justifyContent: 'flex-end',
alignItems: 'center',
@@ -82,29 +79,24 @@ const ElementsRowDecoration = FlexRow.extends({
top: -1,
});
const ElementsLine = styled.view(
{
backgroundColor: colors.light20,
height: props => props.childrenCount * ROW_HEIGHT - 4,
position: 'absolute',
right: 3,
top: ROW_HEIGHT - 3,
zIndex: 2,
width: 2,
borderRadius: '999em',
},
{
ignoreAttributes: ['childrenCount'],
},
);
const ElementsLine = styled('div')(props => ({
backgroundColor: colors.light20,
height: props.childrenCount * ROW_HEIGHT - 4,
position: 'absolute',
right: 3,
top: ROW_HEIGHT - 3,
zIndex: 2,
width: 2,
borderRadius: '999em',
}));
const DecorationImage = styled.image({
const DecorationImage = styled('img')({
height: 12,
marginRight: 5,
width: 12,
});
const NoShrinkText = Text.extends({
const NoShrinkText = styled(Text)({
flexShrink: 0,
flexWrap: 'nowrap',
overflow: 'hidden',
@@ -112,17 +104,17 @@ const NoShrinkText = Text.extends({
fontWeight: 400,
});
const ElementsRowAttributeContainer = NoShrinkText.extends({
const ElementsRowAttributeContainer = styled(NoShrinkText)({
color: colors.dark80,
fontWeight: 300,
marginLeft: 5,
});
const ElementsRowAttributeKey = styled.text({
const ElementsRowAttributeKey = styled('span')({
color: colors.tomato,
});
const ElementsRowAttributeValue = styled.text({
const ElementsRowAttributeValue = styled('span')({
color: colors.slateDark3,
});
@@ -131,11 +123,10 @@ class PartialHighlight extends PureComponent<{
highlighted: ?string,
content: string,
}> {
static HighlightedText = styled.text({
static HighlightedText = styled('span')(({selected}) => ({
backgroundColor: '#ffff33',
color: props =>
props.selected ? `${colors.grapeDark3} !important` : 'auto',
});
color: selected ? `${colors.grapeDark3} !important` : 'auto',
}));
render() {
const {highlighted, content, selected} = this.props;
@@ -380,14 +371,14 @@ class ElementsRow extends PureComponent<ElementsRowProps, ElementsRowState> {
}
}
const ElementsContainer = FlexColumn.extends({
const ElementsContainer = styled(FlexColumn)({
backgroundColor: colors.white,
minHeight: '100%',
minWidth: '100%',
overflow: 'auto',
});
const ElementsBox = FlexColumn.extends({
const ElementsBox = styled(FlexColumn)({
alignItems: 'flex-start',
flex: 1,
overflow: 'auto',

View File

@@ -12,36 +12,31 @@ import textContent from '../../../utils/textContent.js';
import styled from '../../styled/index.js';
import {colors} from '../colors.js';
const FilterText = styled.view(
{
display: 'flex',
alignSelf: 'baseline',
userSelect: 'none',
cursor: 'pointer',
position: 'relative',
maxWidth: '100%',
'&:hover': {
color: colors.white,
},
'&:hover::after': {
content: '""',
position: 'absolute',
top: 3,
bottom: -2,
left: -6,
right: -6,
borderRadius: '999em',
backgroundColor: 'rgba(0, 0, 0, 0.3)',
},
'&:hover *': {
color: `${colors.white} !important`,
zIndex: 2,
},
const FilterText = styled('div')({
display: 'flex',
alignSelf: 'baseline',
userSelect: 'none',
cursor: 'pointer',
position: 'relative',
maxWidth: '100%',
'&:hover': {
color: colors.white,
},
{
ignoreAttributes: ['filterKey', 'addFilter'],
'&:hover::after': {
content: '""',
position: 'absolute',
top: 3,
bottom: -2,
left: -6,
right: -6,
borderRadius: '999em',
backgroundColor: 'rgba(0, 0, 0, 0.3)',
},
);
'&:hover *': {
color: `${colors.white} !important`,
zIndex: 2,
},
});
type Props = {
children: React.Node,

View File

@@ -18,7 +18,7 @@ import {
PureComponent,
} from 'sonar';
const Containter = FlexColumn.extends({
const Containter = styled(FlexColumn)({
fontSize: 17,
justifyContent: 'center',
marginLeft: 60,
@@ -30,12 +30,12 @@ const Containter = FlexColumn.extends({
minWidth: 450,
});
const TitleRow = FlexRow.extends({
const TitleRow = styled(FlexRow)({
alignItems: 'center',
marginBottom: 40,
});
const Icon = FlexBox.extends({
const Icon = styled(FlexBox)({
justifyContent: 'center',
alignItems: 'center',
backgroundColor: brandColors.Sonar,
@@ -45,13 +45,13 @@ const Icon = FlexBox.extends({
borderRadius: 6,
});
const Title = Text.extends({
const Title = styled(Text)({
fontSize: 30,
fontWeight: 300,
paddingLeft: 10,
});
const Button = View.extends({
const Button = styled(View)({
marginTop: 40,
marginBottom: 30,
borderRadius: 6,
@@ -64,7 +64,7 @@ const Button = View.extends({
alignSelf: 'flex-start',
});
const Screenshot = styled.customHTMLTag('img', {
const Screenshot = styled('img')({
alignSelf: 'center',
boxShadow: '0 5px 35px rgba(0,0,0,0.3)',
borderRadius: 5,

View File

@@ -13,58 +13,47 @@ import {findDOMNode} from 'react-dom';
import {colors} from '../colors.js';
import electron from 'electron';
const Token = Text.extends(
{
display: 'inline-flex',
alignItems: 'center',
backgroundColor: props =>
props.focused
? colors.macOSHighlightActive
: props.color || colors.macOSHighlight,
borderRadius: 4,
marginRight: 4,
padding: 4,
paddingLeft: 6,
height: 21,
color: props => (props.focused ? 'white' : 'inherit'),
'&:active': {
backgroundColor: colors.macOSHighlightActive,
color: colors.white,
},
'&:first-of-type': {
marginLeft: 3,
},
const Token = styled(Text)(props => ({
display: 'inline-flex',
alignItems: 'center',
backgroundColor: props.focused
? colors.macOSHighlightActive
: props.color || colors.macOSHighlight,
borderRadius: 4,
marginRight: 4,
padding: 4,
paddingLeft: 6,
height: 21,
color: props.focused ? 'white' : 'inherit',
'&:active': {
backgroundColor: colors.macOSHighlightActive,
color: colors.white,
},
{
ignoreAttributes: ['focused', 'color'],
'&:first-of-type': {
marginLeft: 3,
},
);
}));
const Key = Text.extends(
{
position: 'relative',
fontWeight: 500,
paddingRight: 12,
textTransform: 'capitalize',
lineHeight: '21px',
'&:after': {
content: props => (props.type === 'exclude' ? '"≠"' : '"="'),
paddingLeft: 5,
position: 'absolute',
top: -1,
right: 0,
fontSize: 14,
},
'&:active:after': {
backgroundColor: colors.macOSHighlightActive,
},
const Key = styled(Text)(props => ({
position: 'relative',
fontWeight: 500,
paddingRight: 12,
textTransform: 'capitalize',
lineHeight: '21px',
'&:after': {
content: props.type === 'exclude' ? '"≠"' : '"="',
paddingLeft: 5,
position: 'absolute',
top: -1,
right: 0,
fontSize: 14,
},
{
ignoreAttributes: ['type', 'focused'],
'&:active:after': {
backgroundColor: colors.macOSHighlightActive,
},
);
}));
const Value = Text.extends({
const Value = styled(Text)({
whiteSpace: 'nowrap',
maxWidth: 160,
overflow: 'hidden',
@@ -73,29 +62,24 @@ const Value = Text.extends({
paddingLeft: 3,
});
const Chevron = styled.view(
{
const Chevron = styled('div')(props => ({
border: 0,
paddingLeft: 3,
paddingRight: 1,
marginRight: 0,
fontSize: 16,
backgroundColor: 'transparent',
position: 'relative',
top: -2,
height: 'auto',
lineHeight: 'initial',
color: props.focused ? colors.white : 'inherit',
'&:hover, &:active, &:focus': {
color: 'inherit',
border: 0,
paddingLeft: 3,
paddingRight: 1,
marginRight: 0,
fontSize: 16,
backgroundColor: 'transparent',
position: 'relative',
top: -2,
height: 'auto',
lineHeight: 'initial',
color: props => (props.focused ? colors.white : 'inherit'),
'&:hover, &:active, &:focus': {
color: 'inherit',
border: 0,
backgroundColor: 'transparent',
},
},
{
ignoreAttributes: ['focused'],
},
);
}));
type Props = {|
filter: Filter,

View File

@@ -16,15 +16,16 @@ import FlexBox from '../FlexBox.js';
import Glyph from '../Glyph.js';
import FilterToken from './FilterToken.js';
import PropTypes from 'prop-types';
import styled from '../../styled/index.js';
const SEARCHABLE_STORAGE_KEY = (key: string) => `SEARCHABLE_STORAGE_KEY_${key}`;
const SearchBar = Toolbar.extends({
const SearchBar = styled(Toolbar)({
height: 42,
padding: 6,
});
export const SearchBox = FlexBox.extends({
export const SearchBox = styled(FlexBox)({
backgroundColor: colors.white,
borderRadius: '999em',
border: `1px solid ${colors.light15}`,
@@ -34,8 +35,8 @@ export const SearchBox = FlexBox.extends({
paddingLeft: 4,
});
export const SearchInput = Input.extends({
border: props => (props.focus ? '1px solid black' : 0),
export const SearchInput = styled(Input)(props => ({
border: props.focus ? '1px solid black' : 0,
padding: 0,
fontSize: '1em',
flexGrow: 1,
@@ -47,9 +48,9 @@ export const SearchInput = Input.extends({
color: colors.placeholder,
fontWeight: 300,
},
});
}));
const Clear = Text.extends({
const Clear = styled(Text)({
position: 'absolute',
right: 6,
top: '50%',
@@ -68,14 +69,14 @@ const Clear = Text.extends({
},
});
export const SearchIcon = Glyph.extends({
export const SearchIcon = styled(Glyph)({
marginRight: 3,
marginLeft: 3,
marginTop: -1,
minWidth: 16,
});
const Actions = FlexRow.extends({
const Actions = styled(FlexRow)({
marginLeft: 8,
flexShrink: 0,
});

View File

@@ -117,13 +117,11 @@ type ManagedTableState = {|
shouldScrollToBottom: boolean,
|};
/**
* Wrapper around `Table` that handles row state.
*
* If you require lower level access to the state then use [`<Table>`]()
* directly.
*/
class ManagedTable extends styled.StylableComponent<
const Container = styled(FlexColumn)({
flexGrow: 1,
});
class ManagedTable extends React.Component<
ManagedTableProps,
ManagedTableState,
> {
@@ -446,7 +444,7 @@ class ManagedTable extends styled.StylableComponent<
.filter(Boolean);
return (
<FlexColumn style={{flexGrow: 1}}>
<Container>
<TableHead
columnOrder={columnOrder}
onColumnOrder={this.onColumnOrder}
@@ -456,10 +454,7 @@ class ManagedTable extends styled.StylableComponent<
columnSizes={columnSizes}
onSort={this.onSort}
/>
<FlexColumn
style={{
flexGrow: 1,
}}>
<Container>
<AutoSizer>
{({width, height}) => (
<ContextMenu buildItems={this.buildContextMenuItems}>
@@ -500,8 +495,8 @@ class ManagedTable extends styled.StylableComponent<
</ContextMenu>
)}
</AutoSizer>
</FlexColumn>
</FlexColumn>
</Container>
</Container>
);
}
}

View File

@@ -27,11 +27,11 @@ const invariant = require('invariant');
type MenuTemplate = Array<Electron$MenuItemOptions>;
const TableHeaderArrow = styled.text({
const TableHeaderArrow = styled('span')({
float: 'right',
});
const TableHeaderColumnInteractive = Interactive.extends({
const TableHeaderColumnInteractive = styled(Interactive)({
display: 'inline-block',
overflow: 'hidden',
textOverflow: 'ellipsis',
@@ -39,11 +39,11 @@ const TableHeaderColumnInteractive = Interactive.extends({
width: '100%',
});
const TableHeaderColumnContainer = styled.view({
const TableHeaderColumnContainer = styled('div')({
padding: '0 8px',
});
const TableHeadContainer = FlexRow.extends({
const TableHeadContainer = styled(FlexRow)({
borderBottom: `1px solid ${colors.sectionHeaderBorder}`,
color: colors.light50,
flexShrink: 0,
@@ -56,33 +56,28 @@ const TableHeadContainer = FlexRow.extends({
zIndex: 2,
});
const TableHeadColumnContainer = styled.view(
{
position: 'relative',
backgroundColor: colors.white,
flexShrink: props => (props.width === 'flex' ? 1 : 0),
height: 23,
lineHeight: '23px',
fontSize: '0.85em',
fontWeight: 500,
width: props => (props.width === 'flex' ? '100%' : props.width),
'&::after': {
position: 'absolute',
content: '""',
right: 0,
top: 5,
height: 13,
width: 1,
background: colors.light15,
},
'&:last-child::after': {
display: 'none',
},
const TableHeadColumnContainer = styled('div')(props => ({
position: 'relative',
backgroundColor: colors.white,
flexShrink: props.width === 'flex' ? 1 : 0,
height: 23,
lineHeight: '23px',
fontSize: '0.85em',
fontWeight: 500,
width: props.width === 'flex' ? '100%' : props.width,
'&::after': {
position: 'absolute',
content: '""',
right: 0,
top: 5,
height: 13,
width: 1,
background: colors.light15,
},
{
ignoreAttributes: ['width'],
'&:last-child::after': {
display: 'none',
},
);
}));
const RIGHT_RESIZABLE = {right: true};

View File

@@ -20,81 +20,60 @@ import {colors} from '../colors.js';
import {normaliseColumnWidth} from './utils.js';
import {DEFAULT_ROW_HEIGHT} from './types';
const TableBodyRowContainer = FlexRow.extends(
{
backgroundColor: props => {
if (props.highlighted) {
if (props.highlightedBackgroundColor) {
return props.highlightedBackgroundColor;
} else {
return colors.macOSTitleBarIconSelected;
}
} else {
if (props.backgroundColor) {
return props.backgroundColor;
} else if (props.even && props.zebra) {
return colors.light02;
} else {
return 'transparent';
}
}
},
boxShadow: props => (props.zebra ? 'none' : 'inset 0 -1px #E9EBEE'),
color: props =>
props.highlighted ? colors.white : props.color || 'inherit',
'& *': {
color: props => (props.highlighted ? `${colors.white} !important` : null),
},
'& img': {
backgroundColor: props =>
props.highlighted ? `${colors.white} !important` : 'none',
},
height: props => (props.multiline ? 'auto' : props.rowLineHeight),
lineHeight: props =>
`${String(props.rowLineHeight || DEFAULT_ROW_HEIGHT)}px`,
fontWeight: props => props.fontWeight || 'inherit',
overflow: 'hidden',
width: '100%',
userSelect: 'none',
flexShrink: 0,
'&:hover': {
backgroundColor: props =>
!props.highlighted && props.highlightOnHover ? colors.light02 : 'none',
},
},
{
ignoreAttributes: [
'highlightedBackgroundColor',
'highlightOnHover',
'backgroundColor',
'rowLineHeight',
'highlighted',
'multiline',
'hasHover',
'zebra',
'even',
],
},
);
const backgroundColor = props => {
if (props.highlighted) {
if (props.highlightedBackgroundColor) {
return props.highlightedBackgroundColor;
} else {
return colors.macOSTitleBarIconSelected;
}
} else {
if (props.backgroundColor) {
return props.backgroundColor;
} else if (props.even && props.zebra) {
return colors.light02;
} else {
return 'transparent';
}
}
};
const TableBodyColumnContainer = styled.view(
{
display: 'flex',
flexShrink: props => (props.width === 'flex' ? 1 : 0),
overflow: 'hidden',
padding: '0 8px',
userSelect: 'none',
textOverflow: 'ellipsis',
verticalAlign: 'top',
whiteSpace: props => (props.multiline ? 'normal' : 'nowrap'),
wordWrap: props => (props.multiline ? 'break-word' : 'normal'),
width: props => (props.width === 'flex' ? '100%' : props.width),
maxWidth: '100%',
const TableBodyRowContainer = styled(FlexRow)(props => ({
backgroundColor: backgroundColor(props),
boxShadow: props.zebra ? 'none' : 'inset 0 -1px #E9EBEE',
color: props.highlighted ? colors.white : props.color || 'inherit',
'& *': {
color: props.highlighted ? `${colors.white} !important` : null,
},
{
ignoreAttributes: ['multiline', 'width'],
'& img': {
backgroundColor: props.highlighted ? `${colors.white} !important` : 'none',
},
);
height: props.multiline ? 'auto' : props.rowLineHeight,
lineHeight: `${String(props.rowLineHeight || DEFAULT_ROW_HEIGHT)}px`,
fontWeight: props.fontWeight || 'inherit',
overflow: 'hidden',
width: '100%',
userSelect: 'none',
flexShrink: 0,
'&:hover': {
backgroundColor:
!props.highlighted && props.highlightOnHover ? colors.light02 : 'none',
},
}));
const TableBodyColumnContainer = styled('div')(props => ({
display: 'flex',
flexShrink: props.width === 'flex' ? 1 : 0,
overflow: 'hidden',
padding: '0 8px',
userSelect: 'none',
textOverflow: 'ellipsis',
verticalAlign: 'top',
whiteSpace: props.multiline ? 'normal' : 'nowrap',
wordWrap: props.multiline ? 'break-word' : 'normal',
width: props.width === 'flex' ? '100%' : props.width,
maxWidth: '100%',
}));
type Props = {
columnSizes: TableColumnSizes,

View File

@@ -4,9 +4,7 @@
* LICENSE file in the root directory of this source tree.
* @format
*/
export type {StyledComponent} from './styled/index.js';
//
export {default as Button} from './components/Button.js';
export {default as ToggleButton} from './components/ToggleSwitch.js';
export {
@@ -26,10 +24,6 @@ export {default as LoadingIndicator} from './components/LoadingIndicator.js';
//
export {default as Popover} from './components/Popover.js';
//
export {default as ClickableList} from './components/ClickableList.js';
export {default as ClickableListItem} from './components/ClickableListItem.js';
//
export type {
TableColumns,

View File

@@ -1,145 +0,0 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import type {Tracker} from '../index.js';
import {GarbageCollector} from '../gc.js';
import {StyleSheet} from '../sheet.js';
function createGC(): {|
gc: GarbageCollector,
tracker: Tracker,
|} {
const sheet = new StyleSheet();
const tracker = new Map();
const rulesToClass = new WeakMap();
const gc = new GarbageCollector(sheet, tracker, rulesToClass);
return {gc, tracker};
}
test('register classes to be garbage collected when no references exist', () => {
const {gc} = createGC();
gc.registerClassUse('foo');
expect(gc.getCollectionQueue()).toEqual([]);
gc.deregisterClassUse('foo');
expect(gc.getCollectionQueue()).toEqual(['foo']);
});
test('cancel garbage collection for classes used before actual collection happens', () => {
const {gc} = createGC();
gc.registerClassUse('foo');
expect(gc.getCollectionQueue()).toEqual([]);
gc.deregisterClassUse('foo');
expect(gc.getCollectionQueue()).toEqual(['foo']);
gc.registerClassUse('foo');
expect(gc.getCollectionQueue()).toEqual([]);
});
test('garbage collector removes unreferenced classes', () => {
const {gc, tracker} = createGC();
tracker.set('foo', {
displayName: 'foo',
namespace: '',
selector: '',
style: {},
rules: {},
});
gc.registerClassUse('foo');
expect(gc.getCollectionQueue()).toEqual([]);
gc.deregisterClassUse('foo');
expect(gc.getCollectionQueue()).toEqual(['foo']);
expect(gc.hasQueuedCollection()).toBe(true);
expect(tracker.has('foo')).toBe(true);
gc.collectGarbage();
expect(gc.hasQueuedCollection()).toBe(false);
expect(gc.getCollectionQueue()).toEqual([]);
expect(tracker.has('foo')).toBe(false);
});
test('properly tracks reference counts', () => {
const {gc} = createGC();
gc.registerClassUse('foo');
gc.registerClassUse('foo');
gc.registerClassUse('bar');
expect(gc.getReferenceCount('foo')).toBe(2);
expect(gc.getReferenceCount('bar')).toBe(1);
gc.deregisterClassUse('bar');
expect(gc.getReferenceCount('bar')).toBe(0);
gc.deregisterClassUse('foo');
expect(gc.getReferenceCount('foo')).toBe(1);
gc.deregisterClassUse('foo');
expect(gc.getReferenceCount('foo')).toBe(0);
});
test("gracefully handle deregistering classes we don't have a count for", () => {
const {gc} = createGC();
gc.deregisterClassUse('not-tracking');
});
test('only halt garbage collection if there is nothing left in the queue', () => {
const {gc} = createGC();
gc.registerClassUse('foo');
expect(gc.hasQueuedCollection()).toBe(false);
gc.deregisterClassUse('foo');
expect(gc.hasQueuedCollection()).toBe(true);
gc.registerClassUse('bar');
expect(gc.hasQueuedCollection()).toBe(true);
gc.deregisterClassUse('bar');
expect(gc.hasQueuedCollection()).toBe(true);
gc.registerClassUse('bar');
expect(gc.hasQueuedCollection()).toBe(true);
gc.registerClassUse('foo');
expect(gc.hasQueuedCollection()).toBe(false);
});
test('ensure garbage collection happens', () => {
const {gc} = createGC();
gc.registerClassUse('foo');
gc.deregisterClassUse('foo');
expect(gc.hasQueuedCollection()).toBe(true);
expect(gc.getCollectionQueue()).toEqual(['foo']);
jest.runAllTimers();
expect(gc.hasQueuedCollection()).toBe(false);
expect(gc.getCollectionQueue()).toEqual([]);
});
test('flush', () => {
const {gc} = createGC();
gc.registerClassUse('bar');
gc.deregisterClassUse('bar');
expect(gc.getCollectionQueue()).toEqual(['bar']);
expect(gc.getReferenceCount('bar')).toBe(0);
gc.registerClassUse('foo');
expect(gc.getReferenceCount('foo')).toBe(1);
gc.flush();
expect(gc.getCollectionQueue()).toEqual([]);
expect(gc.getReferenceCount('foo')).toBe(0);
});

View File

@@ -1,14 +0,0 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import hash from '../hash.js';
test('hash', () => {
expect(hash('f')).toBe('1xwd1rk');
expect(hash('foobar')).toBe('slolri');
expect(hash('foobar2')).toBe('34u6r4');
});

View File

@@ -1,387 +0,0 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import styled, {buildKeyframes, flush, gc, tracker} from '../index.js';
const ReactTestRenderer = require('react-test-renderer');
const invariant = require('invariant');
const React = require('react'); // eslint-disable-line
const BasicComponent = styled.view({
color: 'red',
});
const DynamicComponent = styled.view({
color: props => props.color,
});
test('can create a basic component without any errors', () => {
let component;
try {
component = ReactTestRenderer.create(<BasicComponent />);
component.toJSON();
component.unmount();
gc.flush();
} finally {
if (component) {
component.unmount();
}
gc.flush();
}
});
test('can create a basic component and garbage collect', () => {
let component;
try {
component = ReactTestRenderer.create(<BasicComponent />);
const tree = component.toJSON();
expect(tree.type).toBe('div');
const className = tree.props.className;
expect(gc.hasQueuedCollection()).toBe(false);
expect(gc.getReferenceCount(className)).toBe(1);
component.unmount();
expect(gc.getReferenceCount(className)).toBe(0);
expect(gc.hasQueuedCollection()).toBe(true);
} finally {
if (component) {
component.unmount();
}
gc.flush();
}
});
test('remove outdated classes when updating component', () => {
let component;
try {
component = ReactTestRenderer.create(<DynamicComponent color="red" />);
const tree = component.toJSON();
const className = tree.props.className;
expect(gc.hasQueuedCollection()).toBe(false);
expect(gc.getReferenceCount(className)).toBe(1);
// updating with the same props should generate the same style and not trigger a collection
component.update(<DynamicComponent color="red" />);
expect(gc.hasQueuedCollection()).toBe(false);
expect(gc.getReferenceCount(className)).toBe(1);
// change style
component.update(<DynamicComponent color="blue" />);
expect(gc.hasQueuedCollection()).toBe(true);
expect(gc.getReferenceCount(className)).toBe(0);
} finally {
if (component) {
component.unmount();
}
gc.flush();
}
});
test('extra class names should be preserved', () => {
let component;
try {
component = ReactTestRenderer.create(<BasicComponent className="foo" />);
const tree = component.toJSON();
expect(tree.props.className.split(' ').includes('foo')).toBe(true);
} finally {
if (component) {
component.unmount();
}
gc.flush();
}
});
test('should inherit component when passed as first arg to styled', () => {
let component;
try {
const InheritComponent = BasicComponent.extends({
backgroundColor: 'black',
});
component = ReactTestRenderer.create(<InheritComponent />);
const tree = component.toJSON();
const rules = tracker.get(tree.props.className);
invariant(rules, 'expected rules');
expect(rules.style).toEqual({
'background-color': 'black',
color: 'red',
});
} finally {
if (component) {
component.unmount();
}
gc.flush();
}
});
test('when passed class name of another styled component its rules should be inherited', () => {
let component;
try {
class BaseComponent extends styled.StylableComponent<{
className: string,
}> {
render() {
return <BasicComponent className={this.props.className} />;
}
}
const InheritComponent = BaseComponent.extends({
backgroundColor: 'black',
});
component = ReactTestRenderer.create(<InheritComponent />);
const tree = component.toJSON();
const rules = tracker.get(tree.props.className);
invariant(rules, 'expected rules');
expect(rules.style).toEqual({
'background-color': 'black',
color: 'red',
});
} finally {
if (component) {
component.unmount();
}
gc.flush();
}
});
test('supports pseudo selectors', () => {
let component;
try {
const Component = styled.view({
'&:hover': {
color: 'red',
},
});
component = ReactTestRenderer.create(<Component />);
const tree = component.toJSON();
const rules = tracker.get(tree.props.className);
invariant(rules, 'expected rules');
expect(rules.style).toEqual({
color: 'red',
});
} finally {
if (component) {
component.unmount();
}
gc.flush();
}
});
test('supports multiple pseudo selectors', () => {
let component;
try {
const Component = styled.view({
'&:active': {
color: 'blue',
},
'&:hover': {
color: 'red',
},
});
component = ReactTestRenderer.create(<Component />);
const tree = component.toJSON();
const classes = tree.props.className.split(' ');
expect(classes.length).toBe(2);
const hoverRules = tracker.get(classes[1]);
invariant(hoverRules, 'expected hoverRules');
expect(hoverRules.style).toEqual({
color: 'red',
});
expect(hoverRules.namespace).toBe('&:hover');
expect(hoverRules.selector.endsWith(':hover')).toBe(true);
const activeRules = tracker.get(classes[0]);
invariant(activeRules, 'expected activeRules');
expect(activeRules.style).toEqual({
color: 'blue',
});
expect(activeRules.namespace).toBe('&:active');
expect(activeRules.selector.endsWith(':active')).toBe(true);
} finally {
if (component) {
component.unmount();
}
gc.flush();
}
});
test('supports child selectors', () => {
let component;
try {
const Component = styled.view({
'> li': {
color: 'red',
},
});
component = ReactTestRenderer.create(<Component />);
const tree = component.toJSON();
const classes = tree.props.className.split(' ');
expect(classes.length).toBe(1);
const rules = tracker.get(classes[0]);
invariant(rules, 'expected rules');
expect(rules.style).toEqual({
color: 'red',
});
expect(rules.namespace).toBe('> li');
expect(rules.selector.endsWith(' > li')).toBe(true);
} finally {
if (component) {
component.unmount();
}
gc.flush();
}
});
test('flush', () => {
flush();
});
test('innerRef works on styled components', () => {
let component;
try {
const Component = styled.view({});
let called = false;
const innerRef = ref => {
called = true;
};
ReactTestRenderer.create(<Component innerRef={innerRef} />);
expect(called).toBe(true);
} finally {
if (component) {
component.unmount();
}
gc.flush();
}
});
test('ignoreAttributes', () => {
let component;
try {
const Component = styled.view(
{
color: props => props.color,
},
{
ignoreAttributes: ['color'],
},
);
component = ReactTestRenderer.create(<Component color="red" />);
const tree = component.toJSON();
expect(tree.props.color).toBe(undefined);
const rules = tracker.get(tree.props.className);
invariant(rules, 'expected rules');
expect(rules.style).toEqual({
color: 'red',
});
} finally {
if (component) {
component.unmount();
}
gc.flush();
}
});
test('buildKeyframes', () => {
const css = buildKeyframes({
'0%': {
opacity: 0,
},
'50%': {
height: 50,
opacity: 0.8,
},
'100%': {
opacity: 1,
},
});
expect(css).toBe(
[
' 0% {',
' opacity: 0;',
' }',
' 50% {',
' height: 50px;',
' opacity: 0.8;',
' }',
' 100% {',
' opacity: 1;',
' }',
].join('\n'),
);
});
test('keyframes', () => {
const className = styled.keyframes({
'0%': {
opacity: 0,
},
'50%': {
opacity: 0.8,
},
'100%': {
opacity: 1,
},
});
expect(typeof className).toBe('string');
});
test('buildKeyframes only accepts string property values', () => {
expect(() => {
buildKeyframes({
// $FlowFixMe: ignore
'0%': {
fn: () => {},
},
});
}).toThrow('Keyframe objects must only have strings values');
});
test('buildKeyframes only accepts object specs', () => {
expect(() => {
buildKeyframes({
// $FlowFixMe: ignore
'0%': () => {
return '';
},
});
}).toThrow('Keyframe spec must only have objects');
});

View File

@@ -1,76 +0,0 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import {buildRules, normaliseRules} from '../rules.js';
describe('normaliseRules', () => {
test('ensure top level values are expanded', () => {
const normalisedRules = normaliseRules({height: '4px'});
expect(normalisedRules['&'].height).toBe('4px');
});
test('ensure keys are dashed', () => {
const normalisedRules = normaliseRules({
// $FlowFixMe: ignore
'&:hover': {
lineHeight: '4px',
WebkitAppRegion: 'drag',
},
});
const hoverRules = normalisedRules['&:hover'];
expect(Object.keys(hoverRules).length).toBe(2);
expect(hoverRules['line-height']).toBe('4px');
expect(hoverRules['-webkit-app-region']).toBe('drag');
});
test('exclude empty objects', () => {
const normalisedRules = normaliseRules({
'&:hover': {},
});
expect(normalisedRules['&:hover']).toBe(undefined);
});
});
describe('buildRules', () => {
test('ensure null values are left out', () => {
const builtRules = buildRules({height: (null: any)}, {}, {});
expect('height' in builtRules).toBe(false);
const builtRules2 = buildRules(
{
height() {
return (null: any);
},
},
{},
{},
);
expect('height' in builtRules2).toBe(false);
});
test('ensure numbers are appended with px', () => {
expect(buildRules({height: 40}, {}, {}).height).toBe('40px');
});
test("ensure unitless numbers aren't appended with px", () => {
expect(buildRules({'z-index': 4}, {}, {})['z-index']).toBe('4');
});
test('ensure functions are called with props', () => {
const thisProps = {};
expect(
buildRules(
{
border: props => (props === thisProps ? 'foo' : 'bar'),
},
thisProps,
{},
).border,
).toBe('foo');
});
});

View File

@@ -1,74 +0,0 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import {StyleSheet} from '../sheet.js';
describe('flush', () => {
test('should remove all rules', () => {
const sheet = new StyleSheet();
expect(sheet.getRuleCount()).toBe(0);
sheet.insert('foo', 'div {color: red;}');
expect(sheet.getRuleCount()).toBe(1);
sheet.flush();
expect(sheet.getRuleCount()).toBe(0);
});
});
describe('inject', () => {
test("throw's an error when already injected", () => {
const sheet = new StyleSheet();
expect(() => {
sheet.inject();
sheet.inject();
}).toThrow('already injected stylesheet!');
});
});
describe('insert', () => {
test('non-speedy', () => {
const sheet = new StyleSheet();
expect(sheet.getRuleCount()).toBe(0);
sheet.insert('foo', 'div {color: red;}');
expect(sheet.getRuleCount()).toBe(1);
});
test('speedy', () => {
const sheet = new StyleSheet(true);
expect(sheet.getRuleCount()).toBe(0);
sheet.insert('foo', 'div {color: red;}');
expect(sheet.getRuleCount()).toBe(1);
});
});
describe('delete', () => {
test('non-speedy', () => {
const sheet = new StyleSheet();
expect(sheet.getRuleCount()).toBe(0);
sheet.insert('foo', 'div {color: red;}');
expect(sheet.getRuleCount()).toBe(1);
sheet.delete('foo');
expect(sheet.getRuleCount()).toBe(0);
});
test('speedy', () => {
const sheet = new StyleSheet(true);
expect(sheet.getRuleCount()).toBe(0);
sheet.insert('foo', 'div {color: red;}');
expect(sheet.getRuleCount()).toBe(1);
sheet.delete('foo');
expect(sheet.getRuleCount()).toBe(0);
});
});

View File

@@ -1,114 +0,0 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import type {Tracker, RulesToClass} from './index.js';
import type {StyleSheet} from './sheet.js';
const invariant = require('invariant');
export class GarbageCollector {
constructor(sheet: StyleSheet, tracker: Tracker, rulesToClass: RulesToClass) {
this.sheet = sheet;
this.tracker = tracker;
// used to keep track of what classes are actively in use
this.usedClasses = new Map();
// classes to be removed, we put this in a queue and perform it in bulk rather than straight away
// since by the time the next tick happens this style could have been reinserted
this.classRemovalQueue = new Set();
this.rulesToClass = rulesToClass;
}
tracker: Tracker;
sheet: StyleSheet;
usedClasses: Map<string, number>;
garbageTimer: ?TimeoutID;
classRemovalQueue: Set<string>;
rulesToClass: RulesToClass;
hasQueuedCollection(): boolean {
return Boolean(this.garbageTimer);
}
getReferenceCount(key: string): number {
return this.usedClasses.get(key) || 0;
}
// component has been mounted so make sure it's being depended on
registerClassUse(name: string) {
const count = this.usedClasses.get(name) || 0;
this.usedClasses.set(name, count + 1);
if (this.classRemovalQueue.has(name)) {
this.classRemovalQueue.delete(name);
if (this.classRemovalQueue.size === 0) {
this.haltGarbage();
}
}
}
// component has been unmounted so remove its dependencies
deregisterClassUse(name: string) {
let count = this.usedClasses.get(name);
if (count == null) {
return;
}
count--;
this.usedClasses.set(name, count);
if (count === 0) {
this.classRemovalQueue.add(name);
this.scheduleGarbage();
}
}
scheduleGarbage() {
if (this.garbageTimer != null) {
return;
}
this.garbageTimer = setTimeout(() => {
this.collectGarbage();
}, 2000);
}
haltGarbage() {
if (this.garbageTimer) {
clearTimeout(this.garbageTimer);
this.garbageTimer = null;
}
}
getCollectionQueue(): Array<string> {
return Array.from(this.classRemovalQueue);
}
collectGarbage() {
this.haltGarbage();
for (const name of this.classRemovalQueue) {
const trackerInfo = this.tracker.get(name);
invariant(trackerInfo != null, 'trying to remove unknown class');
const {rules} = trackerInfo;
this.rulesToClass.delete(rules);
this.sheet.delete(name);
this.tracker.delete(name);
this.usedClasses.delete(name);
}
this.classRemovalQueue.clear();
}
flush() {
this.haltGarbage();
this.classRemovalQueue.clear();
this.usedClasses.clear();
}
}

View File

@@ -1,74 +0,0 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
export default function hash(str: string): string {
const m = 0x5bd1e995;
const r = 24;
let h = str.length;
let length = str.length;
let currentIndex = 0;
while (length >= 4) {
let k = UInt32(str, currentIndex);
k = Umul32(k, m);
k ^= k >>> r;
k = Umul32(k, m);
h = Umul32(h, m);
h ^= k;
currentIndex += 4;
length -= 4;
}
switch (length) {
case 3:
h ^= UInt16(str, currentIndex);
h ^= str.charCodeAt(currentIndex + 2) << 16;
h = Umul32(h, m);
break;
case 2:
h ^= UInt16(str, currentIndex);
h = Umul32(h, m);
break;
case 1:
h ^= str.charCodeAt(currentIndex);
h = Umul32(h, m);
break;
}
h ^= h >>> 13;
h = Umul32(h, m);
h ^= h >>> 15;
return (h >>> 0).toString(36);
}
function UInt32(str: string, pos: number): number {
return (
str.charCodeAt(pos++) +
(str.charCodeAt(pos++) << 8) +
(str.charCodeAt(pos++) << 16) +
(str.charCodeAt(pos) << 24)
);
}
function UInt16(str: string, pos: number): number {
return str.charCodeAt(pos++) + (str.charCodeAt(pos++) << 8);
}
function Umul32(n: number, m: number): number {
n |= 0;
m |= 0;
const nlo = n & 0xffff;
const nhi = n >>> 16;
const res = (nlo * m + (((nhi * m) & 0xffff) << 16)) | 0;
return res;
}

View File

@@ -5,436 +5,5 @@
* @format
*/
import type {BaseRules, KeyframeRules, RawRules} from './rules.js';
import {buildKeyframeRules, buildRules, normaliseRules} from './rules.js';
import assignDeep from '../../utils/assignDeep.js';
import * as performance from '../../utils/performance.js';
import {GarbageCollector} from './gc.js';
import {StyleSheet} from './sheet.js';
import hash from './hash.js';
const React = require('react');
export type Tracker = Map<
string,
{
displayName: ?string,
namespace: string,
rules: BaseRules,
selector: string,
style: Object,
},
>;
export type RulesToClass = WeakMap<BaseRules, string>;
// map of inserted classes and metadata about them
export const tracker: Tracker = new Map();
// map of rules to their class
const rulesToClass: RulesToClass = new WeakMap();
export const sheet = new StyleSheet(process.env.NODE_ENV === 'production');
export const gc = new GarbageCollector(sheet, tracker, rulesToClass);
function addRules(
displayName: string,
rules: BaseRules,
namespace,
props: Object,
context: Object,
) {
// if these rules have been cached to a className then retrieve it
const cachedClass = rulesToClass.get(rules);
if (cachedClass != null) {
return cachedClass;
}
//
const declarations = [];
const style = buildRules(rules, props, context);
// generate css declarations based on the style object
for (const key in style) {
const val = style[key];
declarations.push(` ${key}: ${val};`);
}
const css = declarations.join('\n');
// build the class name with the display name of the styled component and a unique id based on the css and namespace
const className = displayName + '__' + hash(namespace + css);
// this is the first time we've found this className
if (!tracker.has(className)) {
// build up the correct selector, explode on commas to allow multiple selectors
const selector = namespace
.split(', ')
.map(part => {
if (part[0] === '&') {
return '.' + className + part.slice(1);
} else {
return '.' + className + ' ' + part;
}
})
.join(', ');
// insert the new style text
tracker.set(className, {displayName, namespace, rules, selector, style});
sheet.insert(className, `${selector} {\n${css}\n}`);
// if there's no dynamic rules then cache this
if (hasDynamicRules(rules) === false) {
rulesToClass.set(rules, className);
}
}
return className;
}
// remove all styhles
export function flush() {
gc.flush();
tracker.clear();
sheet.flush();
}
export type TagName = string | Function;
type StyledComponentState = {|
extraClassNames: Array<string>,
classNames: Array<string>,
lastBuiltRules: ?Object,
lastBuiltRulesIsDynamic: boolean,
|};
export class StylableComponent<
Props = void,
State = void,
> extends React.Component<Props, State> {
static extends(
rules: RawRules,
opts?: StyledComponentOpts,
): StyledComponent<any> {
return createStyledComponent(this, rules, opts);
}
}
class StylablePureComponent<
Props = void,
State = void,
> extends React.PureComponent<Props, State> {
static extends(
rules: RawRules,
opts?: StyledComponentOpts,
): StyledComponent<any> {
return createStyledComponent(this, rules, opts);
}
}
class StyledComponentBase<Props> extends React.PureComponent<
Props,
StyledComponentState,
> {
constructor(props: Props, context: Object): void {
super(props, context);
this.state = {
classNames: [],
extraClassNames: [],
lastBuiltRulesIsDynamic: false,
lastBuiltRules: null,
};
}
static defaultProps: ?$Shape<Props>;
static STYLED_CONFIG: {|
tagName: TagName,
ignoreAttributes: ?Array<string>,
builtRules: any,
|};
static extends(
rules: RawRules,
opts?: StyledComponentOpts,
): StyledComponent<any> {
return createStyledComponent(this, rules, opts);
}
componentWillMount(): void {
this.generateClassnames(this.props, null);
}
componentWillReceiveProps(nextProps: Props): void {
this.generateClassnames(nextProps, this.props);
}
componentWillUnmount(): void {
for (const name of this.state.classNames) {
gc.deregisterClassUse(name);
}
}
generateClassnames(props: Props, prevProps: ?Props): void {
throw new Error('unimplemented');
}
}
function hasDynamicRules(rules: Object): boolean {
for (const key in rules) {
const val = rules[key];
if (typeof val === 'function') {
return true;
} else if (typeof val === 'object' && hasDynamicRules(val)) {
return true;
}
}
return false;
}
function hasEquivProps(props: Object, nextProps: Object): boolean {
// check if the props are equivalent
for (const key in props) {
// ignore `children` since we do that check later
if (key === 'children') {
continue;
}
// check strict equality of prop value
if (nextProps[key] !== props[key]) {
return false;
}
}
// check if nextProps has any values that props doesn't
for (const key in nextProps) {
if (!(key in props)) {
return false;
}
}
// check if the boolean equality of children is equivalent
if (Boolean(props.children) !== Boolean(nextProps.children)) {
return false;
}
return true;
}
export type StyledComponent<Props> = Class<StyledComponentBase<Props>>;
type StyledComponentOpts = {
displayName?: string,
contextTypes?: Object,
ignoreAttributes?: Array<string>,
};
function createStyledComponent(
tagName: TagName,
rules: RawRules,
opts?: StyledComponentOpts = {},
): StyledComponent<any> {
let {contextTypes = {}, ignoreAttributes} = opts;
// build up rules
let builtRules = normaliseRules(rules);
// if inheriting from another styled component then take all of it's properties
if (typeof tagName === 'function' && tagName.STYLED_CONFIG) {
// inherit context types
if (tagName.contextTypes) {
contextTypes = {...contextTypes, ...tagName.contextTypes};
}
const parentConfig = tagName.STYLED_CONFIG;
// inherit tagname
tagName = parentConfig.tagName;
// inherit ignoreAttributes
if (parentConfig.ignoreAttributes) {
if (ignoreAttributes) {
ignoreAttributes = ignoreAttributes.concat(
parentConfig.ignoreAttributes,
);
} else {
ignoreAttributes = parentConfig.ignoreAttributes;
}
}
// inherit rules
builtRules = assignDeep({}, parentConfig.builtRules, builtRules);
}
const displayName: string =
opts.displayName == null ? 'StyledComponent' : opts.displayName;
const isDOM = typeof tagName === 'string';
class Constructor<Props: Object> extends StyledComponentBase<Props> {
generateClassnames(props: Props, prevProps: ?Props) {
// if this is a secondary render then check if the props are essentially equivalent
// NOTE: hasEquivProps is not a standard shallow equality test
if (prevProps != null && hasEquivProps(props, prevProps)) {
return;
}
const debugId = performance.mark();
const extraClassNames = [];
let myBuiltRules = builtRules;
// if passed any classes from another styled component, ignore that class and merge in their
// resolved styles
if (props.className) {
const propClassNames = props.className.trim().split(/[\s]+/g);
for (const className of propClassNames) {
const classInfo = tracker.get(className);
if (classInfo) {
const {namespace, style} = classInfo;
myBuiltRules = assignDeep({}, myBuiltRules, {[namespace]: style});
} else {
extraClassNames.push(className);
}
}
}
// if we had the exact same rules as last time and they weren't dynamic then we can bail out here
if (
myBuiltRules !== this.state.lastBuiltRules ||
this.state.lastBuiltRulesIsDynamic !== false
) {
const prevClasses = this.state.classNames;
const classNames = [];
// add rules
for (const namespace in myBuiltRules) {
const className = addRules(
displayName,
myBuiltRules[namespace],
namespace,
props,
this.context,
);
classNames.push(className);
// if this is the first mount render or we didn't previously have this class then add it as new
if (prevProps == null || !prevClasses.includes(className)) {
gc.registerClassUse(className);
}
}
// check what classNames have been removed if this is a secondary render
if (prevProps != null) {
for (const className of prevClasses) {
// if this previous class isn't in the current classes then deregister it
if (!classNames.includes(className)) {
gc.deregisterClassUse(className);
}
}
}
this.setState({
classNames,
lastBuiltRules: myBuiltRules,
lastBuiltRulesIsDynamic: hasDynamicRules(myBuiltRules),
extraClassNames,
});
}
performance.measure(
debugId,
`🚀 ${this.constructor.name} [style calculate]`,
);
}
render() {
const {children, innerRef, ...props} = this.props;
if (ignoreAttributes) {
for (const key of ignoreAttributes) {
delete props[key];
}
}
// build class names
const className = this.state.classNames
.concat(this.state.extraClassNames)
.join(' ');
if (props.is) {
props.class = className;
} else {
props.className = className;
}
//
if (innerRef) {
if (isDOM) {
// dom ref
props.ref = innerRef;
} else {
// probably another styled component so pass it down
props.innerRef = innerRef;
}
}
return React.createElement(tagName, props, children);
}
}
Constructor.STYLED_CONFIG = {
builtRules,
ignoreAttributes,
tagName,
};
Constructor.contextTypes = {
...contextTypes,
};
Object.defineProperty(Constructor, 'name', {
value: displayName,
});
return Constructor;
}
export function buildKeyframes(spec: KeyframeRules) {
let css = [];
const builtRules = buildKeyframeRules(spec);
for (const key in builtRules) {
const declarations = [];
const rules = builtRules[key];
for (const key in rules) {
declarations.push(` ${key}: ${String(rules[key])};`);
}
css.push(` ${key} {`);
css = css.concat(declarations);
css.push(' }');
}
css = css.join('\n');
return css;
}
function createKeyframes(spec: KeyframeRules): string {
const body = buildKeyframes(spec);
const className = `animation-${hash(body)}`;
const css = `@keyframes ${className} {\n${body}\n}`;
sheet.insert(className, css);
return className;
}
type StyledComponentFactory = (
rules: RawRules,
opts?: StyledComponentOpts,
) => StyledComponent<any>;
function createStyledComponentFactory(tagName: string): StyledComponentFactory {
return (rules: RawRules, opts?: StyledComponentOpts) => {
return createStyledComponent(tagName, rules, opts);
};
}
export default {
image: createStyledComponentFactory('img'),
view: createStyledComponentFactory('div'),
text: createStyledComponentFactory('span'),
textInput: createStyledComponentFactory('input'),
customHTMLTag: createStyledComponent,
keyframes: createKeyframes,
StylableComponent,
StylablePureComponent,
};
import styled from 'react-emotion';
export default styled;

View File

@@ -1,180 +0,0 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
import type {CSSPropertySet, CSSPropertyValue} from './types.js';
const dashify = require('dashify');
export type NormalisedRules = {
[namespace: string]: BaseRules,
};
export type BaseRules = {
[key: string]: CSSPropertyValue<string | number>,
};
export type PlainRules = {
[key: string]: string,
};
export type NormalisedKeyframeRules = {
[key: string]: PlainRules,
};
export type KeyframeRules = {
[key: string]: CSSPropertySet,
};
export type RawRules = {
...CSSPropertySet,
[key: string]: CSSPropertySet,
};
const unitlessNumberProperties = new Set([
'animation-iteration-count',
'border-image-outset',
'border-image-slice',
'border-image-width',
'column-count',
'flex',
'flex-grow',
'flex-positive',
'flex-shrink',
'flex-order',
'grid-row',
'grid-column',
'font-weight',
'line-clamp',
'line-height',
'opacity',
'order',
'orphans',
'tab-size',
'widows',
'z-index',
'zoom',
'fill-opacity',
'flood-opacity',
'stop-opacity',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
]);
// put top level styles into an '&' object
function expandRules(rules: RawRules): NormalisedRules {
const expandedRules = {};
const rootRules = {};
for (const key in rules) {
const val = rules[key];
if (typeof val === 'object') {
expandedRules[key] = val;
} else {
rootRules[key] = val;
}
}
if (Object.keys(rootRules).length) {
expandedRules['&'] = rootRules;
}
return expandedRules;
}
function shouldAppendPixel(key: string, val: mixed): boolean {
return (
typeof val === 'number' && !unitlessNumberProperties.has(key) && !isNaN(val)
);
}
export function normaliseRules(rules: RawRules): NormalisedRules {
const expandedRules = expandRules(rules);
const builtRules = {};
for (const key in expandedRules) {
const rules = expandedRules[key];
const myRules = {};
for (const key in rules) {
const val = rules[key];
let dashedKey = dashify(key);
if (/[A-Z]/.test(key[0])) {
dashedKey = `-${dashedKey}`;
}
myRules[dashedKey] = val;
}
if (Object.keys(myRules).length) {
builtRules[key] = myRules;
}
}
return builtRules;
}
export function buildKeyframeRules(
rules: KeyframeRules,
): NormalisedKeyframeRules {
const spec = {};
for (const selector in rules) {
const newRules = {};
const rules2 = rules[selector];
if (!rules2 || typeof rules2 !== 'object') {
throw new Error('Keyframe spec must only have objects');
}
for (const key in rules2) {
let val = rules2[key];
if (shouldAppendPixel(key, val)) {
val += 'px';
} else if (typeof val === 'number') {
val = String(val);
}
if (typeof val !== 'string') {
throw new Error('Keyframe objects must only have strings values');
}
newRules[key] = val;
}
spec[selector] = newRules;
}
return spec;
}
export function buildRules(
rules: BaseRules,
props: NormalisedRules,
context: Object,
): PlainRules {
const style = {};
for (const key in rules) {
let val = rules[key];
if (typeof val === 'function') {
val = val(props, context);
}
if (val != null && shouldAppendPixel(key, val)) {
val += 'px';
}
if (val != null) {
style[key] = String(val);
}
}
return style;
}

View File

@@ -1,92 +0,0 @@
/**
* Copyright 2018-present Facebook.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* @format
*/
const invariant = require('invariant');
function makeStyleTag(): HTMLStyleElement {
const tag = document.createElement('style');
tag.type = 'text/css';
tag.appendChild(document.createTextNode(''));
const {head} = document;
invariant(head, 'expected head');
head.appendChild(tag);
return tag;
}
export class StyleSheet {
constructor(isSpeedy?: boolean) {
this.injected = false;
this.isSpeedy = Boolean(isSpeedy);
this.flush();
this.inject();
}
ruleIndexes: Array<string>;
injected: boolean;
isSpeedy: boolean;
tag: HTMLStyleElement;
getRuleCount(): number {
return this.ruleIndexes.length;
}
flush() {
this.ruleIndexes = [];
if (this.tag) {
this.tag.innerHTML = '';
}
}
inject() {
if (this.injected) {
throw new Error('already injected stylesheet!');
}
this.tag = makeStyleTag();
this.injected = true;
}
delete(key: string) {
const index = this.ruleIndexes.indexOf(key);
if (index < 0) {
// TODO maybe error
return;
}
this.ruleIndexes.splice(index, 1);
const tag = this.tag;
if (this.isSpeedy) {
const sheet = tag.sheet;
invariant(sheet, 'expected sheet');
// $FlowFixMe: sheet is actually CSSStylesheet
sheet.deleteRule(index);
} else {
tag.removeChild(tag.childNodes[index + 1]);
}
}
insert(key: string, rule: string) {
const tag = this.tag;
if (this.isSpeedy) {
const sheet = tag.sheet;
invariant(sheet, 'expected sheet');
// $FlowFixMe: sheet is actually CSSStylesheet
sheet.insertRule(rule, sheet.cssRules.length);
} else {
tag.appendChild(document.createTextNode(rule));
}
this.ruleIndexes.push(key);
}
}

File diff suppressed because it is too large Load Diff