((acc, next) => {
+ if (next) {
+ Object.keys(next).forEach(compName => {
+ const originalTarget = acc[compName] || {}
+ const originalSource = next[compName] || {}
+ acc[compName] = v => {
+ const targetResult = callable(originalTarget)(v)
+ const sourceResult = callable(originalSource)(v)
+ Object.keys(sourceResult).forEach(key => {
+ const transformedArray = []
+
+ const { map, keys } = transformStyleArrayToMap(targetResult[key], new Map(), [])
+ const { keys: keysFinal } = transformStyleArrayToMap(sourceResult[key], map, keys)
+ for (let k = 0; k < keysFinal.length; k++) {
+ transformedArray.push([
+ keysFinal[k] === true ? null : keysFinal[k],
+ map.get(keysFinal[k]),
+ ])
+ }
+
+ targetResult[key] = transformedArray
+ })
+ return targetResult
+ }
+ })
+ }
+ return acc
+ }, initial)
+}
/**
* Merges a single component's styles (keyed by component part) with another component's styles.
@@ -373,6 +449,12 @@ const mergeThemes = (...themes: ThemeInput[]): ThemePrepared => {
acc.animations = mergeAnimations(acc.animations, next.animations)
+ acc.componentSelectorStyles = mergeComponentSelectorStyles(
+ acc.componentSelectorStyles,
+ next.componentSelectorStyles,
+ )
+
+ acc.name = next.name || acc.name
return acc
},
// .reduce() will modify "emptyTheme" object, so we should clone it before actual usage
diff --git a/packages/react/src/lib/renderComponent.tsx b/packages/react/src/lib/renderComponent.tsx
index a691e18ca7..a90356323b 100644
--- a/packages/react/src/lib/renderComponent.tsx
+++ b/packages/react/src/lib/renderComponent.tsx
@@ -182,15 +182,17 @@ const renderComponent = (
theme = emptyTheme,
telemetry = undefined as Telemetry,
_internal_resolvedComponentVariables: resolvedComponentVariables = {},
+ _internal_resolvedComponentStyles: resolvedComponentStyles = {},
} = context || {}
const startTime = telemetry && telemetry.enabled ? performance.now() : 0
const ElementType = getElementType(props) as React.ReactType
const stateAndProps = { ...state, ...props }
-
+ let variablesUpdated = false
// Resolve variables for this component, cache the result in provider
if (!resolvedComponentVariables[displayName]) {
+ variablesUpdated = true
resolvedComponentVariables[displayName] =
callable(theme.componentVariables[displayName])(theme.siteVariables) || {} // component variables must not be undefined/null (see mergeComponentVariables contract)
}
@@ -207,14 +209,6 @@ const renderComponent =
(
? createAnimationStyles(props.animation, context.theme)
: {}
- // Resolve styles using resolved variables, merge results, allow props.styles to override
- const mergedStyles: ComponentSlotStylesPrepared = mergeComponentStyles(
- theme.componentStyles[displayName],
- withDebugId({ root: props.design }, 'props.design'),
- withDebugId({ root: props.styles }, 'props.styles'),
- withDebugId({ root: animationCSSProp }, 'props.animation'),
- )
-
const accessibility: ReactAccessibilityBehavior = getAccessibility(
displayName,
stateAndProps,
@@ -243,13 +237,113 @@ const renderComponent =
(
displayName, // does not affect styles, only used by useEnhancedRenderer in docs
}
- const { resolvedStyles, resolvedStylesDebug, classes } = resolveStylesAndClasses(
- mergedStyles,
- styleParam,
- renderer ? style => renderer.renderRule(() => style, felaParam) : undefined,
- )
+ let classes: ComponentSlotClasses = {}
+ let resolvedStyles: ComponentSlotStylesPrepared = {}
+ let resolvedStylesDebug: { [key: string]: { styles: Object }[] } = {}
+
+ if (!(theme.componentSelectorStyles && theme.componentSelectorStyles[displayName])) {
+ // Resolve styles using resolved variables, merge results, allow props.styles to override
+ const mergedStyles: ComponentSlotStylesPrepared = mergeComponentStyles(
+ theme.componentStyles[displayName],
+ withDebugId({ root: props.design }, 'props.design'),
+ withDebugId({ root: props.styles }, 'props.styles'),
+ withDebugId({ root: animationCSSProp }, 'props.animation'),
+ )
+
+ const result = resolveStylesAndClasses(
+ mergedStyles,
+ styleParam,
+ renderer ? style => renderer.renderRule(() => style, felaParam) : undefined,
+ )
+
+ classes = result.classes
+ resolvedStyles = result.resolvedStyles
+ resolvedStylesDebug = result.resolvedStylesDebug
+
+ classes.root = cx(className, classes.root, props.className)
+ }
+
+ if (
+ (!resolvedComponentStyles[displayName] || variablesUpdated) &&
+ theme.componentSelectorStyles &&
+ theme.componentSelectorStyles[displayName]
+ ) {
+ resolvedComponentStyles[displayName] = true // add flag that the styles were written in the head
+ const rules = theme.componentSelectorStyles[displayName](resolvedVariables)
+ const selectorObjectToCssSelector = (obj, baseClassName) => {
+ let cssSelector = baseClassName || ''
+ Object.keys(obj).forEach(key => {
+ if (obj[key] === true) {
+ cssSelector += `.${key}`
+ } else if (obj[key] === false) {
+ cssSelector += `:not(.${key})`
+ } else {
+ cssSelector += `.${key}--${obj[key]}`
+ }
+ })
+ return cssSelector
+ }
+
+ const generateStylesheetObject = (rules, base) => {
+ return Object.keys(rules).reduce((accR, next) => {
+ const tuples = rules[next]
+ const baseClassName = next === 'root' ? base : `${base}__${next}`
+ const result = tuples.reduce((acc, [selector, style]) => {
+ if (Array.isArray(selector)) {
+ for (let i = 0; i < selector.length; i++) {
+ acc[selectorObjectToCssSelector(selector[i] || {}, baseClassName)] = {
+ ...style,
+ className: selectorObjectToCssSelector(selector[i] || {}, baseClassName),
+ }
+ }
+ return acc
+ }
+ acc[selectorObjectToCssSelector(selector || {}, baseClassName)] = {
+ ...style,
+ className: selectorObjectToCssSelector(selector || {}, baseClassName),
+ }
+ return acc
+ }, {})
+ accR[next] = result
+ return accR
+ }, {})
+ }
- classes.root = cx(className, classes.root, props.className)
+ // TODO: fix className resolution
+ let baseClassName =
+ displayName === 'Menu'
+ ? 'ui-menu'
+ : displayName === 'MenuItem'
+ ? 'ui-menu__item'
+ : displayName === 'MenuItemWrapper'
+ ? 'ui-menu__item__wrapper'
+ : 'ui-menu__divider'
+
+ if (theme.name) {
+ baseClassName = `${theme.name}.${baseClassName}`
+ }
+ const stylesheet = generateStylesheetObject(rules, baseClassName)
+
+ Object.keys(stylesheet).forEach(slot => {
+ Object.keys(stylesheet[slot]).forEach(selector => {
+ renderer.renderRule(() => stylesheet[slot][selector], felaParam)
+ })
+ })
+
+ if (props.design || props.style || animationCSSProp) {
+ const mergedStyles: ComponentSlotStylesPrepared = mergeComponentStyles(
+ withDebugId({ root: props.design }, 'props.design'),
+ withDebugId({ root: props.styles }, 'props.styles'),
+ withDebugId({ root: animationCSSProp }, 'props.animation'),
+ )
+
+ if (mergedStyles.root) {
+ resolvedStyles = { root: callable(mergedStyles['root'])(styleParam) }
+ const classesRoot = renderer.renderRule(callable(resolvedStyles['root']), felaParam)
+ classes.root = cx(className, classesRoot, props.className)
+ }
+ }
+ }
const resolvedConfig: RenderResultConfig
= {
ElementType,
diff --git a/packages/react/src/lib/resolveComponentRules.ts b/packages/react/src/lib/resolveComponentRules.ts
new file mode 100644
index 0000000000..1762c02059
--- /dev/null
+++ b/packages/react/src/lib/resolveComponentRules.ts
@@ -0,0 +1,72 @@
+import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '../themes/types'
+
+export function isObject(item): boolean {
+ return typeof item === 'object' && !Array.isArray(item) && item !== null
+}
+
+// fast object merge with assumptions
+export function deepMergeObjects(target: object, source: object): object {
+ Object.keys(source).forEach(key => {
+ const value = source[key]
+
+ if (isObject(value)) {
+ if (!target[key]) target[key] = {}
+ deepMergeObjects(target[key], value)
+ } else {
+ target[key] = value
+ }
+ })
+
+ return target
+}
+
+const isMatch = (props, selector): boolean => {
+ if (selector === null) return true
+ return Object.keys(selector).every(
+ k =>
+ props[k] === selector[k] ||
+ ((typeof selector[k] === 'boolean' || props[k] === undefined) &&
+ !!props[k] === !!selector[k]),
+ )
+}
+
+const reduceSelectorStyleTuples = (props, tuples: [object, ICSSInJSStyle][]): ICSSInJSStyle => {
+ return tuples.reduce((acc, [selector, style]) => {
+ if (Array.isArray(selector)) {
+ for (let i = 0; i < selector.length; i++) {
+ if (isMatch(props, selector[i])) {
+ return deepMergeObjects(acc, style)
+ }
+ }
+ return acc
+ }
+ return isMatch(props, selector) ? deepMergeObjects(acc, style) : acc
+ }, {})
+}
+
+// Map reduces object values (array of tuples [selector, style])
+const resolveComponentRules = (rules, props = {}): ICSSInJSStyle => {
+ return Object.keys(rules).reduce((acc, next) => {
+ acc[next] = reduceSelectorStyleTuples(props, rules[next])
+ return acc
+ }, {})
+}
+
+// TODO: temporary during migration to selector/style tuple styles
+// Backports selector/style tuple styles to previous signature
+export const backportComponentStyle = (selectorStyleFunc): ComponentSlotStylesPrepared => {
+ if (typeof selectorStyleFunc !== 'function') {
+ return selectorStyleFunc
+ }
+
+ const withoutVariables = selectorStyleFunc({})
+
+ return Object.keys(withoutVariables).reduce((acc, part) => {
+ acc[part] = ({ props, variables }) => {
+ return resolveComponentRules(selectorStyleFunc(variables), props)[part]
+ }
+ return acc
+ }, {})
+}
+
+export default resolveComponentRules
diff --git a/packages/react/src/themes/teams-dark/index.ts b/packages/react/src/themes/teams-dark/index.ts
index 0e883d6101..e54739b4a3 100644
--- a/packages/react/src/themes/teams-dark/index.ts
+++ b/packages/react/src/themes/teams-dark/index.ts
@@ -8,6 +8,7 @@ export default mergeThemes(
teams,
createTheme(
{
+ name: 'teams-dark',
siteVariables,
componentVariables,
},
diff --git a/packages/react/src/themes/teams-high-contrast/componentStyles.ts b/packages/react/src/themes/teams-high-contrast/componentStyles.ts
index fc807a3cbc..ad79d17820 100644
--- a/packages/react/src/themes/teams-high-contrast/componentStyles.ts
+++ b/packages/react/src/themes/teams-high-contrast/componentStyles.ts
@@ -1,6 +1,5 @@
export { default as Attachment } from './components/Attachment/attachmentStyles'
export { default as Button } from './components/Button/buttonStyles'
-export { default as MenuItem } from './components/Menu/menuItemStyles'
export { default as Alert } from './components/Alert/alertStyles'
export { default as Dialog } from './components/Dialog/dialogStyles'
export { default as Dropdown } from './components/Dropdown/dropdownStyles'
diff --git a/packages/react/src/themes/teams-high-contrast/components/Avatar/avatarVariables.ts b/packages/react/src/themes/teams-high-contrast/components/Avatar/avatarVariables.ts
index 08cf6cc6b9..047326ca10 100644
--- a/packages/react/src/themes/teams-high-contrast/components/Avatar/avatarVariables.ts
+++ b/packages/react/src/themes/teams-high-contrast/components/Avatar/avatarVariables.ts
@@ -2,6 +2,6 @@ import { AvatarVariables } from '../../../teams/components/Avatar/avatarVariable
export default (siteVariables: any): Partial => ({
avatarBorderColor: siteVariables.colors.white,
- avatarBorderWidth: 2,
+ avatarBorderWidth: '2px',
statusBorderColor: siteVariables.colors.black,
})
diff --git a/packages/react/src/themes/teams-high-contrast/components/Menu/menuItemStyles.ts b/packages/react/src/themes/teams-high-contrast/components/Menu/menuItemStyles.ts
index b4d8cfde7b..af435ab7ca 100644
--- a/packages/react/src/themes/teams-high-contrast/components/Menu/menuItemStyles.ts
+++ b/packages/react/src/themes/teams-high-contrast/components/Menu/menuItemStyles.ts
@@ -1,109 +1,43 @@
-import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '../../../types'
+import { ComponentSelectorsAndStyles } from '../../../types'
import { MenuVariables } from '../../../teams/components/Menu/menuVariables'
-import { MenuItemProps, MenuItemState } from '../../../../components/Menu/MenuItem'
+import { MenuItemProps } from '../../../../components/Menu/MenuItem'
import { underlinedItem } from '../../../teams/components/Menu/menuItemStyles'
-type MenuItemPropsAndState = MenuItemProps & MenuItemState
-
-const menuItemStyles: ComponentSlotStylesPrepared = {
- wrapper: ({ props: p, variables: v }): ICSSInJSStyle => {
- const {
- iconOnly,
- isFromKeyboard,
- vertical,
- active,
- underlined,
- primary,
- pointing,
- disabled,
- } = p
-
- return {
- ':hover': {
- color: v.colorActive,
- ...(!active && {
- ...(primary && !underlined && { color: v.colorActive }),
- background: v.backgroundColorFocus,
- }),
- },
-
- ...(active &&
- !underlined && {
- background: v.backgroundColorActive,
- color: v.colorActive,
- }),
-
- ...((iconOnly || vertical) && {
- ...(isFromKeyboard && {
- color: v.colorActive,
- background: v.backgroundColorFocus,
- }),
-
- ...(active && {
- color: v.colorActive,
- background: v.backgroundColorActive,
- }),
-
- ':hover': {
- color: v.colorActive,
- background: v.backgroundColorFocus,
- },
- }),
-
- ...(underlined && {
- ...(active && {
- color: v.color,
- }),
- ':hover': {
- color: v.color,
- },
- ...(isFromKeyboard && {
- color: v.colorActive,
- }),
- }),
-
- ...(pointing &&
- vertical && {
- '::before': {
- display: 'none',
- },
- }),
-
- ...(disabled && {
- cursor: 'default',
- ':hover': {
- // reset all existing hover styles
- },
- }),
- }
- },
-
- root: ({ props, variables: v }): ICSSInJSStyle => {
- const { iconOnly, isFromKeyboard, underlined, primary, color, active } = props
-
- return {
- ...(underlined && {
- ...(active && {
- color: v.color,
- ...(!primary &&
- !color && {
- ...underlinedItem(v.color),
- }),
- }),
+const menuItemStyles: ComponentSelectorsAndStyles = v => ({
+ root: [
+ [
+ { underlined: true },
+ {
':hover': {
color: v.color,
},
- ...(isFromKeyboard && {
- color: v.colorActive,
- }),
- }),
- // focus styles
- ...(isFromKeyboard &&
- iconOnly && {
- borderColor: 'transparent',
- }),
- }
- },
-}
+ },
+ ],
+ [
+ { underlined: true, active: true },
+ {
+ color: v.color,
+ },
+ ],
+ [
+ { underlined: true, active: true, primary: false },
+ {
+ ...underlinedItem(v.color),
+ },
+ ],
+ [
+ { underlined: true, isFromKeyboard: true },
+ {
+ color: v.colorActive,
+ },
+ ],
+ [
+ { isFromKeyboard: true, iconOnly: true },
+ {
+ borderColor: 'transparent',
+ },
+ ],
+ ],
+})
export default menuItemStyles
diff --git a/packages/react/src/themes/teams-high-contrast/components/Menu/menuItemWrapperStyles.ts b/packages/react/src/themes/teams-high-contrast/components/Menu/menuItemWrapperStyles.ts
new file mode 100644
index 0000000000..30039ddd25
--- /dev/null
+++ b/packages/react/src/themes/teams-high-contrast/components/Menu/menuItemWrapperStyles.ts
@@ -0,0 +1,82 @@
+import { ComponentSelectorsAndStyles } from '../../../types'
+import { MenuVariables } from '../../../teams/components/Menu/menuVariables'
+import { MenuItemWrapperProps } from '../../../../components/Menu/MenuItemWrapper'
+
+const menuItemWrapperStyles: ComponentSelectorsAndStyles<
+ MenuItemWrapperProps,
+ MenuVariables
+> = v => ({
+ root: [
+ [
+ { disabled: false },
+ {
+ ':hover': {
+ color: v.colorActive,
+ },
+ },
+ ],
+ [
+ { disabled: false, active: false, underlined: false },
+ {
+ ':hover': {
+ background: v.backgroundColorFocus,
+ },
+ },
+ ],
+ [
+ { active: true, underlined: false },
+ {
+ color: v.colorActive,
+ background: v.backgroundColorActive,
+ },
+ ],
+ [
+ [{ iconOnly: true, disabled: false }, { vertical: true, disabled: false }],
+ {
+ ':hover': {
+ background: v.backgroundColorFocus,
+ },
+ },
+ ],
+ [
+ [{ iconOnly: true, isFromKeyboard: true }, { vertical: true, isFromKeyboard: true }],
+ {
+ color: v.colorActive,
+ background: v.backgroundColorFocus,
+ },
+ ],
+ [
+ [{ iconOnly: true, active: true }, { vertical: true, active: true }],
+ {
+ color: v.colorActive,
+ background: v.backgroundColorActive,
+ },
+ ],
+ [
+ { underlined: true, disabled: false },
+ {
+ ':hover': {
+ color: v.color,
+ },
+ },
+ ],
+ [{ underlined: true, active: true }, { color: v.color }],
+ [
+ { underlined: true, isFromKeyboard: true },
+ {
+ color: v.colorActive,
+ },
+ ],
+ [
+ { pointing: true, vertical: true },
+ {
+ '::before': {
+ display: 'none',
+ },
+ },
+ ],
+ [{ disabled: true }, { cursor: 'default' }],
+ ],
+})
+
+export default menuItemWrapperStyles
diff --git a/packages/react/src/themes/teams-high-contrast/index.ts b/packages/react/src/themes/teams-high-contrast/index.ts
index be1fbbf1fb..1f1106a4af 100644
--- a/packages/react/src/themes/teams-high-contrast/index.ts
+++ b/packages/react/src/themes/teams-high-contrast/index.ts
@@ -4,14 +4,23 @@ import * as componentVariables from './componentVariables'
import * as componentStyles from './componentStyles'
import teams from '../teams'
import { createTheme } from '../createTheme'
+import menuItemStyles from './components/Menu/menuItemStyles'
+import menuItemWrapperStyles from './components/Menu/menuItemWrapperStyles'
export default mergeThemes(
teams,
createTheme(
{
+ name: 'teams-high-contrast',
siteVariables,
componentVariables,
componentStyles,
+ componentSelectorStyles: {
+ Menu: v => ({}),
+ MenuItem: menuItemStyles,
+ MenuDivider: v => ({}),
+ MenuItemWrapper: menuItemWrapperStyles,
+ },
},
'teams-high-contrast',
),
diff --git a/packages/react/src/themes/teams/componentStyles.ts b/packages/react/src/themes/teams/componentStyles.ts
index a502b9559a..3281d38b6d 100644
--- a/packages/react/src/themes/teams/componentStyles.ts
+++ b/packages/react/src/themes/teams/componentStyles.ts
@@ -65,10 +65,6 @@ export { default as ItemLayout } from './components/ItemLayout/itemLayoutStyles'
export { default as List } from './components/List/listStyles'
export { default as ListItem } from './components/List/listItemStyles'
-export { default as Menu } from './components/Menu/menuStyles'
-export { default as MenuItem } from './components/Menu/menuItemStyles'
-export { default as MenuDivider } from './components/Menu/menuDividerStyles'
-
export { default as MenuButton } from './components/MenuButton/menuButtonStyles'
export { default as Popup } from './components/Popup/popupStyles'
diff --git a/packages/react/src/themes/teams/componentVariables.ts b/packages/react/src/themes/teams/componentVariables.ts
index 8624bcf47f..8e2b6d1a43 100644
--- a/packages/react/src/themes/teams/componentVariables.ts
+++ b/packages/react/src/themes/teams/componentVariables.ts
@@ -57,6 +57,7 @@ export { default as ListItem } from './components/List/listItemVariables'
export { default as Menu } from './components/Menu/menuVariables'
export { default as MenuItem } from './components/Menu/menuItemVariables'
export { default as MenuDivider } from './components/Menu/menuDividerVariables'
+export { default as MenuItemWrapper } from './components/Menu/menuItemWrapperVariables'
export { default as Popup } from './components/Popup/popupVariables'
export { default as PopupContent } from './components/Popup/popupContentVariables'
diff --git a/packages/react/src/themes/teams/components/Avatar/avatarStyles.ts b/packages/react/src/themes/teams/components/Avatar/avatarStyles.ts
index 81573be61c..402cc0f812 100644
--- a/packages/react/src/themes/teams/components/Avatar/avatarStyles.ts
+++ b/packages/react/src/themes/teams/components/Avatar/avatarStyles.ts
@@ -1,58 +1,152 @@
-import { pxToRem } from '../../../../lib'
-import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '../../../types'
+import { ComponentSelectorsAndStyles } from '../../../types'
import { AvatarProps } from '../../../../components/Avatar/Avatar'
+import { AvatarVariables } from './avatarVariables'
+import { backportComponentStyle } from '../../../../lib/resolveComponentRules'
-const sizeToPxValue = {
- smallest: 24,
- smaller: 24,
- small: 24,
- medium: 32,
- large: 36,
- larger: 42,
- largest: 48,
-}
+const avatarStyles: ComponentSelectorsAndStyles = v => ({
+ root: [
+ [
+ null,
+ {
+ position: 'relative',
+ backgroundColor: 'inherit',
+ display: 'inline-block',
+ verticalAlign: 'middle',
+ height: v.medium,
+ width: v.medium,
+ },
+ ],
-const avatarStyles: ComponentSlotStylesPrepared = {
- root: ({ props: { size } }): ICSSInJSStyle => {
- const sizeInRem = pxToRem(sizeToPxValue[size])
+ //
+ // Sizes
+ //
+ [{ size: 'smallest' }, { width: v.smallest, height: v.smallest }],
+ [{ size: 'smaller' }, { width: v.smaller, height: v.smaller }],
+ [{ size: 'small' }, { width: v.small, height: v.small }],
+ [{ size: 'medium' }, { width: v.medium, height: v.medium }],
+ [{ size: 'large' }, { width: v.large, height: v.large }],
+ [{ size: 'larger' }, { width: v.larger, height: v.larger }],
+ [{ size: 'largest' }, { width: v.largest, height: v.largest }],
+ ],
- return {
- position: 'relative',
- backgroundColor: 'inherit',
- display: 'inline-block',
- verticalAlign: 'middle',
- height: sizeInRem,
- width: sizeInRem,
- }
- },
- image: ({ variables: v }): ICSSInJSStyle => ({
- borderColor: v.avatarBorderColor,
- borderStyle: 'solid',
- borderWidth: `${v.avatarBorderWidth}px`,
+ // ----------------------------------------
+ // Image
+ // ----------------------------------------
+ image: [
+ [
+ null,
+ {
+ borderColor: v.avatarBorderColor,
+ borderStyle: 'solid',
+ borderWidth: v.avatarBorderWidth,
- height: '100%',
- objectFit: 'cover',
- verticalAlign: 'top',
- width: '100%',
- }),
- label: ({ props: { size } }): ICSSInJSStyle => {
- const sizeInRem = pxToRem(sizeToPxValue[size])
- return {
- display: 'inline-block',
- width: sizeInRem,
- height: sizeInRem,
- lineHeight: sizeInRem,
- fontSize: pxToRem(sizeToPxValue[size] / 2.333),
- verticalAlign: 'top',
- textAlign: 'center',
- padding: '0px',
- }
- },
- status: ({ variables: v }): ICSSInJSStyle => ({
- position: 'absolute',
- bottom: `-${v.statusBorderWidth}px`,
- right: `-${v.statusBorderWidth}px`,
- }),
-}
+ height: '100%',
+ objectFit: 'cover',
+ verticalAlign: 'top',
+ width: '100%',
+ },
+ ],
+ ],
-export default avatarStyles
+ // ----------------------------------------
+ // Label
+ // ----------------------------------------
+ label: [
+ [
+ null,
+ {
+ display: 'inline-block',
+ width: v.medium,
+ height: v.medium,
+ lineHeight: v.medium,
+ fontSize: `calc(${v.medium} / 2.333)`,
+ verticalAlign: 'top',
+ textAlign: 'center',
+ padding: '0px',
+ },
+ ],
+
+ //
+ // Sizes
+ //
+ [
+ { size: 'smallest' },
+ {
+ fontSize: `calc(${v.smallest} / 2.333)`,
+ width: v.smallest,
+ height: v.smallest,
+ lineHeight: v.smallest,
+ },
+ ],
+ [
+ { size: 'smaller' },
+ {
+ fontSize: `calc(${v.smaller} / 2.333)`,
+ width: v.smaller,
+ height: v.smaller,
+ lineHeight: v.smaller,
+ },
+ ],
+ [
+ { size: 'small' },
+ {
+ fontSize: `calc(${v.small} / 2.333)`,
+ width: v.small,
+ height: v.small,
+ lineHeight: v.small,
+ },
+ ],
+ [
+ { size: 'medium' },
+ {
+ fontSize: `calc(${v.medium} / 2.333)`,
+ width: v.medium,
+ height: v.medium,
+ lineHeight: v.medium,
+ },
+ ],
+ [
+ { size: 'large' },
+ {
+ fontSize: `calc(${v.large} / 2.333)`,
+ width: v.large,
+ height: v.large,
+ lineHeight: v.large,
+ },
+ ],
+ [
+ { size: 'larger' },
+ {
+ fontSize: `calc(${v.larger} / 2.333)`,
+ width: v.larger,
+ height: v.larger,
+ lineHeight: v.larger,
+ },
+ ],
+ [
+ { size: 'largest' },
+ {
+ fontSize: `calc(${v.largest} / 2.333)`,
+ width: v.largest,
+ height: v.largest,
+ lineHeight: v.largest,
+ },
+ ],
+ ],
+
+ // ----------------------------------------
+ // Status
+ // ----------------------------------------
+ status: [
+ [
+ null,
+ {
+ position: 'absolute',
+ bottom: 0,
+ right: 0,
+ boxShadow: `0 0 0 ${v.statusBorderWidth} ${v.statusBorderColor}`,
+ },
+ ],
+ ],
+})
+export default backportComponentStyle(avatarStyles)
diff --git a/packages/react/src/themes/teams/components/Avatar/avatarVariables.ts b/packages/react/src/themes/teams/components/Avatar/avatarVariables.ts
index f952d65e12..e8215bcc21 100644
--- a/packages/react/src/themes/teams/components/Avatar/avatarVariables.ts
+++ b/packages/react/src/themes/teams/components/Avatar/avatarVariables.ts
@@ -1,13 +1,31 @@
+import { pxToRem } from '../../../../lib'
+
export interface AvatarVariables {
avatarBorderColor: string
- avatarBorderWidth: number
+ avatarBorderWidth: string
statusBorderColor: string
- statusBorderWidth: number
+ statusBorderWidth: string
+
+ smallest: string
+ smaller: string
+ small: string
+ medium: string
+ large: string
+ larger: string
+ largest: string
}
export default (siteVariables): AvatarVariables => ({
avatarBorderColor: '',
- avatarBorderWidth: 0,
+ avatarBorderWidth: '0',
statusBorderColor: siteVariables.bodyBackground,
- statusBorderWidth: 2,
+ statusBorderWidth: '2px',
+
+ smallest: pxToRem(24),
+ smaller: pxToRem(24),
+ small: pxToRem(24),
+ medium: pxToRem(32),
+ large: pxToRem(36),
+ larger: pxToRem(42),
+ largest: pxToRem(48),
})
diff --git a/packages/react/src/themes/teams/components/Menu/menuDividerStyles.ts b/packages/react/src/themes/teams/components/Menu/menuDividerStyles.ts
index 14dc8f37fe..7fe720c2d7 100644
--- a/packages/react/src/themes/teams/components/Menu/menuDividerStyles.ts
+++ b/packages/react/src/themes/teams/components/Menu/menuDividerStyles.ts
@@ -1,32 +1,44 @@
-import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '../../../types'
+import { ComponentSelectorsAndStyles } from '../../../types'
import { MenuDividerProps } from '../../../../components/Menu/MenuDivider'
import { MenuVariables } from './menuVariables'
-import { getColorScheme } from '../../colors'
+// import { backportComponentStyle } from '../../../../lib/resolveComponentRules'
-const menuDividerStyles: ComponentSlotStylesPrepared = {
- root: ({ props: p, variables: v }): ICSSInJSStyle => {
- const colors = getColorScheme(v.colorScheme, null, p.primary)
- const borderColor = p.primary ? v.primaryBorderColor : v.borderColor || colors.border
- const borderType = p.vertical ? 'borderTop' : 'borderLeft'
-
- return p.content
- ? {
+const menuDividerStyles: ComponentSelectorsAndStyles = v => {
+ const primaryBorderColor =
+ v.primaryBorderColor || (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.border)
+ const defaultBorderColor =
+ v.borderColor || (v.colorScheme && v.colorScheme.default && v.colorScheme.default.border)
+ return {
+ root: [
+ [
+ { content: true },
+ {
display: 'flex',
justifyContent: 'center',
flexDirection: 'column',
textAlign: 'center',
- }
- : {
- [borderType]: `1px solid ${borderColor}`,
- ...(!p.vertical && {
- alignSelf: 'stretch',
- }),
- ...(p.vertical &&
- p.inSubmenu && {
- margin: '8px 0',
- }),
- }
- },
+ },
+ ],
+ [
+ { content: false, vertical: true, primary: true },
+ { borderTop: `1px solid ${primaryBorderColor}` },
+ ],
+ [
+ { content: false, vertical: true, primary: false },
+ { borderTop: `1px solid ${defaultBorderColor}` },
+ ],
+ [{ content: false, vertical: false }, { alignSelf: 'stretch' }],
+ [{ content: false, vertical: true, inSubmenu: true }, { margin: '8px 0' }],
+ [
+ { content: false, vertical: false, primary: true },
+ { borderLeft: `1px solid ${primaryBorderColor}` },
+ ],
+ [
+ { content: false, vertical: false, primary: false },
+ { borderLeft: `1px solid ${defaultBorderColor}` },
+ ],
+ ],
+ }
}
export default menuDividerStyles
diff --git a/packages/react/src/themes/teams/components/Menu/menuItemStyles.ts b/packages/react/src/themes/teams/components/Menu/menuItemStyles.ts
index 6e822e09b3..79d4a7f92b 100644
--- a/packages/react/src/themes/teams/components/Menu/menuItemStyles.ts
+++ b/packages/react/src/themes/teams/components/Menu/menuItemStyles.ts
@@ -1,450 +1,287 @@
import { pxToRem } from '../../../../lib'
-import {
- ComponentSlotStylesPrepared,
- ICSSInJSStyle,
- StrictColorScheme,
- ItemType,
-} from '../../../types'
-import { MenuVariables, menuColorAreas } from './menuVariables'
-import { MenuItemProps, MenuItemState } from '../../../../components/Menu/MenuItem'
-import { getColorScheme } from '../../colors'
+import { ICSSInJSStyle, ComponentSelectorsAndStyles } from '../../../types'
+import { MenuVariables } from './menuVariables'
+import MenuItem, { MenuItemProps } from '../../../../components/Menu/MenuItem'
import getIconFillOrOutlineStyles from '../../getIconFillOrOutlineStyles'
-type MenuItemPropsAndState = MenuItemProps & MenuItemState
-
-export const verticalPillsBottomMargin = pxToRem(5)
-export const horizontalPillsRightMargin = pxToRem(8)
-export const verticalPointingBottomMargin = pxToRem(12)
-
export const underlinedItem = (color: string): ICSSInJSStyle => ({
paddingBottom: 0,
borderBottom: `solid ${pxToRem(4)} ${color}`,
transition: 'color .1s ease',
})
-const getActionStyles = ({
- props: { primary, underlined, iconOnly },
- variables: v,
- colors,
-}: {
- props: MenuItemPropsAndState
- variables: MenuVariables
- colors: StrictColorScheme>
-}): ICSSInJSStyle =>
- underlined || iconOnly
- ? {
- color: v.color,
- }
- : primary
- ? {
- color: colors.foregroundActive,
- background: v.backgroundColorActive || colors.backgroundActive,
- }
- : {
- color: v.color,
- background: v.backgroundColorActive || colors.backgroundActive,
- }
-
-const getFocusedStyles = ({
- props,
- variables: v,
- colors,
-}: {
- props: MenuItemPropsAndState
- variables: MenuVariables
- colors: StrictColorScheme>
-}): ICSSInJSStyle => {
- const { primary, underlined, isFromKeyboard, active, vertical } = props
- if (active && !underlined && !vertical) return {}
- return {
- color: primary ? colors.foregroundFocus : v.colorActive,
- background: v.backgroundColorFocus || colors.backgroundFocus,
- ...(vertical && isFromKeyboard && !primary
- ? {
- border: `solid 1px ${v.borderColorFocus}`,
- outline: `solid 1px ${v.outlineColorFocus}`,
- margin: pxToRem(1),
- background: v.verticalBackgroundColorFocus || colors.backgroundFocus,
- }
- : {}),
- }
-}
-
-const getHoverStyles = ({
- props,
- variables: v,
- colors,
-}: {
- props: MenuItemPropsAndState
- variables: MenuVariables
- colors: StrictColorScheme>
-}): ICSSInJSStyle => {
- const { underlined, active, vertical } = props
- if (active && !underlined && !vertical) return {}
- return {
- ...(underlined
- ? {
- color: v.colorActive,
- }
- : {
- color: colors.foregroundHover,
- background: v.backgroundColorHover || colors.backgroundHover,
- }),
- }
-}
-
-const pointingBeak = ({
- props,
- variables: v,
- colors,
-}: {
- props: MenuItemProps
- variables: MenuVariables
- colors: StrictColorScheme>
-}): ICSSInJSStyle => {
- const { pointing, primary } = props
-
- let top: string
- let borders: ICSSInJSStyle
-
- const backgroundColor = v.backgroundColorActive || colors.backgroundActive
- const borderColor = v.borderColor || primary ? v.primaryBorderColor : colors.border
-
- if (pointing === 'start') {
- borders = {
- borderTop: `1px solid ${borderColor}`,
- borderLeft: `1px solid ${borderColor}`,
- }
- top = '-1px' // 1px for the border
- } else {
- borders = {
- borderBottom: `1px solid ${borderColor}`,
- borderRight: `1px solid ${borderColor}`,
- }
- top = '100%'
- }
-
- return {
- '::after': {
- visibility: 'visible',
- background: backgroundColor,
- position: 'absolute',
- content: '""',
- top,
- left: '50%',
- transform: 'translateX(-50%) translateY(-50%) rotate(45deg)',
- margin: '.5px 0 0',
- width: pxToRem(10),
- height: pxToRem(10),
- border: 'none',
- ...borders,
- zIndex: 2,
- transition: 'background .1s ease',
- },
- }
-}
-
-const menuItemStyles: ComponentSlotStylesPrepared = {
- wrapper: ({ props, variables: v, theme }): ICSSInJSStyle => {
- const {
- active,
- disabled,
- iconOnly,
- isFromKeyboard,
- pills,
- pointing,
- secondary,
- underlined,
- vertical,
- primary,
- } = props
-
- const colors = getColorScheme(v.colorScheme, null, primary)
-
- return {
- color: 'inherit',
- lineHeight: 1,
- position: 'relative',
- verticalAlign: 'middle',
- display: 'block',
-
- ...(secondary && {
- background: 'salmon',
- }),
-
- ...(vertical && {
- border: `solid ${v.verticalItemBorderWidth} ${v.verticalItemBorderColor}`,
- }),
-
- ...(pills && {
- ...(vertical
- ? { margin: `0 0 ${verticalPillsBottomMargin} 0` }
- : { margin: `0 ${horizontalPillsRightMargin} 0 0` }),
- borderRadius: pxToRem(5),
- }),
-
- ...(underlined && {
- display: 'flex',
- alignItems: 'center',
- height: pxToRem(29),
- lineHeight: v.lineHeightBase,
- padding: `0 ${pxToRem(4)}`,
- margin: `0 ${pxToRem(4)} 0 0`,
- ':nth-child(n+2)': {
- marginLeft: `${pxToRem(4)}`,
+const menuItemStyles: ComponentSelectorsAndStyles = v => ({
+ root: [
+ [
+ null,
+ {
+ color: 'inherit',
+ display: 'block',
+ cursor: 'pointer',
+ whiteSpace: 'nowrap',
+ ':focus': {
+ outline: 0,
},
- boxShadow: 'none',
- }),
-
- // item separator
- ...(!vertical &&
- !pills &&
- !underlined &&
- !iconOnly && {
- boxShadow: `-1px 0 0 0 ${
- primary ? v.primaryBorderColor : v.borderColor || colors.border
- } inset`,
- }),
-
- // active styles
- ...(active && {
- ...getActionStyles({ props, variables: v, colors }),
-
- ...(pointing &&
- vertical &&
- !isFromKeyboard && {
- '::before': {
- content: `''`,
- position: 'absolute',
- width: pxToRem(3),
- height: `calc(100% + ${pxToRem(4)})`,
- top: pxToRem(-2),
- backgroundColor: v.pointingIndicatorBackgroundColor,
- ...(pointing === 'end' ? { right: pxToRem(-2) } : { left: pxToRem(-2) }),
- },
- }),
-
- ...(pointing &&
- !vertical && {
- ...pointingBeak({ props, variables: v, colors }),
- }),
- }),
-
- ...(iconOnly && {
- display: 'flex',
-
- // focus styles
- ...(isFromKeyboard && {
- color: v.iconOnlyColorActive,
- }),
-
- // hover styles
':hover': {
- color: v.iconOnlyColorActive,
+ color: 'inherit',
+ },
+ [`& .${MenuItem.slotClassNames.indicator}`]: {
+ position: 'relative',
+ float: 'right',
+ left: pxToRem(12),
+ userSelect: 'none',
+ marginRight: pxToRem(4),
+ },
+ [`& .${MenuItem.slotClassNames.submenu}`]: { zIndex: 1000 },
+ [`& .${MenuItem.slotClassNames.content}`]: {
+ whiteSpace: 'normal',
+ lineHeight: 1.5,
+ marginTop: pxToRem(-4),
+ marginBottom: pxToRem(-4),
+ display: 'inline-block',
},
- }),
-
- ...(!iconOnly && {
- // focus styles
- ...(isFromKeyboard && getFocusedStyles({ props, variables: v, colors })),
-
- // hover styles
- ':hover': getHoverStyles({ props, variables: v, colors }),
- }),
-
- ':first-child': {
- ...(!pills &&
- !iconOnly &&
- !(pointing && vertical) &&
- !underlined && {
- ...(vertical && {
- '::before': {
- display: 'none',
- },
- }),
- ...(!vertical && {
- borderBottomLeftRadius: pxToRem(3),
- borderTopLeftRadius: pxToRem(3),
- }),
- }),
},
-
- ...(disabled && {
- color: v.colorDisabled || colors.foregroundDisabled,
- ':hover': {
- // empty - overwrite all existing hover styles
+ ],
+ [
+ { iconOnly: false },
+ {
+ [`& .${MenuItem.slotClassNames.icon}`]: {
+ // reduce margins so text has the dominant influence on the vertical height
+ marginTop: 0,
+ marginBottom: pxToRem(-8),
+ verticalAlign: 'top',
},
- }),
- }
- },
-
- root: ({ props: p, variables: v }): ICSSInJSStyle => {
- const {
- active,
- iconOnly,
- isFromKeyboard,
- pointing,
- primary,
- underlined,
- vertical,
- disabled,
- } = p
-
- const colors = getColorScheme(v.colorScheme, null, primary)
-
- return {
- color: 'inherit',
- display: 'block',
- cursor: 'pointer',
- whiteSpace: 'nowrap',
-
- ...(pointing &&
- vertical && {
- border: '1px solid transparent',
- }),
-
- ...(iconOnly && {
+ },
+ ],
+ [
+ { inSubmenu: true },
+ {
+ [`& .${MenuItem.slotClassNames.indicator}`]: {
+ position: 'absolute',
+ top: pxToRem(6),
+ right: pxToRem(2),
+ left: 'unset',
+ },
+ },
+ ],
+ [
+ [{ inSubmenu: true }, { vertical: true }],
+ {
+ [`& .${MenuItem.slotClassNames.content}`]: {
+ width: 'max-content',
+ marginRight: pxToRem(16),
+ },
+ },
+ ],
+ [
+ [{ inSubmenu: true, icon: true, menu: true }, { vertical: true, icon: true, menu: true }],
+ {
+ [`& .${MenuItem.slotClassNames.content}`]: {
+ minWidth: pxToRem(46 - 26 - 16),
+ maxWidth: pxToRem(262 - 26 - 16),
+ },
+ },
+ ],
+ [
+ [{ inSubmenu: true, icon: true, menu: false }, { vertical: true, icon: true, menu: false }],
+ {
+ [`& .${MenuItem.slotClassNames.content}`]: {
+ minWidth: pxToRem(46 - 26),
+ maxWidth: pxToRem(262 - 26),
+ },
+ },
+ ],
+ [
+ [{ inSubmenu: true, icon: false, menu: true }, { vertical: true, icon: false, menu: true }],
+ {
+ minWidth: pxToRem(46 - 16),
+ maxWidth: pxToRem(262 - 16),
+ },
+ ],
+ [
+ [{ inSubmenu: true, icon: false, menu: false }, { vertical: true, icon: false, menu: false }],
+ {
+ [`& .${MenuItem.slotClassNames.content}`]: {
+ minWidth: pxToRem(46),
+ maxWidth: pxToRem(262),
+ },
+ },
+ ],
+ [
+ [
+ { pointing: 'start', vertical: true },
+ { pointing: 'end', vertical: true },
+ { pointing: true, vertical: true },
+ ],
+ {
+ border: '1px solid transparent',
+ },
+ ],
+ [
+ { iconOnly: true },
+ {
border: `${pxToRem(2)} solid transparent`,
- }),
-
- ...(underlined
- ? { padding: `${pxToRem(4)} 0` }
- : pointing && vertical
- ? { padding: `${pxToRem(8)} ${pxToRem(18)}` }
- : vertical
- ? { padding: v.verticalItemPadding }
- : {
- padding: v.horizontalPadding,
- }),
-
- ...(iconOnly && {
margin: pxToRem(1),
padding: pxToRem(5), // padding works this way to get the border to only be 30x30px on focus which is the current design
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
- }),
-
- // active styles
- ...(active && {
- ...(iconOnly && {
- color: v.iconOnlyColorActive,
- ...getIconFillOrOutlineStyles({ outline: false }),
- }),
-
- ...(primary
- ? {
- ...(underlined && {
- color: colors.borderActive,
- ...underlinedItem(v.borderColorActive || colors.borderActive),
- }),
- }
- : underlined && {
- fontWeight: 700,
- ...underlinedItem(v.colorActive),
- }),
- }),
-
- // focus styles
- ...(isFromKeyboard && {
- ...(iconOnly && {
- borderRadius: '50%',
- borderColor: v.iconOnlyColorActive,
+ },
+ ],
+ [
+ { iconOnly: true, disabled: false },
+ {
+ ':hover': {
...getIconFillOrOutlineStyles({ outline: false }),
- }),
-
- ...(primary
- ? {
- ...(iconOnly && {
- color: 'inherit',
- borderColor: v.borderColorActive || colors.borderActive,
- }),
-
- ...(underlined && { color: 'inherit' }),
-
- ...(underlined && active && underlinedItem(colors.foregroundActive)),
- }
- : {
- ...(underlined && { fontWeight: 700 }),
-
- ...(underlined && active && underlinedItem(v.colorActive)),
- }),
- }),
-
- ':focus': {
- outline: 0,
+ },
},
-
- // hover styles
- ':hover': {
+ ],
+ [
+ { underlined: true },
+ {
+ padding: `${pxToRem(4)} 0`,
+ },
+ ],
+ [
+ [
+ { underlined: false, pointing: 'start', vertical: true },
+ { underlined: false, pointing: 'end', vertical: true },
+ { underlined: false, pointing: true, vertical: true },
+ ],
+ {
+ padding: `${pxToRem(8)} ${pxToRem(18)}`,
+ },
+ ],
+ [
+ { underlined: false, pointing: false, vertical: true, iconOnly: false },
+ {
+ padding: v.verticalItemPadding,
+ },
+ ],
+ [
+ { underlined: false, vertical: false, iconOnly: false },
+ {
+ padding: v.horizontalPadding,
+ },
+ ],
+ [
+ { active: true, iconOnly: true },
+ {
+ color: v.iconOnlyColorActive,
+ ...getIconFillOrOutlineStyles({ outline: false }),
+ },
+ ],
+ [
+ { active: true, primary: true, underlined: true },
+ {
+ color: v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.borderActive,
+ ...underlinedItem(
+ v.borderColorActive ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.borderActive),
+ ),
+ },
+ ],
+ [
+ { active: true, primary: false, underlined: true },
+ {
+ fontWeight: 700,
+ ...underlinedItem(v.colorActive),
+ },
+ ],
+ [
+ { primary: true, active: false, underlined: true, disabled: false },
+ {
+ ':hover': {
+ ...underlinedItem(
+ v.underlinedBorderColor ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.backgroundActive),
+ ),
+ },
+ },
+ ],
+ [
+ { primary: false, active: false, underlined: true, disabled: false },
+ {
+ ':hover': {
+ ...underlinedItem(
+ v.backgroundColorActive ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.backgroundActive),
+ ),
+ },
+ },
+ ],
+ [
+ { isFromKeyboard: true, iconOnly: true },
+ {
+ borderRadius: '50%',
+ borderColor: v.iconOnlyColorActive,
+ ...getIconFillOrOutlineStyles({ outline: false }),
+ },
+ ],
+ [
+ { isFromKeyboard: true, primary: true, iconOnly: true },
+ {
color: 'inherit',
-
- ...(iconOnly && getIconFillOrOutlineStyles({ outline: false })),
-
- ...(primary
- ? {
- ...(iconOnly && { color: 'inherit' }),
- ...(!active &&
- underlined &&
- underlinedItem(v.underlinedBorderColor || colors.backgroundActive)),
- }
- : !active &&
- underlined &&
- underlinedItem(v.backgroundColorActive || colors.backgroundActive)),
+ borderColor:
+ v.borderColorActive ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.borderActive),
},
-
- ...(disabled && {
- cursor: 'default',
+ ],
+ [
+ { isFromKeyboard: true, primary: true, underlined: true },
+ {
+ color: 'inherit',
+ },
+ ],
+ [
+ { isFromKeyboard: true, primary: true, underlined: true, active: true },
+ {
+ ...underlinedItem(
+ v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.foregroundActive,
+ ),
+ },
+ ],
+ [
+ { isFromKeyboard: true, primary: false, underlined: true },
+ {
+ fontWeight: 700,
+ },
+ ],
+ [
+ { isFromKeyboard: true, primary: false, underlined: true, active: true },
+ {
+ ...underlinedItem(v.colorActive),
+ },
+ ],
+ [
+ { primary: true, active: false, underlined: true, disabled: false },
+ {
':hover': {
- // reset all existing hover styles
- color: 'inherit',
+ ...underlinedItem(
+ v.underlinedBorderColor ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.backgroundActive),
+ ),
},
- }),
- }
- },
-
- content: ({ props: p }): ICSSInJSStyle => {
- const widthAdjust = (p.icon ? 26 : 0) + (p.menu ? 16 : 0)
- return {
- whiteSpace: 'normal',
- lineHeight: 1.5,
- marginTop: pxToRem(-4),
- marginBottom: pxToRem(-4),
- display: 'inline-block',
- ...((p.inSubmenu || p.vertical) && {
- width: 'max-content',
- minWidth: pxToRem(46 - widthAdjust),
- maxWidth: pxToRem(262 - widthAdjust),
- marginRight: pxToRem(16),
- }),
- }
- },
-
- icon: ({ props: p }): ICSSInJSStyle => ({
- ...(!p.iconOnly && {
- // reduce margins so text has the dominant influence on the vertical height
- marginTop: 0,
- marginBottom: pxToRem(-8),
- verticalAlign: 'top',
- }),
- }),
-
- menu: () => ({ zIndex: 1000 }),
-
- indicator: ({ props: p }) => ({
- position: 'relative',
- float: 'right',
- left: pxToRem(12),
- userSelect: 'none',
- marginRight: pxToRem(4),
-
- ...(p.inSubmenu && {
- position: 'absolute',
- top: pxToRem(6),
- right: pxToRem(2),
- left: 'unset',
- }),
- }),
-}
+ },
+ ],
+ [
+ { primary: false, active: false, underlined: true, disabled: false },
+ {
+ ':hover': {
+ ...underlinedItem(
+ v.backgroundColorActive ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.backgroundActive),
+ ),
+ },
+ },
+ ],
+ [
+ { disabled: true },
+ {
+ cursor: 'default',
+ },
+ ],
+ ],
+})
export default menuItemStyles
diff --git a/packages/react/src/themes/teams/components/Menu/menuItemWrapperStyles.ts b/packages/react/src/themes/teams/components/Menu/menuItemWrapperStyles.ts
new file mode 100644
index 0000000000..384d13fa34
--- /dev/null
+++ b/packages/react/src/themes/teams/components/Menu/menuItemWrapperStyles.ts
@@ -0,0 +1,327 @@
+import { pxToRem } from '../../../../lib'
+import { ICSSInJSStyle, ComponentSelectorsAndStyles } from '../../../types'
+import { MenuVariables } from './menuVariables'
+import { MenuItemWrapperProps } from '../../../../components/Menu/MenuItemWrapper'
+
+export const underlinedItem = (color: string): ICSSInJSStyle => ({
+ paddingBottom: 0,
+ borderBottom: `solid ${pxToRem(4)} ${color}`,
+ transition: 'color .1s ease',
+})
+
+const menuItemWrapperStyles: ComponentSelectorsAndStyles<
+ MenuItemWrapperProps,
+ MenuVariables
+> = v => ({
+ root: [
+ [
+ null,
+ {
+ color: 'inherit',
+ lineHeight: 1,
+ position: 'relative',
+ verticalAlign: 'middle',
+ display: 'block',
+ },
+ ],
+ [{ secondary: true }, { background: 'salmon' }],
+ [
+ { vertical: true },
+ {
+ border: `solid ${v.verticalItemBorderWidth} ${v.verticalItemBorderColor}`,
+ },
+ ],
+ [{ pills: true }, { borderRadius: pxToRem(5) }],
+ [{ pills: true, vertical: true }, { margin: `0 0 ${v.verticalPillsBottomMargin} 0` }],
+ [{ pills: true, vertical: false }, { margin: `0 ${v.horizontalPillsRightMargin} 0 0` }],
+ [
+ { underlined: true },
+ {
+ display: 'flex',
+ alignItems: 'center',
+ height: pxToRem(29),
+ lineHeight: v.lineHeightBase,
+ padding: `0 ${pxToRem(4)}`,
+ margin: `0 ${pxToRem(4)} 0 0`,
+ ':nth-child(n+2)': {
+ marginLeft: `${pxToRem(4)}`,
+ },
+ boxShadow: 'none',
+ },
+ ],
+ [
+ { vertical: false, pills: false, underlined: false, iconOnly: false, primary: true },
+ {
+ boxShadow: `-1px 0 0 0 ${v.primaryBorderColor ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.border)} inset`,
+ },
+ ],
+ [
+ { vertical: false, pills: false, underlined: false, iconOnly: false, primary: false },
+ {
+ boxShadow: `-1px 0 0 0 ${v.borderColor ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.border)} inset`,
+ },
+ ],
+ [
+ [{ active: true, underlined: true }, { active: true, iconOnly: true }],
+ {
+ color: v.color,
+ },
+ ],
+ [
+ { active: true, underlined: false, iconOnly: false, primary: true },
+ {
+ color: v.colorScheme && v.colorScheme.default && v.colorScheme.brand.foregroundActive,
+ background:
+ v.backgroundColorActive ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.backgroundActive),
+ },
+ ],
+ [
+ { active: true, underlined: false, iconOnly: false, primary: false },
+ {
+ color: v.color,
+ background:
+ v.backgroundColorActive ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.backgroundActive),
+ },
+ ],
+ [
+ [
+ { active: true, pointing: 'start', vertical: true, isFromKeyboard: false },
+ { active: true, pointing: 'end', vertical: true, isFromKeyboard: false },
+ { active: true, pointing: true, vertical: true, isFromKeyboard: false },
+ ],
+ {
+ '::before': {
+ content: `''`,
+ position: 'absolute',
+ width: pxToRem(3),
+ height: `calc(100% + ${pxToRem(4)})`,
+ top: pxToRem(-2),
+ backgroundColor: v.pointingIndicatorBackgroundColor,
+ },
+ },
+ ],
+ [
+ { active: true, vertical: true, isFromKeyboard: false, pointing: 'end' },
+ {
+ '::before': {
+ right: pxToRem(-2),
+ },
+ },
+ ],
+ [
+ [
+ { active: true, vertical: true, isFromKeyboard: false, pointing: 'start' },
+ { active: true, vertical: true, isFromKeyboard: false, pointing: true },
+ ],
+ {
+ '::before': {
+ left: pxToRem(-2),
+ },
+ },
+ ],
+ [
+ [
+ { active: true, pointing: 'start', vertical: false },
+ { active: true, pointing: 'end', vertical: false },
+ { active: true, pointing: true, vertical: false },
+ ],
+ {
+ '::after': {
+ visibility: 'visible',
+ position: 'absolute',
+ content: '""',
+ left: '50%',
+ transform: 'translateX(-50%) translateY(-50%) rotate(45deg)',
+ margin: '.5px 0 0',
+ width: pxToRem(10),
+ height: pxToRem(10),
+ border: 'none',
+ zIndex: 2,
+ transition: 'background .1s ease',
+ },
+ },
+ ],
+ [
+ [{ active: true, pointing: 'start', vertical: false, primary: true }],
+ {
+ '::after': {
+ top: '-1px',
+ background:
+ v.backgroundColorActive ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.backgroundActive),
+ borderTop: `1px solid ${v.borderColor || v.primaryBorderColor}`,
+ borderLeft: `1px solid ${v.borderColor || v.primaryBorderColor}`,
+ },
+ },
+ ],
+ [
+ [
+ { active: true, pointing: 'start', vertical: false, primary: false },
+ { active: true, pointing: true, vertical: false, primary: false },
+ ],
+ {
+ '::after': {
+ top: '-1px',
+ background:
+ v.backgroundColorActive ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.backgroundActive),
+ borderTop: `1px solid ${v.borderColor ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.border)}`,
+ borderLeft: `1px solid ${v.borderColor ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.border)}`,
+ },
+ },
+ ],
+ [
+ { active: true, pointing: 'end', vertical: false, primary: true },
+ {
+ '::after': {
+ top: '100%',
+ background:
+ v.backgroundColorActive ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.backgroundActive),
+ borderBottom: `1px solid ${v.borderColor || v.primaryBorderColor}`,
+ borderRight: `1px solid ${v.borderColor || v.primaryBorderColor}`,
+ },
+ },
+ ],
+ [
+ { active: true, pointing: 'end', vertical: false, primary: false },
+ {
+ '::after': {
+ top: '100%',
+ background:
+ v.backgroundColorActive ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.backgroundActive),
+ borderBottom: `1px solid ${v.borderColor ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.border)}`,
+ borderRight: `1px solid ${v.borderColor ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.border)}`,
+ },
+ },
+ ],
+ [
+ { iconOnly: true, disabled: false },
+ {
+ display: 'flex',
+ ':hover': {
+ color: v.iconOnlyColorActive,
+ },
+ },
+ ],
+ [
+ { iconOnly: true, isFromKeyboard: true },
+ {
+ color: v.iconOnlyColorActive,
+ },
+ ],
+ [
+ [
+ { iconOnly: false, isFromKeyboard: true, primary: true, active: false },
+ { iconOnly: false, isFromKeyboard: true, primary: true, underlined: true },
+ { iconOnly: false, isFromKeyboard: true, primary: true, vertical: true },
+ ],
+ {
+ color: v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.foregroundFocus,
+ background:
+ v.backgroundColorFocus ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.backgroundFocus),
+ },
+ ],
+ [
+ [
+ { iconOnly: false, isFromKeyboard: true, primary: false, active: false },
+ { iconOnly: false, isFromKeyboard: true, primary: false, underlined: true },
+ { iconOnly: false, isFromKeyboard: true, primary: false, vertical: true },
+ ],
+ {
+ color: v.colorActive,
+ background:
+ v.backgroundColorFocus ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.backgroundFocus),
+ },
+ ],
+ [
+ { iconOnly: false, isFromKeyboard: true, vertical: true, primary: false },
+ {
+ border: `solid 1px ${v.borderColorFocus}`,
+ outline: `solid 1px ${v.outlineColorFocus}`,
+ margin: pxToRem(1),
+ background:
+ v.verticalBackgroundColorFocus ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.backgroundFocus),
+ },
+ ],
+ [
+ { iconOnly: false, underlined: true, disabled: false },
+ {
+ ':hover': {
+ color: v.colorActive,
+ },
+ },
+ ],
+ [
+ { iconOnly: false, underlined: false, primary: true, active: false, disabled: false },
+ {
+ ':hover': {
+ color: v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.foregroundHover,
+ background:
+ v.backgroundColorHover ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.backgroundHover),
+ },
+ },
+ ],
+ [
+ { iconOnly: false, underlined: false, primary: false, active: false, disabled: false },
+ {
+ ':hover': {
+ color: v.colorScheme && v.colorScheme.default && v.colorScheme.default.foregroundHover,
+ background:
+ v.backgroundColorHover ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.backgroundHover),
+ },
+ },
+ ],
+ [
+ { pills: false, iconOnly: false, underlined: false, pointing: false, vertical: true },
+ {
+ ':first-child': {
+ '::before': {
+ display: 'none',
+ },
+ },
+ },
+ ],
+ [
+ { pills: false, iconOnly: false, underlined: false, pointing: false, vertical: false },
+ {
+ ':first-child': {
+ borderBottomLeftRadius: pxToRem(3),
+ borderTopLeftRadius: pxToRem(3),
+ },
+ },
+ ],
+ [
+ { disabled: true, primary: false },
+ {
+ color:
+ v.colorDisabled ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.foregroundDisabled),
+ },
+ ],
+ [
+ { disabled: true, primary: true },
+ {
+ color:
+ v.colorDisabled ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.foregroundDisabled),
+ },
+ ],
+ ],
+})
+
+export default menuItemWrapperStyles
diff --git a/packages/react/src/themes/teams/components/Menu/menuItemWrapperVariables.ts b/packages/react/src/themes/teams/components/Menu/menuItemWrapperVariables.ts
new file mode 100644
index 0000000000..45031f722b
--- /dev/null
+++ b/packages/react/src/themes/teams/components/Menu/menuItemWrapperVariables.ts
@@ -0,0 +1,3 @@
+import menuVariables from './menuVariables'
+
+export default menuVariables
diff --git a/packages/react/src/themes/teams/components/Menu/menuStyles.ts b/packages/react/src/themes/teams/components/Menu/menuStyles.ts
index 3e3a0e0ce1..a905a3fb94 100644
--- a/packages/react/src/themes/teams/components/Menu/menuStyles.ts
+++ b/packages/react/src/themes/teams/components/Menu/menuStyles.ts
@@ -1,67 +1,101 @@
import { pxToRem } from '../../../../lib'
-import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '../../../types'
+import { ComponentSelectorsAndStyles } from '../../../types'
import { MenuProps, MenuState } from '../../../../components/Menu/Menu'
import { MenuVariables } from './menuVariables'
-import {
- verticalPillsBottomMargin,
- horizontalPillsRightMargin,
- verticalPointingBottomMargin,
-} from './menuItemStyles'
-import { getColorScheme } from '../../colors'
+// import { backportComponentStyle } from '../../../../lib/resolveComponentRules'
type MenuPropsAndState = MenuProps & MenuState
-export default {
- root: ({ props: p, variables: v }): ICSSInJSStyle => {
- const { iconOnly, fluid, pointing, pills, primary, underlined, vertical, submenu } = p
- const colors = getColorScheme(v.colorScheme, null, primary)
-
- return {
- display: 'flex',
- minHeight: pxToRem(24),
- margin: 0,
- padding: 0,
- color: v.color,
- backgroundColor: v.backgroundColor || 'inherit',
- listStyleType: 'none',
- ...(iconOnly && { alignItems: 'center' }),
- ...(vertical && {
- flexDirection: 'column',
- backgroundColor: v.verticalBackgroundColor,
- padding: `${pxToRem(8)} 0`,
- ...(submenu && {
+const menuStyles: ComponentSelectorsAndStyles = v => {
+ return {
+ root: [
+ [
+ null,
+ {
+ display: 'flex',
+ minHeight: pxToRem(24),
+ margin: 0,
+ padding: 0,
+ color: v.color,
+ backgroundColor: v.backgroundColor || 'inherit',
+ listStyleType: 'none',
+ },
+ ],
+ [{ iconOnly: true }, { alignItems: 'center' }],
+ [
+ { vertical: true },
+ {
+ flexDirection: 'column',
+ backgroundColor: v.verticalBackgroundColor,
+ padding: `${pxToRem(8)} 0`,
+ },
+ ],
+ [
+ { vertical: true, submenu: true },
+ {
boxShadow: v.verticalBoxShadow,
- }),
- ...(!fluid && !submenu && { width: 'fit-content' }),
- ...(iconOnly && {
+ },
+ ],
+ [
+ { vertical: true, iconOnly: true },
+ {
display: 'inline-block',
width: 'auto',
- }),
- }),
- ...(!pills &&
- !iconOnly &&
- !(pointing && vertical) &&
- !underlined && {
- // primary has hardcoded grey border color
- border: `${v.borderWidth} solid ${
- primary ? v.primaryBorderColor : v.borderColor || colors.border
- }`,
+ },
+ ],
+ [{ vertical: true, fluid: false, submenu: false }, { width: 'fit-content' }],
+ [
+ [
+ { pills: false, iconOnly: false, underlined: false, pointing: false, primary: true },
+ { pills: false, iconOnly: false, underlined: false, vertical: false, primary: true },
+ ],
+ {
+ border: `${v.borderWidth} solid ${v.primaryBorderColor ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.border)}`,
+ borderRadius: pxToRem(4),
+ },
+ ],
+ [
+ [
+ { pills: false, iconOnly: false, underlined: false, pointing: false, primary: true },
+ { pills: false, iconOnly: false, underlined: false, vertical: false, primary: true },
+ ],
+ {
+ border: `${v.borderWidth} solid ${v.primaryBorderColor ||
+ (v.colorScheme && v.colorScheme.brand && v.colorScheme.brand.border)}`,
borderRadius: pxToRem(4),
- }),
- ...(underlined && {
- borderBottom: `${v.underlinedBottomBorderWidth} solid ${v.underlinedBorderColor}`,
- }),
- }
- },
- divider: ({ props: { pointing, vertical, pills } }) => ({
- ...(pointing &&
- vertical && {
- marginBottom: verticalPointingBottomMargin,
- }),
- ...(pills && {
- ...(vertical
- ? { margin: `0 0 ${verticalPillsBottomMargin} 0` }
- : { margin: `0 ${horizontalPillsRightMargin} 0 0` }),
- }),
- }),
-} as ComponentSlotStylesPrepared
+ },
+ ],
+ [
+ [
+ { pills: false, iconOnly: false, underlined: false, pointing: false, primary: false },
+ { pills: false, iconOnly: false, underlined: false, vertical: false, primary: false },
+ ],
+ {
+ border: `${v.borderWidth} solid ${v.borderColor ||
+ (v.colorScheme && v.colorScheme.default && v.colorScheme.default.border)}`,
+ borderRadius: pxToRem(4),
+ },
+ ],
+ [
+ { underlined: true },
+ {
+ borderBottom: `${v.underlinedBottomBorderWidth} solid ${v.underlinedBorderColor}`,
+ },
+ ],
+ ],
+ divider: [
+ [
+ { pointing: true, vertical: true },
+ {
+ marginBottom: v.verticalPointingBottomMargin,
+ },
+ ],
+ [{ pills: true, vertical: true }, { margin: `0 0 ${v.verticalPillsBottomMargin} 0` }],
+ [{ pills: true, vertical: false }, { margin: `0 ${v.horizontalPillsRightMargin} 0 0` }],
+ ],
+ }
+}
+
+export default menuStyles
+// export default backportComponentStyle(menuStyles)
diff --git a/packages/react/src/themes/teams/components/Menu/menuVariables.ts b/packages/react/src/themes/teams/components/Menu/menuVariables.ts
index 0ddfa87f1f..cd439929b8 100644
--- a/packages/react/src/themes/teams/components/Menu/menuVariables.ts
+++ b/packages/react/src/themes/teams/components/Menu/menuVariables.ts
@@ -57,6 +57,10 @@ export interface MenuVariables {
dividerHeight: string
borderWidth: string
+
+ verticalPillsBottomMargin: string
+ horizontalPillsRightMargin: string
+ verticalPointingBottomMargin: string
}
export default (siteVars: any): MenuVariables => {
@@ -122,5 +126,9 @@ export default (siteVars: any): MenuVariables => {
dividerHeight: pxToRem(1),
borderWidth: pxToRem(1),
+
+ verticalPillsBottomMargin: pxToRem(5),
+ horizontalPillsRightMargin: pxToRem(8),
+ verticalPointingBottomMargin: pxToRem(12),
}
}
diff --git a/packages/react/src/themes/teams/components/Slider/sliderStyles.ts b/packages/react/src/themes/teams/components/Slider/sliderStyles.ts
index d9637d0db7..58a9b12e1c 100644
--- a/packages/react/src/themes/teams/components/Slider/sliderStyles.ts
+++ b/packages/react/src/themes/teams/components/Slider/sliderStyles.ts
@@ -1,8 +1,9 @@
import * as React from 'react'
import { SliderVariables } from './sliderVariables'
-import Slider, { SliderProps, SliderState } from '../../../../components/Slider/Slider'
-import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '../../../types'
+import Slider, { SliderProps } from '../../../../components/Slider/Slider'
+import { ComponentSelectorsAndStyles, ICSSInJSStyle } from '../../../types'
import getBorderFocusStyles from '../../getBorderFocusStyles'
+import { backportComponentStyle } from '../../../../lib/resolveComponentRules'
const selectors = {
WEBKIT_THUMB: '::-webkit-slider-thumb',
@@ -12,7 +13,7 @@ const selectors = {
MS_THUMB: '::-ms-thumb',
}
-const getCommonSlotStyles = (p: SliderProps, v: SliderVariables): ICSSInJSStyle => ({
+const getCommonSlotStyles = (v: SliderVariables): ICSSInJSStyle => ({
cursor: 'pointer',
pointerEvents: 'none',
position: 'absolute',
@@ -24,110 +25,134 @@ const getCommonSlotStyles = (p: SliderProps, v: SliderVariables): ICSSInJSStyle
// this selector is used to identify the thumb slot from a previous sibling
const thumbFromPreviousSiblingSelector = `&+ .${Slider.slotClassNames.thumb}`
-const getFluidStyles = (p: SliderProps) => p.fluid && !p.vertical && { width: '100%' }
-
-const sliderStyles: ComponentSlotStylesPrepared = {
- root: ({ props: p, variables: v }): ICSSInJSStyle => ({
- height: v.height,
-
- ...(p.disabled && { pointerEvents: 'none' }),
- ...(p.vertical && { height: v.length, width: v.height }),
- ...getFluidStyles(p),
- }),
-
- input: ({ props: p, variables: v, theme: { siteVariables } }): ICSSInJSStyle => {
- const activeThumbStyles: React.CSSProperties = {
- height: v.activeThumbHeight,
- width: v.activeThumbWidth,
- background: v.activeThumbColor,
- marginTop: `calc(${v.height} / 2 - ${v.activeThumbHeight} / 2)`,
- marginLeft: `calc(-${v.activeThumbWidth} / 2)`,
- }
- const borderFocusStyles = getBorderFocusStyles({
- siteVariables,
- borderPadding: v.thumbBorderPadding,
- })
- const thumbStyles = { border: 0, width: '1px' }
-
- return {
- '-webkit-appearance': 'none',
- cursor: 'pointer',
- height: '100%',
- width: '100%',
- margin: 0,
- padding: 0,
- opacity: 0,
-
- [selectors.WEBKIT_THUMB]: { ...thumbStyles, '-webkit-appearance': 'none' },
- [selectors.MOZ_THUMB]: thumbStyles,
- [selectors.MS_THUMB]: { ...thumbStyles, marginTop: `calc(-${v.thumbHeight} / 2)` },
-
- [selectors.MS_FILL_LOWER]: { display: 'none' },
- [selectors.MS_FILL_UPPER]: { display: 'none' },
-
- ...getFluidStyles(p),
-
- ':active': { [thumbFromPreviousSiblingSelector]: activeThumbStyles },
-
- ':focus': {
- outline: 0, // TODO: check if this is correct
- [thumbFromPreviousSiblingSelector]: borderFocusStyles[':focus'],
- },
- ':focus-visible': {
- [thumbFromPreviousSiblingSelector]: {
- ...borderFocusStyles[':focus-visible'],
- ...activeThumbStyles,
+const sliderStyles: ComponentSelectorsAndStyles = v => {
+ const thumbStyles = { border: 0, width: '1px' }
+
+ const activeThumbStyles: React.CSSProperties = {
+ height: v.activeThumbHeight,
+ width: v.activeThumbWidth,
+ background: v.activeThumbColor,
+ marginTop: `calc(${v.height} / 2 - ${v.activeThumbHeight} / 2)`,
+ marginLeft: `calc(-${v.activeThumbWidth} / 2)`,
+ }
+
+ const transformOriginValue = `calc(${v.length} / 2)`
+
+ const borderFocusStyles = getBorderFocusStyles({
+ siteVariables: {
+ borderWidth: v.focusBorderWidth,
+ borderRadius: v.focusBorderRadius,
+ focusInnerBorderColor: v.focusInnerBorderColor,
+ focusOuterBorderColor: v.focusOuterBorderColor,
+ },
+ borderPadding: v.thumbBorderPadding,
+ })
+
+ return {
+ root: [
+ [null, { height: v.height }],
+ [{ disabled: true }, { pointerEvents: 'none' }],
+ [{ vertical: true }, { height: v.length, width: v.height }],
+ [{ fluid: true, vertical: false }, { width: '100%' }],
+ ],
+
+ input: [
+ [
+ null,
+ {
+ '-webkit-appearance': 'none',
+ cursor: 'pointer',
+ height: '100%',
+ width: '100%',
+ margin: 0,
+ padding: 0,
+ opacity: 0,
+
+ [selectors.WEBKIT_THUMB]: { ...thumbStyles, '-webkit-appearance': 'none' },
+ [selectors.MOZ_THUMB]: thumbStyles,
+ [selectors.MS_THUMB]: { ...thumbStyles, marginTop: `calc(-${v.thumbHeight} / 2)` },
+
+ [selectors.MS_FILL_LOWER]: { display: 'none' },
+ [selectors.MS_FILL_UPPER]: { display: 'none' },
+ ':active': { [thumbFromPreviousSiblingSelector]: activeThumbStyles },
+ ':focus': {
+ outline: 0,
+ [thumbFromPreviousSiblingSelector]: borderFocusStyles[':focus'],
+ },
+ ':focus-visible': {
+ [thumbFromPreviousSiblingSelector]: {
+ ...borderFocusStyles[':focus-visible'],
+ ...activeThumbStyles,
+ },
+ },
},
- },
- }
- },
-
- inputWrapper: ({ props: p, variables: v }) => {
- const transformOriginValue = `calc(${v.length} / 2)`
-
- return {
- position: 'relative',
- display: 'inline-block',
- height: v.height,
- width: v.length,
- ...(p.vertical && {
- transform: 'rotate(-90deg)',
- transformOrigin: `${transformOriginValue} ${transformOriginValue}`,
- }),
- ...getFluidStyles(p),
- }
- },
-
- rail: ({ props: p, variables: v }) => ({
- width: '100%',
- background: v.railColor,
-
- ...getCommonSlotStyles(p, v),
- ...(p.disabled && { background: v.disabledRailColor }),
- }),
-
- track: ({ props: p, variables: v }) => ({
- background: v.trackColor,
-
- ...getCommonSlotStyles(p, v),
- ...(p.disabled && { background: v.disabledTrackColor }),
- }),
-
- thumb: ({ props: p, variables: v }) => ({
- border: 0,
- borderRadius: '100%',
- cursor: 'pointer',
- pointerEvents: 'none',
- position: 'absolute',
-
- background: v.thumbColor,
- height: v.thumbHeight,
- width: v.thumbWidth,
- marginTop: `calc(${v.height} / 2 - ${v.thumbHeight} / 2)`,
- marginLeft: `calc(-${v.thumbWidth} / 2)`,
-
- ...(p.disabled && { background: v.disabledThumbColor }),
- }),
+ ],
+ [{ fluid: true, vertical: false }, { width: '100%' }],
+ ],
+
+ inputWrapper: [
+ [
+ null,
+ {
+ position: 'relative',
+ display: 'inline-block',
+ height: v.height,
+ width: v.length,
+ },
+ ],
+ [
+ { vertical: true },
+ {
+ transform: 'rotate(-90deg)',
+ transformOrigin: `${transformOriginValue} ${transformOriginValue}`,
+ },
+ ],
+ [{ fluid: true, vertical: false }, { width: '100%' }],
+ ],
+
+ rail: [
+ [
+ null,
+ {
+ width: '100%',
+ background: v.railColor,
+ ...getCommonSlotStyles(v),
+ },
+ ],
+ [{ disabled: true }, { background: v.disabledRailColor }],
+ ],
+
+ track: [
+ [
+ null,
+ {
+ background: v.trackColor,
+ ...getCommonSlotStyles(v),
+ },
+ ],
+ [{ disabled: true }, { background: v.disabledTrackColor }],
+ ],
+
+ thumb: [
+ [
+ null,
+ {
+ border: 0,
+ borderRadius: '100%',
+ cursor: 'pointer',
+ pointerEvents: 'none',
+ position: 'absolute',
+
+ background: v.thumbColor,
+ height: v.thumbHeight,
+ width: v.thumbWidth,
+ marginTop: `calc(${v.height} / 2 - ${v.thumbHeight} / 2)`,
+ marginLeft: `calc(-${v.thumbWidth} / 2)`,
+ },
+ ],
+ [{ disabled: true }, { background: v.disabledThumbColor }],
+ ],
+ }
}
-export default sliderStyles
+export default backportComponentStyle(sliderStyles)
diff --git a/packages/react/src/themes/teams/components/Slider/sliderVariables.ts b/packages/react/src/themes/teams/components/Slider/sliderVariables.ts
index 43d880df98..22d61848b8 100644
--- a/packages/react/src/themes/teams/components/Slider/sliderVariables.ts
+++ b/packages/react/src/themes/teams/components/Slider/sliderVariables.ts
@@ -20,6 +20,11 @@ export interface SliderVariables {
trackColor: string
disabledTrackColor: string
+
+ focusBorderWidth: string
+ focusBorderRadius: string
+ focusInnerBorderColor: string
+ focusOuterBorderColor: string
}
export default (siteVars: SiteVariablesPrepared): SliderVariables => {
const { colorScheme } = siteVars
@@ -43,5 +48,10 @@ export default (siteVars: SiteVariablesPrepared): SliderVariables => {
trackColor: colorScheme.brand.foregroundActive,
disabledTrackColor: colorScheme.default.foregroundDisabled1,
+
+ focusBorderWidth: siteVars.borderWidth,
+ focusBorderRadius: siteVars.borderRadius,
+ focusInnerBorderColor: siteVars.focusInnerBorderColor,
+ focusOuterBorderColor: siteVars.focusOuterBorderColor,
}
}
diff --git a/packages/react/src/themes/teams/components/Status/statusStyles.ts b/packages/react/src/themes/teams/components/Status/statusStyles.ts
index 4dc83435a7..c253c93ab2 100644
--- a/packages/react/src/themes/teams/components/Status/statusStyles.ts
+++ b/packages/react/src/themes/teams/components/Status/statusStyles.ts
@@ -1,81 +1,54 @@
-import { pxToRem, SizeValue } from '../../../../lib'
-import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '../../../types'
import { StatusProps } from '../../../../components/Status/Status'
import { StatusVariables } from './statusVariables'
-
-const getBackgroundColor = (state: string, variables: StatusVariables) => {
- switch (state) {
- case 'success':
- return variables.successBackgroundColor
- case 'info':
- return variables.infoBackgroundColor
- case 'warning':
- return variables.warningBackgroundColor
- case 'error':
- return variables.errorBackgroundColor
- case 'unknown':
- default:
- return variables.defaultBackgroundColor
- }
-}
-
-const getTextColor = (state: string, variables: StatusVariables) => {
- switch (state) {
- case 'success':
- return variables.successTextColor
- case 'info':
- return variables.infoTextColor
- case 'warning':
- return variables.warningTextColor
- case 'error':
- return variables.errorTextColor
- case 'unknown':
- default:
- return variables.defaultTextColor
- }
-}
-
-const sizeToPxValue: Record = {
- smallest: 8,
- smaller: 8,
- small: 8,
- medium: 10,
- large: 12,
- larger: 14,
- largest: 16,
-}
-
-export const getSizeStyles = (sizeInPx: number, variables: StatusVariables) => {
- const borderWidth = (variables.borderColor && variables.borderWidth) || 0
- const sizeInRem = pxToRem(sizeInPx + 2 * borderWidth)
-
- return {
- height: sizeInRem,
- width: sizeInRem,
- }
-}
-
-const statusStyles: ComponentSlotStylesPrepared = {
- root: ({ props: { color, size, state }, variables }): ICSSInJSStyle => {
- return {
- display: 'inline-flex',
- alignItems: 'center',
- justifyContent: 'center',
- ...getSizeStyles(sizeToPxValue[size], variables),
- verticalAlign: 'middle',
- borderRadius: '9999px',
- ...(variables.borderColor && {
- borderColor: variables.borderColor,
- borderWidth: pxToRem(variables.borderWidth),
- borderStyle: 'solid',
- }),
- backgroundColor: color || getBackgroundColor(state, variables),
- }
- },
-
- icon: ({ props: { state }, variables }): ICSSInJSStyle => ({
- color: getTextColor(state, variables),
- }),
-}
-
-export default statusStyles
+import { ComponentSelectorsAndStyles } from '../../../types'
+import { backportComponentStyle } from '../../../../lib/resolveComponentRules'
+
+const statusStyles: ComponentSelectorsAndStyles = v => ({
+ root: [
+ [
+ null,
+ {
+ display: 'inline-flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ verticalAlign: 'middle',
+ width: v.medium,
+ height: v.medium,
+ color: v.defaultTextColor,
+ backgroundColor: v.defaultBackgroundColor,
+ borderRadius: '9999px',
+ },
+ ],
+
+ //
+ // States
+ //
+ [{ state: 'success' }, { backgroundColor: v.successBackgroundColor }],
+ [{ state: 'info' }, { backgroundColor: v.infoBackgroundColor }],
+ [{ state: 'warning' }, { backgroundColor: v.warningBackgroundColor }],
+ [{ state: 'error' }, { backgroundColor: v.errorBackgroundColor }],
+
+ //
+ // Sizes
+ //
+ [{ size: 'smallest' }, { width: v.smallest, height: v.smallest }],
+ [{ size: 'smaller' }, { width: v.smaller, height: v.smaller }],
+ [{ size: 'small' }, { width: v.small, height: v.small }],
+ [{ size: 'medium' }, { width: v.medium, height: v.medium }],
+ [{ size: 'large' }, { width: v.large, height: v.large }],
+ [{ size: 'larger' }, { width: v.larger, height: v.larger }],
+ [{ size: 'largest' }, { width: v.largest, height: v.largest }],
+ ],
+
+ // ----------------------------------------
+ // Icon
+ // ----------------------------------------
+ icon: [
+ [{ state: 'success' }, { color: v.successTextColor }],
+ [{ state: 'info' }, { color: v.infoTextColor }],
+ [{ state: 'warning' }, { color: v.warningTextColor }],
+ [{ state: 'error' }, { color: v.errorTextColor }],
+ ],
+})
+
+export default backportComponentStyle(statusStyles)
diff --git a/packages/react/src/themes/teams/components/Status/statusVariables.ts b/packages/react/src/themes/teams/components/Status/statusVariables.ts
index 9abb603dfe..a1bf013b71 100644
--- a/packages/react/src/themes/teams/components/Status/statusVariables.ts
+++ b/packages/react/src/themes/teams/components/Status/statusVariables.ts
@@ -1,6 +1,7 @@
+import { pxToRem } from '../../../../lib'
+import { SiteVariablesInput } from '../../../types'
+
export interface StatusVariables {
- borderColor: string
- borderWidth: number
successBackgroundColor: string
successTextColor: string
infoBackgroundColor: string
@@ -11,11 +12,20 @@ export interface StatusVariables {
errorTextColor: string
defaultBackgroundColor: string
defaultTextColor: string
+
+ borderColor: string
+ borderWidth: string
+
+ smallest: string
+ smaller: string
+ small: string
+ medium: string
+ large: string
+ larger: string
+ largest: string
}
-export default siteVariables => ({
- borderColor: undefined,
- borderWidth: 2,
+export default (siteVariables: SiteVariablesInput): StatusVariables => ({
successBackgroundColor: siteVariables.colors.green[200],
successTextColor: siteVariables.colors.white,
infoBackgroundColor: siteVariables.colors.brand[500],
@@ -26,4 +36,15 @@ export default siteVariables => ({
errorTextColor: siteVariables.colors.white,
defaultBackgroundColor: siteVariables.colors.grey[350],
defaultTextColor: siteVariables.colors.white,
+
+ borderColor: 'transparent',
+ borderWidth: '0',
+
+ smallest: pxToRem(8),
+ smaller: pxToRem(8),
+ small: pxToRem(8),
+ medium: pxToRem(10),
+ large: pxToRem(12),
+ larger: pxToRem(14),
+ largest: pxToRem(16),
})
diff --git a/packages/react/src/themes/teams/getBorderFocusStyles.ts b/packages/react/src/themes/teams/getBorderFocusStyles.ts
index 74bc5d605e..44dcae651e 100644
--- a/packages/react/src/themes/teams/getBorderFocusStyles.ts
+++ b/packages/react/src/themes/teams/getBorderFocusStyles.ts
@@ -1,11 +1,11 @@
import * as React from 'react'
-import { ICSSInJSStyle, SiteVariablesPrepared } from '../types'
+import { ICSSInJSStyle } from '../types'
type CSSBorderStyles = Pick
type BorderFocusStyles = CSSBorderStyles & {
- siteVariables?: SiteVariablesPrepared
+ siteVariables?: { [key: string]: any }
focusInnerBorderColor?: string
focusOuterBorderColor?: string
borderPadding?: React.CSSProperties['padding']
diff --git a/packages/react/src/themes/teams/index.tsx b/packages/react/src/themes/teams/index.tsx
index f894c18b1f..c02e089d1a 100644
--- a/packages/react/src/themes/teams/index.tsx
+++ b/packages/react/src/themes/teams/index.tsx
@@ -11,6 +11,10 @@ import { default as svgIconsAndStyles } from './components/Icon/svg'
import { TeamsSvgIconSpec, SvgIconSpecWithStyles } from './components/Icon/svg/types'
import { createTheme } from '../createTheme'
+import menuStyles from './components/Menu/menuStyles'
+import menuItemStyles from './components/Menu/menuItemStyles'
+import menuDividerStyles from './components/Menu/menuDividerStyles'
+import menuItemWrapperStyles from './components/Menu/menuItemWrapperStyles'
const declareSvg = (svgIcon: SvgIconSpec): ThemeIconSpec => ({
isSvg: true,
@@ -49,9 +53,16 @@ const icons: ThemeIcons = {
const teamsTheme: ThemePrepared = createTheme(
{
+ name: 'teams-light',
siteVariables,
componentVariables,
componentStyles,
+ componentSelectorStyles: {
+ Menu: menuStyles,
+ MenuItem: menuItemStyles,
+ MenuDivider: menuDividerStyles,
+ MenuItemWrapper: menuItemWrapperStyles,
+ },
fontFaces,
staticStyles,
icons,
diff --git a/packages/react/src/themes/types.ts b/packages/react/src/themes/types.ts
index 19b70875ba..e43d170382 100644
--- a/packages/react/src/themes/types.ts
+++ b/packages/react/src/themes/types.ts
@@ -259,7 +259,6 @@ export type ComponentVariablesObject = any
export type ComponentVariablesPrepared = (
siteVariables?: SiteVariablesPrepared,
- props?: any, // TODO: REMOVE THIS CRUFT
) => ComponentVariablesObject
// TODO: Make this generic
@@ -370,6 +369,16 @@ export type ComponentSlotStyle =
export interface ComponentSlotStylesInput
extends ObjectOf> {}
+export type ComponentStyleSelector =
+ | { [key in keyof TProps]: any }
+ | { [key in keyof TProps]: any }[]
+
+export type ComponentSelectorsAndStyles = (
+ vars: TVars,
+) => {
+ [key: string]: [ComponentStyleSelector, ICSSInJSStyle][]
+}
+
export interface ComponentSlotStylesPrepared
extends ObjectOf> {}
@@ -418,9 +427,16 @@ export interface ThemeAnimation {
// Theme
// ========================================================
export interface ThemeInput {
+ name?: string
siteVariables?: SiteVariablesInput
componentVariables?: ThemeComponentVariablesInput
componentStyles?: ThemeComponentStylesInput
+ componentSelectorStyles?: {
+ Menu: ComponentSelectorsAndStyles
+ MenuItem: ComponentSelectorsAndStyles
+ MenuDivider: ComponentSelectorsAndStyles
+ MenuItemWrapper: ComponentSelectorsAndStyles
+ }
fontFaces?: FontFaces
staticStyles?: StaticStyles
icons?: ThemeIcons
@@ -436,9 +452,16 @@ export interface ThemeInput {
// As a theme cascades down the tree and is merged with the previous theme on
// context, the resulting theme takes this shape.
export interface ThemePrepared {
+ name?: string
siteVariables: SiteVariablesPrepared
componentVariables: { [key in keyof ThemeComponentVariablesPrepared]: ComponentVariablesPrepared }
componentStyles: { [key in keyof ThemeComponentStylesPrepared]: ComponentSlotStylesPrepared }
+ componentSelectorStyles?: {
+ Menu: ComponentSelectorsAndStyles
+ MenuItem: ComponentSelectorsAndStyles
+ MenuDivider: ComponentSelectorsAndStyles
+ MenuItemWrapper: ComponentSelectorsAndStyles
+ }
icons: ThemeIcons
fontFaces: FontFaces
staticStyles: StaticStyles
diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts
index 00d14f8544..6e2d533c5b 100644
--- a/packages/react/src/types.ts
+++ b/packages/react/src/types.ts
@@ -171,4 +171,5 @@ export interface ProviderContextPrepared {
theme: ThemePrepared
telemetry: Telemetry | undefined
_internal_resolvedComponentVariables: Record
+ _internal_resolvedComponentStyles: Record
}
diff --git a/packages/style/compile.ts b/packages/style/compile.ts
new file mode 100644
index 0000000000..4444a99be4
--- /dev/null
+++ b/packages/style/compile.ts
@@ -0,0 +1,357 @@
+/**
+ * To run:
+ * nodemon --watch 'src/themes/toCSS.ts' --exec 'ts-node src/themes/toCSS.ts'
+ */
+// import { cssifyObject } from 'css-in-js-utils'
+import * as _ from 'lodash'
+import { ThemePrepared } from '@stardust-ui/react/src'
+
+export const fs = require('fs')
+export const pkg = require('../../package.json')
+
+// ============================================================
+// Utilities
+// ============================================================
+
+// For a given base style and override style, returns only those
+// properties in the override which update the base style.
+// Duplicate or unnecessary properties are removed.
+export const getOverrideStyles = (base = {}, override = {}) => {
+ return Object.keys(base).reduce((acc, next) => {
+ if (base[next] !== override[next]) {
+ acc[next] = _.isPlainObject(base[next]) ? getOverrideStyles(base[next]) : base[next]
+ }
+
+ return acc
+ }, {})
+}
+
+export const sortObject = unsorted => {
+ return Object.keys(unsorted)
+ .sort()
+ .reduce((sorted, key) => {
+ const value = unsorted[key]
+ sorted[key] = _.isPlainObject(value) ? sortObject(value) : value
+ return sorted
+ }, {})
+}
+
+// ============================================================
+// Selectors
+// ============================================================
+export const nameToSelector = (name: string) => `.ui-${_.kebabCase(name)}`
+
+export const slotToSelector = (slot: string) => `__${_.kebabCase(slot)}`
+
+export const propsToSelector = (props: object) => {
+ return Object.keys(props).reduce((acc, key) => {
+ const value = props[key]
+ const kebabKey = _.kebabCase(key)
+
+ const className = value === true ? kebabKey : `${kebabKey}-${_.kebabCase(value)}`
+
+ return `.${className}`
+ }, '')
+}
+export const makeSelector = (name: string, slot: string, props: object) => {
+ return slot === 'root'
+ ? nameToSelector(name) + propsToSelector(props)
+ : nameToSelector(name) + slotToSelector(slot) + propsToSelector(props)
+}
+
+// ============================================================
+// JS Style Objects
+// ============================================================
+
+export const jsStyleToCSSObject = (styleObject = {}) => {
+ return Object.keys(styleObject).reduce((acc, key) => {
+ const value = styleObject[key]
+ const cssKey = /[A-Z]/.test(key) ? _.kebabCase(key) : key
+
+ acc[cssKey] = _.isPlainObject(value) ? jsStyleToCSSObject(value) : value
+
+ return acc
+ }, {})
+}
+/**
+ * IN {
+ * '.button': {
+ * color: 'blue',
+ * '&:hover': { color: 'red' }
+ * '.icon': { color: 'inherit' }
+ * }
+ * }
+ * OUT {
+ * '.button': { color: 'blue' }
+ * '.button:hover': { color: 'red' }
+ * '.button .icon': { color: 'inherit' }
+ * }
+ */
+// const flattenJSStyleSheet = styleSheet => {
+// const flattened = {}
+// const selectors = []
+//
+// // TODO: naive, doesn't handle element selectors
+// const isSelector = str => /^[^a-zA-Z]/.test(str.trim())
+//
+// // ensures proper spacing between selectors
+// // supports '&' for root selector
+// const joinSelectors = selectors => {
+// return selectors
+// .reduce((acc, next, i) => {
+// const maybeSpace = i > 0 && /^[^&]/.test(next) ? ' ' : ''
+// acc += maybeSpace + next
+// return acc
+// }, '')
+// .replace(/&/g, '')
+// }
+//
+// const flattenStyle = style => {
+// Object.keys(style).forEach(key => {
+// if (isSelector(key)) {
+// selectors.push(key)
+// flattenStyle(_.get(styleSheet, selectors))
+// } else {
+// _.set(flattened, [joinSelectors(selectors), key], style[key])
+// }
+// })
+// // once we've finished iterating this style object
+// // we're no longer under the current selector
+// selectors.pop()
+// }
+//
+// flattenStyle(styleSheet)
+//
+// return flattened
+// }
+
+// ============================================================
+// CSS String
+// ============================================================
+
+export const cssObjectToString = (cssObject = {}) => {
+ return (
+ JSON.stringify(cssObject, null, 2)
+ // remove double quotes
+ .replace(/"/gm, '')
+ // quote content properties
+ .replace(/ content: (.*),/gm, ' content: "$1",')
+ // remove colon before curly braces
+ .replace(/: \{/gm, ' {')
+ // remove commas
+ .replace(/,$/gm, '')
+ // add semis
+ .replace(/([^{}])$/gm, '$1;')
+ // remove empty rules
+ .replace(/^.*\{\}\n/gm, '')
+ )
+}
+
+export const jsStyleSheetToCSSString = (jsStyleObject = {}) => {
+ return Object.keys(jsStyleObject)
+ .reduce((cssFile, selector) => {
+ const style = cssObjectToString(jsStyleToCSSObject(jsStyleObject[selector]))
+ return [...cssFile, `${selector} ${style}`]
+ }, [])
+ .join('\n\n')
+}
+
+// ============================================================
+// Stardust Theme Utils
+// ============================================================
+
+export const makeStyleArg = (name: string, props: object, theme: ThemePrepared) => {
+ const SEE_FINDINGS_RADIO_GROUP_ITEM = {}
+
+ return {
+ rtl: false,
+ disableAnimations: false,
+ props: {
+ isFromKeyboard: false, // see FINDINGS 3
+ // ..._.get(stardust, `${name}.defaultProps`, {}), // see FINDINGS 1
+ ...props,
+ },
+ variables: theme.componentVariables[name]
+ ? theme.componentVariables[name](theme.siteVariables, SEE_FINDINGS_RADIO_GROUP_ITEM)
+ : {},
+ colors: {},
+ theme,
+ }
+}
+
+// const spec = {
+// name: COMPONENT_NAME,
+// definition: [
+// // ----------------------------------------
+// // Base
+// // ----------------------------------------
+// {},
+//
+// // ----------------------------------------
+// // Types
+// // ----------------------------------------
+// { primary: true },
+// { secondary: true },
+//
+// // icon
+// // { iconOnly: true },
+// // { iconPosition: 'before' },
+// // { iconPosition: 'after' },
+//
+// // ----------------------------------------
+// // States
+// // ----------------------------------------
+// // { active: true },
+// { disabled: true },
+// // { focus: true },
+//
+// // ----------------------------------------
+// // Variations
+// // ----------------------------------------
+// // { circular: true },
+// // { fluid: true },
+// { text: true },
+// ],
+// }
+
+// ======================================================================
+// USAGE
+// ======================================================================
+
+export function toSCSS(theme = {}, opts = {}) {
+ // TODO: we should have a function to prepare an input theme
+ const { componentStyles = {}, componentVariables = {}, siteVariables = {} } = theme as any
+ const { name = '', version = '', gitHubURL = '', npmURL = '', docsURL = '' } = opts as any
+
+ let result = [
+ `/*`,
+ ` * Name : ${name}`,
+ ` * Version : ${version}`,
+ ` * GitHub : ${gitHubURL}`,
+ ` * NPM : ${npmURL}`,
+ ` * Docs : ${docsURL}`,
+ ` */`,
+ '',
+ ].join('\n')
+
+ const componentNames = Object.keys(componentStyles)
+ console.log('componentNames', componentNames)
+
+ componentNames.forEach(name => {
+ if (typeof componentStyles[name] !== 'function') {
+ return console.log('Skipping old style signature:', name)
+ }
+
+ const headerLength = 40
+ const paddingLength = Math.floor((headerLength - name.length) / 2)
+ const padding = ' '.repeat(paddingLength)
+
+ result += `\n/${'*'.repeat(headerLength - 1)}\n`
+ result += `${padding}${name}${padding}\n`
+ result += `${'*'.repeat(headerLength - 1)}/\n\n`
+
+ const variables = componentVariables[name] ? componentVariables[name](siteVariables) : {}
+ const styles = componentStyles[name](variables)
+ const anatomy = Object.keys(styles)
+
+ anatomy.forEach(slot => {
+ styles[slot].forEach(([propsMatcher, styleObject]) => {
+ const slotSelector = makeSelector(name, slot, propsMatcher)
+ const slotStyle = cssObjectToString(jsStyleToCSSObject(styleObject))
+ result += `${slotSelector}${slotStyle}\n`
+ })
+ })
+ })
+
+ return result
+}
+
+// const makeStyleSheet = spec => {
+// const [baseProps, ...restProps] = spec.definition
+//
+// // TODO: make styles for all anatomy parts
+// // const anatomy = Object.keys(buttonStyles)
+//
+// const baseStyle = themes.teams.componentStyles[COMPONENT_NAME].root(makeStyleArg(spec.name, { props: baseProps }))
+// const baseSelector = nameToSelector(spec.name) + propsToSelector(baseProps)
+//
+// const styleSheet = { [baseSelector]: baseStyle }
+//
+// return restProps.reduce((acc, props) => {
+// const style = themes.teams.componentStyles[COMPONENT_NAME].root(makeStyleArg(spec.name, { props }))
+// const selector = nameToSelector(spec.name) + propsToSelector(props)
+//
+// acc[selector] = getOverrideStyles(style, baseStyle)
+//
+// return acc
+// }, styleSheet)
+// }
+//
+// const jsStyleSheet = makeStyleSheet(spec)
+//
+// console.log(_.mapValues(jsStyleSheet, (val, key) => cssifyObject(val)))
+
+/*
+============================================================
+
+ FINDINGS
+
+============================================================
+
+TODO
+ - [ ] stardust.scss .ui-icon style is missing font-family value
+ - [ ] stardust.scss .ui-layout style is missing grid-template-columns value
+
+# 1. defaultProps
+Many styles are dependant on defaultProp values. Some styles break (statusStyles) when the default
+props are not present.
+
+Aside from breaking, there are some components where a default prop value is going to make sense. In
+those cases, the default "style props" should likely be part of the base theme. This way, when
+calling the style functions, the correct "base" style is produced for the component.
+
+If we leave defaultProps in each component, then styles have a hard dependency on components.
+
+# 2. Prop Permutations
+In order to generate styles for each type, state, and variation we need to have props objects
+that describe each of these. In order to invoke a style object code path, we need to pass the
+correct props to the style function.
+
+This has consequences and benefits. It means we need to produce a _definition_ of each component.
+This definition should codify the design terms available to a component.
+
+In order to produce a stylesheet with the proper flow and optimizations, we need to know which order
+these design terms are applied. This way the base style is defined at the top of the sheet and more
+detailed definitions are at the bottom. This order also has to be shared and adopted, else, we
+won't be able to reliably produce styles.
+
+# 3. isFromKeyboard
+https://github.com/stardust-ui/react/issues/749
+
+There is no way to encode this in a flat stylesheet. Even if we produce two flat styles, one with
+keyboard focus and one without, we will have to decide on a selector to use in the stylesheet. This
+means consumers will also have to use a similar utility and creating matching elements in order to
+apply the style.
+
+# 4. RadioGroupItem
+
+The variables for this file require props to be provided. This is the only file doing so.
+Only styles should be dependant on runtime values, such as props. Variables should be static.
+
+# 5. dropdownSearchInputStyles
+https://github.com/stardust-ui/react/issues/753
+
+This style file has no root key.
+
+# 6. inputStyles
+
+The `root` key is applied to the `wrapper` slot in the render function.
+
+# 7. attachmentStyles
+
+Does not gracefully handle undefined props.progress in the "progress" slot styles for width.
+
+# 8. Missing variables for menuDividerStyles and menuItemStyles
+
+There are variables for the Menu itself, but not for the divider or item subcomponents.
+*/
diff --git a/packages/style/index.ts b/packages/style/index.ts
new file mode 100644
index 0000000000..966e515f2d
--- /dev/null
+++ b/packages/style/index.ts
@@ -0,0 +1,39 @@
+import { toSCSS } from './compile'
+// import { ThemeInput } from '@stardust-ui/react/src'
+// import resolveComponentRules from '@stardust-ui/react/src/lib/resolveComponentRules'
+import * as stardust from '../react'
+
+console.clear()
+
+console.log('='.repeat(80))
+console.time('compile theme:')
+
+const theme = {
+ siteVariables: {
+ brandColor: 'cornflowerblue',
+ ...stardust.themes.base.siteVariables,
+ },
+
+ componentVariables: {
+ Button: () => ({}),
+ ...stardust.themes.base.componentVariables,
+ },
+
+ componentStyles: {
+ Button: props => ({
+ root: [[{}, { display: 'inline-block' }]],
+ }),
+ ...stardust.themes.base.componentStyles,
+ },
+}
+
+const scss = toSCSS(theme, {
+ name: 'Stardust UI',
+ version: '1.0.0',
+ gitHubURL: 'https://github.com/stardust-ui',
+ npmURL: 'https://npmjs.com/@stardust-ui',
+ docsURL: 'https://stardust-ui.github.io',
+})
+
+console.log(scss)
+console.timeEnd('compile theme:')
diff --git a/packages/style/package.json b/packages/style/package.json
new file mode 100644
index 0000000000..0511576f5f
--- /dev/null
+++ b/packages/style/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@stardust-ui/style",
+ "version": "1.0.0",
+ "description": "Tools for creating and building framework agnostic themes.",
+ "main": "index.js",
+ "repository": "https://github.com/stardust-ui/react",
+ "license": "MIT"
+}
diff --git a/packages/style/tsconfig.json b/packages/style/tsconfig.json
new file mode 100644
index 0000000000..f94e22b1aa
--- /dev/null
+++ b/packages/style/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../build/tsconfig.common",
+ "include": ["src"]
+}
diff --git a/yarn.lock b/yarn.lock
index a4afdacdce..a14aabb6d2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5785,6 +5785,17 @@ fela-dom@^10.6.1:
fast-loops "^1.0.1"
fela-utils "^10.6.1"
+fela-monolithic@^10.8.2:
+ version "10.8.2"
+ resolved "https://registry.yarnpkg.com/fela-monolithic/-/fela-monolithic-10.8.2.tgz#bb1bf27443757775bcdcd12087b08a58666ecf4c"
+ integrity sha512-hEuKDFeSgD9i6MzkGGWugUr/z25E1iento6oFRQXVz1zi9Ia9+FYM+feCX8xV9ORXrKgO8ozrLhh9K7Yfx4q3w==
+ dependencies:
+ css-in-js-utils "^3.0.0"
+ fast-loops "^1.0.0"
+ fela-utils "^10.8.2"
+ isobject "^3.0.1"
+ string-hash "^1.1.3"
+
fela-plugin-custom-property@^10.6.1:
version "10.6.1"
resolved "https://registry.yarnpkg.com/fela-plugin-custom-property/-/fela-plugin-custom-property-10.6.1.tgz#3a2d06ac4e509a7ea0335b3b959850ad5f3eede1"
@@ -5853,6 +5864,14 @@ fela-utils@^10.6.1:
css-in-js-utils "^3.0.0"
fast-loops "^1.0.0"
+fela-utils@^10.8.2:
+ version "10.8.2"
+ resolved "https://registry.yarnpkg.com/fela-utils/-/fela-utils-10.8.2.tgz#b9d38e043aaf5d3d48012686ffb84e6378f24a35"
+ integrity sha512-RmoDOIby14Zb3Xn03noLolyMC2528xcNO5KcNCaznyByd1Acq8DnvQn91Ph9nBLcLqdC1rGme5HwRcrCOHG+kA==
+ dependencies:
+ css-in-js-utils "^3.0.0"
+ fast-loops "^1.0.0"
+
fela@^10.6.1:
version "10.6.1"
resolved "https://registry.yarnpkg.com/fela/-/fela-10.6.1.tgz#66a8349018a5c06908deca7fc18a83b0df9d63a6"
@@ -13045,6 +13064,11 @@ string-argv@^0.0.2:
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.0.2.tgz#dac30408690c21f3c3630a3ff3a05877bdcbd736"
integrity sha1-2sMECGkMIfPDYwo/86BYd73L1zY=
+string-hash@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b"
+ integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=
+
string-length@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"