diff --git a/packages/component/src/Composer.tsx b/packages/component/src/Composer.tsx index 7fc814deee..9eb1a5b88f 100644 --- a/packages/component/src/Composer.tsx +++ b/packages/component/src/Composer.tsx @@ -53,14 +53,14 @@ type ComposerCoreUIProps = { }; const ComposerCoreUI = memo(({ children }: ComposerCoreUIProps) => { - const [{ cssVariables }] = useStyleSet(); + const [{ cssCustomProperties }] = useStyleSet(); const dictationOnError = useCallback(err => { console.error(err); }, []); return ( -
+
{/* When is finalized, it will be using an independent instance that lives inside . */} diff --git a/packages/component/src/Styles/CSSTokens.ts b/packages/component/src/Styles/CSSTokens.ts new file mode 100644 index 0000000000..2710c63a5a --- /dev/null +++ b/packages/component/src/Styles/CSSTokens.ts @@ -0,0 +1,18 @@ +import CustomPropertyNames from './CustomPropertyNames'; + +type CustomPropertyNamesType = typeof CustomPropertyNames; + +type CSSTokensType>> = { + [K in keyof T]: `var(${T[K]})`; +}; + +// To add/remove/update a token, go to `CustomPropertyName.ts`. +const CSSTokens = new Proxy>({} as CSSTokensType, { + get(_, key: keyof CustomPropertyNamesType) { + // We already checked in the `CustomPropertyName`. + // eslint-disable-next-line security/detect-object-injection + return `var(${CustomPropertyNames[key]})`; + } +}); + +export default CSSTokens; diff --git a/packages/component/src/Styles/CustomPropertyName.ts b/packages/component/src/Styles/CustomPropertyName.ts deleted file mode 100644 index 9f75471c28..0000000000 --- a/packages/component/src/Styles/CustomPropertyName.ts +++ /dev/null @@ -1,25 +0,0 @@ -type WebChatCustomPropertyName = `--webchat__${string}`; - -const ColorAccent: WebChatCustomPropertyName = '--webchat__color--accent'; -const ColorTimestamp: WebChatCustomPropertyName = '--webchat__color--timestamp'; -const FontPrimary: WebChatCustomPropertyName = '--webchat__font--primary'; -const FontSizeSmall: WebChatCustomPropertyName = '--webchat__font-size--small'; -const IconURLExternalLink: WebChatCustomPropertyName = '--webchat__icon-url--external-link'; -const MaxWidthBubble: WebChatCustomPropertyName = '--webchat__max-width--bubble'; -const MinHeightBubble: WebChatCustomPropertyName = '--webchat__min-height--bubble'; -const PaddingRegular: WebChatCustomPropertyName = '--webchat__padding--regular'; - -// TODO: Unsure if we should export this type. -// This is because web devs are going to use CSS stylesheet to override us, -// they are working on CSS rather than JS. - -export { - ColorAccent, - ColorTimestamp, - FontPrimary, - FontSizeSmall, - IconURLExternalLink, - MaxWidthBubble, - MinHeightBubble, - PaddingRegular -}; diff --git a/packages/component/src/Styles/CustomPropertyNames.ts b/packages/component/src/Styles/CustomPropertyNames.ts new file mode 100644 index 0000000000..7bae2a59a0 --- /dev/null +++ b/packages/component/src/Styles/CustomPropertyNames.ts @@ -0,0 +1,16 @@ +const CustomPropertyNames = Object.freeze({ + // Make sure key names does not have JavaScript forbidden names. + ColorAccent: '--webchat__color--accent', + ColorTimestamp: '--webchat__color--timestamp', + FontPrimary: '--webchat__font--primary', + FontSizeSmall: '--webchat__font-size--small', + IconURLExternalLink: '--webchat__icon-url--external-link', + MaxWidthBubble: '--webchat__max-width--bubble', + MinHeightBubble: '--webchat__min-height--bubble', + PaddingRegular: '--webchat__padding--regular' +} as const); + +// This is for type-checking only to make sure the CSS custom property names is `--webchat__${string}`. +const _TypeChecking: Readonly> = CustomPropertyNames; + +export default CustomPropertyNames; diff --git a/packages/component/src/Styles/StyleSet/CSSVariables.ts b/packages/component/src/Styles/StyleSet/CSSCustomProperties.ts similarity index 61% rename from packages/component/src/Styles/StyleSet/CSSVariables.ts rename to packages/component/src/Styles/StyleSet/CSSCustomProperties.ts index 7aaedb7ec2..a97816d6e6 100644 --- a/packages/component/src/Styles/StyleSet/CSSVariables.ts +++ b/packages/component/src/Styles/StyleSet/CSSCustomProperties.ts @@ -1,7 +1,8 @@ import { StrictStyleOptions } from 'botframework-webchat-api'; -import * as CustomPropertyName from '../CustomPropertyName'; -export default function createCSSVariablesStyle({ +import CustomPropertyNames from '../CustomPropertyNames'; + +export default function createCSSCustomPropertiesStyle({ accent, bubbleMaxWidth, bubbleMinHeight, @@ -13,7 +14,7 @@ export default function createCSSVariablesStyle({ timestampColor }: StrictStyleOptions) { return { - '&.webchat__css-variables': { + '&.webchat__css-custom-properties': { display: 'contents', // TODO: Should we register the CSS property for inheritance, type checking, and initial value? @@ -31,14 +32,14 @@ export default function createCSSVariablesStyle({ // - We should put styling varibles here, e.g. paddingRegular // - We MUST NOT put runtime variables here, e.g. sendTimeout // - This is because we cannot programmatically know when the sendTimeout change - [CustomPropertyName.ColorAccent]: accent, - [CustomPropertyName.ColorTimestamp]: timestampColor || subtle, // Maybe we should not need this if we allow web devs to override CSS variables for certain components. - [CustomPropertyName.FontPrimary]: primaryFont, - [CustomPropertyName.FontSizeSmall]: fontSizeSmall, - [CustomPropertyName.IconURLExternalLink]: markdownExternalLinkIconImage, - [CustomPropertyName.MaxWidthBubble]: bubbleMaxWidth + 'px', - [CustomPropertyName.MinHeightBubble]: bubbleMinHeight + 'px', - [CustomPropertyName.PaddingRegular]: paddingRegular + 'px' + [CustomPropertyNames.ColorAccent]: accent, + [CustomPropertyNames.ColorTimestamp]: timestampColor || subtle, // Maybe we should not need this if we allow web devs to override CSS variables for certain components. + [CustomPropertyNames.FontPrimary]: primaryFont, + [CustomPropertyNames.FontSizeSmall]: fontSizeSmall, + [CustomPropertyNames.IconURLExternalLink]: markdownExternalLinkIconImage, + [CustomPropertyNames.MaxWidthBubble]: bubbleMaxWidth + 'px', + [CustomPropertyNames.MinHeightBubble]: bubbleMinHeight + 'px', + [CustomPropertyNames.PaddingRegular]: paddingRegular + 'px' } }; } diff --git a/packages/component/src/Styles/StyleSet/LinkDefinitions.ts b/packages/component/src/Styles/StyleSet/LinkDefinitions.ts index 11a02b68cd..9186ccdddc 100644 --- a/packages/component/src/Styles/StyleSet/LinkDefinitions.ts +++ b/packages/component/src/Styles/StyleSet/LinkDefinitions.ts @@ -5,7 +5,8 @@ import { NOT_FORCED_COLORS_SELECTOR } from './Constants'; -// TODO: Fix CSS var. +import CSSTokens from '../CSSTokens'; + export default function createLinkDefinitionsStyleSet() { return { '&.webchat__link-definitions': { @@ -28,10 +29,6 @@ export default function createLinkDefinitionsStyleSet() { } }, - // '.webchat__link-definitions__header-chevron': { - // verticalAlign: 'middle' - // }, - '&:not([open]) .webchat__link-definitions__header-chevron': { marginBottom: '-0.1em', transform: 'rotate(-180deg)' @@ -130,7 +127,7 @@ export default function createLinkDefinitionsStyleSet() { padding: 4, [NOT_FORCED_COLORS_SELECTOR]: { - color: 'var(--webchat__color--accent)' + color: CSSTokens.ColorAccent } }, diff --git a/packages/component/src/Styles/StyleSet/ModalDialog.ts b/packages/component/src/Styles/StyleSet/ModalDialog.ts index ceac1f2e6a..be7a5e2ee2 100644 --- a/packages/component/src/Styles/StyleSet/ModalDialog.ts +++ b/packages/component/src/Styles/StyleSet/ModalDialog.ts @@ -5,10 +5,12 @@ import { NOT_FORCED_COLORS_SELECTOR } from './Constants'; +import CSSTokens from '../CSSTokens'; + export default function createModalDialogStyleSet() { return { '&.webchat__modal-dialog': { - fontFamily: 'var(--webchat__font--primary)', + fontFamily: CSSTokens.FontPrimary, width: '100%', [NOT_FORCED_COLORS_SELECTOR]: { @@ -18,11 +20,11 @@ export default function createModalDialogStyleSet() { '& .webchat__modal-dialog__box': { borderRadius: 2, - height: 'calc(100% - var(--webchat__padding--regular) * 2)', + height: `calc(100% - ${CSSTokens.PaddingRegular} * 2)`, overflow: 'hidden', margin: 'auto', maxWidth: '60%', - width: 'calc(100% - var(--webchat__padding--regular) * 2)', + width: `calc(100% - ${CSSTokens.PaddingRegular} * 2)`, [LIGHT_THEME_SELECTOR]: { // From Power BI: @@ -49,7 +51,7 @@ export default function createModalDialogStyleSet() { '& .webchat__modal-dialog__close-button-layout': { float: 'right', - padding: 'var(--webchat__padding--regular)' + padding: CSSTokens.PaddingRegular }, '& .webchat__modal-dialog__close-button': { @@ -112,7 +114,7 @@ export default function createModalDialogStyleSet() { }, '& .webchat__modal-dialog__body': { - margin: 'calc(var(--webchat__padding--regular) * 2)' + margin: `calc(${CSSTokens.PaddingRegular} * 2)` } } }; diff --git a/packages/component/src/Styles/StyleSet/OriginatorActivityStatus.ts b/packages/component/src/Styles/StyleSet/OriginatorActivityStatus.ts deleted file mode 100644 index 12483eef42..0000000000 --- a/packages/component/src/Styles/StyleSet/OriginatorActivityStatus.ts +++ /dev/null @@ -1,14 +0,0 @@ -export default function createOriginatorActivityStatusStyle() { - return { - '&.webchat__originator-activity-status': { - alignItems: 'center', - /* These are the fonts used in Web Chat default style options. */ - fontFamily: 'var(--webchat__font--primary)', - fontSize: '80%', - - '&.webchat__originator-activity-status--link': { - color: 'var(--webchat__color--accent)' - } - } - }; -} diff --git a/packages/component/src/Styles/StyleSet/RenderMarkdown.ts b/packages/component/src/Styles/StyleSet/RenderMarkdown.ts index 92058a335f..152cddc2f3 100644 --- a/packages/component/src/Styles/StyleSet/RenderMarkdown.ts +++ b/packages/component/src/Styles/StyleSet/RenderMarkdown.ts @@ -1,6 +1,5 @@ -/* eslint no-magic-numbers: "off" */ - import { FORCED_COLORS_SELECTOR, NOT_FORCED_COLORS_SELECTOR } from './Constants'; +import CSSTokens from '../CSSTokens'; // This style is for accompanying result of `renderMarkdown()`. // Mostly, it should only styles elements that are generated/modified during `renderMarkdown()`. @@ -9,7 +8,7 @@ export default function createMarkdownStyle() { return { '&.webchat__render-markdown': { '& .webchat__render-markdown__external-link-icon': { - backgroundImage: 'var(--webchat__icon-url--external-link)', + backgroundImage: CSSTokens.IconURLExternalLink, height: '.75em', marginLeft: '.25em' }, @@ -27,7 +26,7 @@ export default function createMarkdownStyle() { }, [NOT_FORCED_COLORS_SELECTOR]: { - color: 'var(--webchat__color--accent)' + color: CSSTokens.ColorAccent } }, diff --git a/packages/component/src/Styles/StyleSet/SendStatus.ts b/packages/component/src/Styles/StyleSet/SendStatus.ts index 0b26232b2d..7d585747a9 100644 --- a/packages/component/src/Styles/StyleSet/SendStatus.ts +++ b/packages/component/src/Styles/StyleSet/SendStatus.ts @@ -1,10 +1,12 @@ +import CSSTokens from '../CSSTokens'; + export default function createSendStatusStyle() { return { '&.webchat__activity-status': { - color: 'var(--webchat__color--timestamp)', - fontFamily: 'var(--webchat__font--primary)', - fontSize: 'var(--webchat__font-size--small)', - marginTop: 'calc(var(--webchat__padding--regular) / 2)' + color: CSSTokens.ColorTimestamp, + fontFamily: CSSTokens.FontPrimary, + fontSize: CSSTokens.FontSizeSmall, + marginTop: `calc(${CSSTokens.PaddingRegular} / 2)` }, '&.webchat__activity-status--slotted': { @@ -16,7 +18,7 @@ export default function createSendStatusStyle() { alignItems: 'center', '&.webchat__activity-status__originator--has-link': { - color: 'var(--webchat__color--accent)' + color: CSSTokens.ColorAccent } } }; diff --git a/packages/component/src/Styles/StyleSet/SlottedActivityStatus.ts b/packages/component/src/Styles/StyleSet/SlottedActivityStatus.ts index cc6feff4c9..df5f000e59 100644 --- a/packages/component/src/Styles/StyleSet/SlottedActivityStatus.ts +++ b/packages/component/src/Styles/StyleSet/SlottedActivityStatus.ts @@ -1,13 +1,15 @@ +import CSSTokens from '../CSSTokens'; + export default function createSlottedActivityStatus() { return { '&.webchat__slotted-activity-status': { alignItems: 'center', display: 'inline-flex', gap: 4, - marginTop: 'calc(var(--webchat__padding--regular) / 2)', + marginTop: `calc(${CSSTokens.PaddingRegular} / 2)`, '& .webchat__slotted-activity-status__pipe': { - fontSize: 'var(--webchat__font-size--small)' + fontSize: CSSTokens.FontSizeSmall } } }; diff --git a/packages/component/src/Styles/StyleSet/TextContent.ts b/packages/component/src/Styles/StyleSet/TextContent.ts index 6fa8195ea2..f871c522d2 100644 --- a/packages/component/src/Styles/StyleSet/TextContent.ts +++ b/packages/component/src/Styles/StyleSet/TextContent.ts @@ -1,17 +1,17 @@ -/* eslint no-magic-numbers: "off" */ +import CSSTokens from '../CSSTokens'; export default function createTextContentStyle() { return { '&.webchat__text-content': { - fontFamily: 'var(--webchat__font--primary)', + fontFamily: CSSTokens.FontPrimary, margin: 0, - minHeight: 'calc(var(--webchat__min-height--bubble) - var(--webchat__padding--regular) * 2)', - padding: 'var(--webchat__padding--regular)', + minHeight: `calc(${CSSTokens.MinHeightBubble} - ${CSSTokens.PaddingRegular} * 2)`, + padding: CSSTokens.PaddingRegular, '&.webchat__text-content--is-markdown': { display: 'flex', flexDirection: 'column', - gap: 'var(--webchat__padding--regular)' + gap: CSSTokens.PaddingRegular }, '& .webchat__text-content__markdown > :first-child': { @@ -23,7 +23,7 @@ export default function createTextContentStyle() { }, '& .webchat__text-content__markdown img:not(.webchat__render-markdown__external-link-icon)': { - maxWidth: 'var(--webchat__max-width--bubble)', + maxWidth: CSSTokens.MaxWidthBubble, width: '100%' }, diff --git a/packages/component/src/Styles/StyleSet/ThumbButton.ts b/packages/component/src/Styles/StyleSet/ThumbButton.ts index 54ee9d3c7e..7f5eba727e 100644 --- a/packages/component/src/Styles/StyleSet/ThumbButton.ts +++ b/packages/component/src/Styles/StyleSet/ThumbButton.ts @@ -1,3 +1,5 @@ +import CSSTokens from '../CSSTokens'; + export default function () { return { '&.webchat__thumb-button': { @@ -21,7 +23,7 @@ export default function () { }, '& .webchat__thumb-button__image': { - color: 'var(--webchat__color--accent)', + color: CSSTokens.ColorAccent, width: 14 }, diff --git a/packages/component/src/Styles/createStyleSet.ts b/packages/component/src/Styles/createStyleSet.ts index 74907fb81c..8039614d0a 100644 --- a/packages/component/src/Styles/createStyleSet.ts +++ b/packages/component/src/Styles/createStyleSet.ts @@ -11,7 +11,7 @@ import createCarouselFilmStrip from './StyleSet/CarouselFilmStrip'; import createCarouselFilmStripAttachment from './StyleSet/CarouselFilmStripAttachment'; import createCarouselFlipper from './StyleSet/CarouselFlipper'; import createConnectivityNotification from './StyleSet/ConnectivityNotification'; -import createCSSVariablesStyle from './StyleSet/CSSVariables'; +import createCSSCustomPropertiesStyle from './StyleSet/CSSCustomProperties'; import createDictationInterimsStyle from './StyleSet/DictationInterims'; import createErrorBoxStyle from './StyleSet/ErrorBox'; import createErrorNotificationStyle from './StyleSet/ErrorNotification'; @@ -99,7 +99,7 @@ export default function createStyleSet(styleOptions: StyleOptions) { // Following styles follows new house rules: // - Use CSS var instead of strictStyleOptions - cssVariables: createCSSVariablesStyle(strictStyleOptions), + cssCustomProperties: createCSSCustomPropertiesStyle(strictStyleOptions), linkDefinitions: createLinkDefinitionsStyle(), modalDialog: createModalDialogStyle(), renderMarkdown: createRenderMarkdownStyle(),