diff --git a/CHANGELOG.md b/CHANGELOG.md index 9242b426d..fddc39677 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [21.13.0-pre-prod.1](https://github.com/WTW-IM/es-components/compare/v21.13.0-pre-prod.0...v21.13.0-pre-prod.1) (2024-03-15) + +### Build + +- fixing types declaration in package.json ([2b2b8cc](https://github.com/WTW-IM/es-components/commit/2b2b8cc7463ad966adbbceb746ea903a296f1244)) + +# [21.13.0-pre-prod.0](https://github.com/WTW-IM/es-components/compare/v21.12.0...v21.13.0-pre-prod.0) (2024-03-12) + +### Build + +- adding start-localtest to es-components options ([890783d](https://github.com/WTW-IM/es-components/commit/890783dc42cb10b3c7323ed258a8e82a3c811934)) + +### New + +- using icon styles from Icon in StatusTracker ([b4cbf5e](https://github.com/WTW-IM/es-components/commit/b4cbf5e44a2a37baa85fa480b62268fd83fb3257)) + # [21.12.0](https://github.com/WTW-IM/es-components/compare/v21.12.0-pre-prod.7...v21.12.0) (2024-03-06) **Note:** Version bump only for package es-components-monorepo diff --git a/lerna.json b/lerna.json index 8ec3a97d8..981ac797a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,7 +1,7 @@ { "lerna": "2.11.0", "packages": ["packages/*"], - "version": "21.12.0", + "version": "21.13.0-pre-prod.1", "command": { "publish": { "message": "Build: [skip ci][skip-release] %s" diff --git a/package-lock.json b/package-lock.json index 4eb2dfaf1..479eb7bea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28302,7 +28302,7 @@ } }, "packages/es-components": { - "version": "21.12.0", + "version": "21.13.0-pre-prod.1", "license": "MIT", "dependencies": { "@babel/helpers": "^7.20.7", diff --git a/package.json b/package.json index 3662935e0..ecf842f2c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "version_only": "lerna publish --skip-npm --force-publish", "publish_only": "lerna publish --skip-git", "start": "npm start -w es-components", + "start-localtest": "npm run start-localtest -w es-components", "commit": "commit", "build-themes": "npm run build -w es-components-via-theme -w es-components-wtw-theme -w es-components-wtw-legacy-theme", "preprepare": "npm run build -w es-components-shared-types && npm run build-themes", diff --git a/packages/es-components/CHANGELOG.md b/packages/es-components/CHANGELOG.md index 1eee3cb3f..971df7cc1 100644 --- a/packages/es-components/CHANGELOG.md +++ b/packages/es-components/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [21.13.0-pre-prod.1](https://github.com/wtw-im/es-components/compare/v21.13.0-pre-prod.0...v21.13.0-pre-prod.1) (2024-03-15) + +### Build + +- fixing types declaration in package.json ([2b2b8cc](https://github.com/wtw-im/es-components/commit/2b2b8cc7463ad966adbbceb746ea903a296f1244)) + +# [21.13.0-pre-prod.0](https://github.com/wtw-im/es-components/compare/v21.12.0...v21.13.0-pre-prod.0) (2024-03-12) + +### Build + +- adding start-localtest to es-components options ([890783d](https://github.com/wtw-im/es-components/commit/890783dc42cb10b3c7323ed258a8e82a3c811934)) + +### New + +- using icon styles from Icon in StatusTracker ([b4cbf5e](https://github.com/wtw-im/es-components/commit/b4cbf5e44a2a37baa85fa480b62268fd83fb3257)) + # [21.12.0](https://github.com/wtw-im/es-components/compare/v21.12.0-pre-prod.7...v21.12.0) (2024-03-06) **Note:** Version bump only for package es-components diff --git a/packages/es-components/package.json b/packages/es-components/package.json index 30ddfe27b..12a2ecf9d 100644 --- a/packages/es-components/package.json +++ b/packages/es-components/package.json @@ -1,13 +1,13 @@ { "name": "es-components", - "version": "21.12.0", + "version": "21.13.0-pre-prod.1", "description": "React components built for Exchange Solutions products", "author": "Willis Towers Watson - Individual Marketplace", "license": "MIT", "repository": "https://github.com/wtw-im/es-components", "module": "lib/index.js", "main": "cjs/index.js", - "types": "types/index.d.ts", + "types": "types/src/index.d.ts", "sideEffects": false, "scripts": { "build": "cross-env MAIN_BUILD=\"$( (git merge-base --is-ancestor HEAD origin/main && echo 'true') || echo 'false' )\" npm run rollup", @@ -19,6 +19,7 @@ "pre-prod-sg-build": "npm run styleguide-preprod && npm run pre-prod-fc-icons", "pre-prod-fc-icons": "cp docs/full-color-icons.html docs/pre-prod && cp docs/full-color-icons.js docs/pre-prod", "start": "concurrently --kill-others \"rollup -c docs-rollup.config.mjs -w --configEnv local\" \"styleguidist server --env local\"", + "start-localtest": "concurrently --kill-others \"rollup -c docs-rollup.config.mjs -w --configEnv local\" \"styleguidist server --env local --run-localtest\"", "test": "jest", "test:watch": "jest --watch", "wait-for-start": "wait-on http://127.0.0.1:6060", diff --git a/packages/es-components/src/components/base/icons/IconContext.ts b/packages/es-components/src/components/base/icons/IconContext.ts index f2b4d1c87..64e7cd44b 100644 --- a/packages/es-components/src/components/base/icons/IconContext.ts +++ b/packages/es-components/src/components/base/icons/IconContext.ts @@ -1,10 +1,13 @@ import { createContext } from 'react'; +import type { RootNode } from '../../util/useRootNode'; + +type TrueRootNode = NonNullable; class NodeError extends Error { - public node: HTMLElement; + public node: TrueRootNode; constructor( - node: HTMLElement, + node: TrueRootNode, ...errorArgs: ConstructorParameters ) { super(...errorArgs); @@ -27,7 +30,7 @@ if (local === subdomain) 'app.localtest.viabenefits.com:34300' ); -const getExistingStyleTag = (node: HTMLElement) => +const getExistingStyleTag = (node: TrueRootNode) => node.querySelector(`[${iconStyleAttribute}]`) || node.querySelector(`link[href="${iconsAsset}"]:not([rel="preload"])`); @@ -39,7 +42,7 @@ const createStyleTag = () => { return styleTag; }; -const addTag = (node: HTMLElement, func: (node: HTMLElement) => void) => { +const addTag = (node: TrueRootNode, func: (node: TrueRootNode) => void) => { try { const foundTag = getExistingStyleTag(node); if (foundTag) return foundTag; @@ -58,24 +61,24 @@ const addTag = (node: HTMLElement, func: (node: HTMLElement) => void) => { } }; -const defaultIconContext: { initializedNodes: Array } = { +const defaultIconContext: { initializedNodes: Array } = { initializedNodes: [] }; -const documentAppend = (tag: HTMLElement) => document.head.append(tag); -const initializeBody = (node: HTMLElement) => { +const documentAppend = (tag: TrueRootNode) => document.head.append(tag); +const initializeBody = (node: TrueRootNode) => { if (getExistingStyleTag(document.head)) return node; return addTag(node, documentAppend); }; -const initializeNode = (node: HTMLElement) => { +const initializeNode = (node: TrueRootNode) => { // body must always be set up setup(document.body); // eslint-disable-line no-use-before-define - return addTag(node, (tag: HTMLElement) => node.prepend(tag)); + return addTag(node, (tag: TrueRootNode) => node.prepend(tag)); }; -const setup = (node: HTMLElement | undefined) => { +const setup = (node: RootNode | undefined) => { if (!node) return; const { initializedNodes } = defaultIconContext; diff --git a/packages/es-components/src/components/base/icons/useIconStyles.tsx b/packages/es-components/src/components/base/icons/useIconStyles.tsx new file mode 100644 index 000000000..d0f59c5cd --- /dev/null +++ b/packages/es-components/src/components/base/icons/useIconStyles.tsx @@ -0,0 +1,128 @@ +import React, { useMemo, forwardRef } from 'react'; +import { css } from 'styled-components'; +import { IconName } from 'es-components-shared-types'; +import { useRootNodeContext, RootNode } from '../../util/useRootNode'; +import Icon, { IconBaseProps, IconProps, iconBaseStyles } from './Icon'; + +type DocumentRoot = Document | ShadowRoot; +type GetComputedStyleFunc = typeof window.getComputedStyle; +type ComputedStyleParams = Parameters; +type ComputedStyleReturn = ReturnType; +type IconElement = HTMLElement; + +const computeStyle = (...args: ComputedStyleParams) => { + try { + return window.getComputedStyle(...args); + } catch (err) { + if ((err as Error)?.message?.match(/Not Implemented/i)) { + return {} as ComputedStyleReturn; + } + throw err; + } +}; + +const getIconElementStyles = (icon: IconElement) => { + const computedStyle = computeStyle(icon); + const beforeStyle = computeStyle(icon, ':before'); + return css` + ${() => css` + ${iconBaseStyles} + + // computed styles + content: ${beforeStyle.content}; + font-family: ${computedStyle.fontFamily}; + font-size: ${computedStyle.fontSize}; + `} + `; +}; + +const getIconStyles = (iconName: IconName, rootNode?: RootNode) => { + const rules = [ + ...new Set([ + ...((rootNode?.getRootNode() as DocumentRoot | undefined)?.styleSheets || + []), + ...document.styleSheets + ]) + ] + .flatMap(sheet => { + try { + return [...(sheet.rules || [])]; + } catch (e) { + return []; + } + }) + .filter((rule: CSSRule): rule is CSSStyleRule => + Boolean((rule as CSSStyleRule).selectorText) + ); + const baseIconRule = rules.find(rule => rule.selectorText === '.bds-icon'); + const iconRule = rules.find(rule => + [`.bds-${iconName}:before`, `.bds-${iconName}::before`].includes( + rule.selectorText + ) + ); + const iconStyles = css` + ${baseIconRule?.cssText} + ${iconRule?.cssText} + `; + return iconStyles; +}; + +export type HiddenIconConfig = { + [key in IconName]?: React.Ref; +}; + +type HiddenIconProps = JSXElementProps<'div'> & { + icons: HiddenIconConfig; + iconProps?: Omit; +}; + +export const HiddenIcons = forwardRef( + function ForwardedHiddenIcons({ icons, style, iconProps, ...props }, ref) { + return ( + + ); + } +); + +export const useIconStyles = ( + iconName?: IconName, + icon?: IconElement | null +) => { + const rootNode = useRootNodeContext(); + const iconStyles = useMemo( + () => + icon + ? getIconElementStyles(icon) + : iconName + ? getIconStyles(iconName, rootNode) + : undefined, + [iconName, rootNode, icon] + ); + return iconStyles; +}; + +export default useIconStyles; diff --git a/packages/es-components/src/components/containers/sliding-pane/SlidingPane.tsx b/packages/es-components/src/components/containers/sliding-pane/SlidingPane.tsx index 9a1f4463a..98220ef71 100644 --- a/packages/es-components/src/components/containers/sliding-pane/SlidingPane.tsx +++ b/packages/es-components/src/components/containers/sliding-pane/SlidingPane.tsx @@ -211,7 +211,10 @@ export default function SlidingPane({ }: SlidingPaneProps) { const [rootNode, RootNodeLocator] = useRootNodeLocator(document.body); useDisableBodyScroll(isOpen); - const handle = useCallback(() => rootNode, [rootNode]); + const handle = useCallback<() => HTMLElement>( + () => rootNode as HTMLElement, + [rootNode] + ); const modalParentSelector = parentSelector || handle; const Pane = getPane(from); const styles = { diff --git a/packages/es-components/src/components/controls/textbox/Textbox.tsx b/packages/es-components/src/components/controls/textbox/Textbox.tsx index 111c6855a..a01710025 100644 --- a/packages/es-components/src/components/controls/textbox/Textbox.tsx +++ b/packages/es-components/src/components/controls/textbox/Textbox.tsx @@ -1,15 +1,9 @@ -import React, { - useCallback, - useImperativeHandle, - useState, - useEffect, - useMemo -} from 'react'; +import React, { useCallback, useImperativeHandle, useState } from 'react'; import PropTypes from 'prop-types'; import styled, { css } from 'styled-components'; import { IconName, iconNames } from 'es-components-shared-types'; import getStyledProp, { ESThemeProps } from '../../util/getStyledProp'; -import Icon, { iconBaseStyles, IconBaseProps } from '../../base/icons/Icon'; +import { HiddenIcons, useIconStyles } from '../../base/icons/useIconStyles'; import InputBase, { useValidationStyleProps, validationStateHighlightStyles, @@ -23,90 +17,23 @@ import InputBase, { } from './InputBase'; import { callRefs } from '../../util/callRef'; -type GetComputedStyleFunc = typeof window.getComputedStyle; -type ComputedStyleParams = Parameters; -type ComputedStyleReturn = ReturnType; -type IconElement = HTMLElement; - -const computeStyle = (...args: ComputedStyleParams) => { - try { - return window.getComputedStyle(...args); - } catch (err) { - if ((err as Error)?.message?.match(/Not Implemented/i)) { - return {} as ComputedStyleReturn; - } - throw err; - } -}; - -type IconStyleProps = ValidationStyleProps & IconBaseProps; - -const getIconStyles = (icon: IconElement) => { - const computedStyle = computeStyle(icon); - const beforeStyle = computeStyle(icon, ':before'); - return css` - ${props => css` - ${iconBaseStyles} - - // computed styles - content: ${beforeStyle.content}; - font-family: ${computedStyle.fontFamily}; - font-size: ${computedStyle.fontSize}; - - // themed addon styles - background-color: ${getStyledProp('backgroundColor', 'addOn', props)}; - color: ${getStyledProp('textColor', 'addOn', props)}; - - // base addon styles - box-sizing: border-box; - display: flex; - flex-direction: column; - justify-content: center; - line-height: 1; - margin: 0; - outline: 0; - padding: 0.333em 0.6111em; - `} - `; -}; - -const useIconStyles = ( - iconName: IconName | undefined, - icon: Maybe -) => { - const [iconRecheck, setIconRecheck] = useState(0); - const iconStyles = useMemo( - () => (icon ? getIconStyles(icon) : css``), - // eslint-disable-next-line react-hooks/exhaustive-deps - [icon, iconRecheck] - ); - - useEffect( - function ensureIconRendered() { - if (!iconName || icon) { - return; - } - - requestAnimationFrame(() => setIconRecheck(r => r + 1)); - }, - [iconName, icon] - ); - - return iconStyles; -}; - -type IconStyles = ReturnType; - export type TextboxAdditionProps = { hasPrepend?: boolean; hasAppend?: boolean; }; -export type InputWrapperProps = IconStyleProps & - TextboxAdditionProps & { - prependIconStyles: IconStyles; - appendIconStyles: IconStyles; - }; +type IconStyles = ReturnType; + +export type InputWrapperProps = Override< + ValidationStyleProps, + Override< + TextboxAdditionProps, + { + prependIconStyles?: IconStyles; + appendIconStyles?: IconStyles; + } + > +>; const InputWrapper = styled.div` ${validationStateSetupStyles} @@ -119,19 +46,30 @@ const InputWrapper = styled.div` ${validationStateHighlightStyles} } - ${({ prependIconStyles }) => - css` - &&:before { - ${prependIconStyles} - } - `} + &&:before { + ${props => props.prependIconStyles} + } - ${({ appendIconStyles }) => - css` - &&:after { - ${appendIconStyles} - } + &&:after { + ${props => props.appendIconStyles} + } + + &&:before, + &&:after { + ${props => css` + background-color: ${getStyledProp('backgroundColor', 'addOn', props)}; + color: ${getStyledProp('textColor', 'addOn', props)}; `} + + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: center; + line-height: 1; + margin: 0; + outline: 0; + padding: 0.333em 0.6111em; + } `; export const TextboxBase = styled(InputBase)` @@ -171,21 +109,6 @@ export const TextboxBase = styled(InputBase)` } `; -const Hidden = (props: JSXElementProps<'div'>) => ( -