diff --git a/modules/banner/react/lib/Banner.tsx b/modules/banner/react/lib/Banner.tsx index 650889272d..c49f552127 100644 --- a/modules/banner/react/lib/Banner.tsx +++ b/modules/banner/react/lib/Banner.tsx @@ -60,6 +60,7 @@ const BannerWrapper = styled('button')( }, ({error, theme}) => { theme = getTheme(theme); // eslint-disable-line no-param-reassign + console.log(theme.breakpoints.only('m')); return { backgroundColor: error === ErrorType.Error ? theme.palette.error.main : theme.palette.alert.main, diff --git a/modules/core/react/index.ts b/modules/core/react/index.ts index df07444923..aeb78ea9b5 100644 --- a/modules/core/react/index.ts +++ b/modules/core/react/index.ts @@ -13,6 +13,7 @@ import spacing, { } from './lib/spacing'; import type, {CanvasType, fontFamily, monoFontFamily, CanvasTypeVariant} from './lib/type'; import {CSSProperties} from './lib/types'; +import {breakpoints, CanvasBreakpoints, BreakpointKey} from './lib/theming/breakpoints'; const {default: colors, ...semanticColors} = canvasColorsWeb; const canvas = { @@ -31,6 +32,7 @@ export * from './lib/TypeWrappers'; export * from '@workday/canvas-colors-web'; export { borderRadius, + breakpoints, colors, depth, spacing, @@ -39,6 +41,8 @@ export { fontFamily, monoFontFamily, BrandingColor, + BreakpointKey, + CanvasBreakpoints, CanvasDepth, CanvasDepthValue, CanvasSpacing, diff --git a/modules/core/react/lib/theming/breakpoints.ts b/modules/core/react/lib/theming/breakpoints.ts new file mode 100644 index 0000000000..debb95c989 --- /dev/null +++ b/modules/core/react/lib/theming/breakpoints.ts @@ -0,0 +1,65 @@ +export enum BreakpointKey { + zero = 'zero', + s = 's', + m = 'm', + l = 'l', + xl = 'xl', +} + +export type BreakpointFnParam = BreakpointKey | keyof typeof BreakpointKey; + +export type CanvasBreakpoints = { + zero: number; + s: number; + m: number; + l: number; + xl: number; + [key: string]: number; +}; + +export const breakpointKeys = ['zero', 's', 'm', 'l', 'xl']; + +export const breakpoints: CanvasBreakpoints = { + zero: 0, + s: 600, + m: 960, + l: 1280, + xl: 1920, +}; + +const step = 0.5; + +export function up(key: BreakpointFnParam) { + const value = typeof breakpoints[key as BreakpointKey] === 'number' ? breakpoints[key] : key; + return `@media (min-width:${value}px)`; +} + +export function down(key: BreakpointFnParam) { + const endIndex = breakpointKeys.indexOf(key as BreakpointKey) + 1; + const upperbound = breakpoints[breakpointKeys[endIndex]]; + + if (endIndex === breakpointKeys.length) { + // xl down applies to all sizes + return up(BreakpointKey.zero); + } + + const value = typeof upperbound === 'number' && endIndex > 0 ? upperbound : 0; + return `@media (max-width:${value - step}px)`; +} + +export function between(start: BreakpointFnParam, end: BreakpointFnParam) { + const endIndex = breakpointKeys.indexOf(end) + 1; + + if (endIndex === breakpointKeys.length) { + return up(start); + } + + return ( + `@media (min-width:${breakpoints[start]}px) and ` + + `(max-width:${breakpoints[breakpointKeys[endIndex]] - step}px)` + ); +} + +export function only(key: BreakpointFnParam) { + return between(key, key); +} diff --git a/modules/core/react/lib/theming/theme.ts b/modules/core/react/lib/theming/theme.ts index d699b956fc..d540fae8ce 100644 --- a/modules/core/react/lib/theming/theme.ts +++ b/modules/core/react/lib/theming/theme.ts @@ -1,6 +1,7 @@ import colors from '@workday/canvas-colors-web'; import deepmerge from 'deepmerge'; import {CanvasTheme} from './types'; +import {breakpoints, up, down, between, only} from './breakpoints'; /** * Considerations: @@ -41,8 +42,14 @@ export const defaultCanvasTheme: CanvasTheme = { focusOutline: colors.blueberry400, }, }, + breakpoints: { + values: breakpoints, + up, + down, + between, + only, + }, // Typography - // Breakpoints // Depth? }; diff --git a/modules/core/react/lib/theming/types.ts b/modules/core/react/lib/theming/types.ts index 4a79bf2cb1..4e4ce27a6d 100644 --- a/modules/core/react/lib/theming/types.ts +++ b/modules/core/react/lib/theming/types.ts @@ -1,3 +1,5 @@ +import {CanvasBreakpoints, BreakpointFnParam} from './breakpoints'; + /** * A single palette within a Canvas theme */ @@ -26,6 +28,13 @@ export interface CanvasTheme { alert: CanvasThemePalette; [index: string]: CanvasThemePalette | CanvasThemeCommonPalette; }; + breakpoints: { + values: CanvasBreakpoints; + up: (key: BreakpointFnParam) => string; + down: (key: BreakpointFnParam) => string; + only: (key: BreakpointFnParam) => string; + between: (start: BreakpointFnParam, end: BreakpointFnParam) => string; + }; } /** diff --git a/modules/core/react/spec/breakpoints.spec.tsx b/modules/core/react/spec/breakpoints.spec.tsx new file mode 100644 index 0000000000..2c9d18e710 --- /dev/null +++ b/modules/core/react/spec/breakpoints.spec.tsx @@ -0,0 +1,51 @@ +import {BreakpointKey, up, down, between, only} from '../lib/theming/breakpoints'; + +describe('Breakpoints', () => { + test('up function works with enum', () => { + const mediaQuery = up(BreakpointKey.m); + + expect(mediaQuery).toBe('@media (min-width:960px)'); + }); + + test('up function works with string', () => { + const mediaQuery = up('m'); + + expect(mediaQuery).toBe('@media (min-width:960px)'); + }); + + test('down function works with enum', () => { + const mediaQuery = down(BreakpointKey.m); + + expect(mediaQuery).toBe('@media (max-width:1279.5px)'); + }); + + test('down function works with string', () => { + const mediaQuery = down('m'); + + expect(mediaQuery).toBe('@media (max-width:1279.5px)'); + }); + + test('between function works with enums', () => { + const mediaQuery = between(BreakpointKey.m, BreakpointKey.l); + + expect(mediaQuery).toBe('@media (min-width:960px) and (max-width:1919.5px)'); + }); + + test('between function works with string', () => { + const mediaQuery = between('m', 'l'); + + expect(mediaQuery).toBe('@media (min-width:960px) and (max-width:1919.5px)'); + }); + + test('only function works with enums', () => { + const mediaQuery = only(BreakpointKey.m); + + expect(mediaQuery).toBe('@media (min-width:960px) and (max-width:1279.5px)'); + }); + + test('between function works with string', () => { + const mediaQuery = only('m'); + + expect(mediaQuery).toBe('@media (min-width:960px) and (max-width:1279.5px)'); + }); +});