;
+const EMPTY_THEME = Object.freeze({});
+
const ModalDialogComposer = memo(({ children }: Props) => {
const [renderFunctionAndDialogInit, setRenderFunctionAndDialogInit] = useState<
RenderFunctionAndDialogInit | undefined
@@ -30,14 +33,16 @@ const ModalDialogComposer = memo(({ children }: Props) => {
{children}
{renderFunctionAndDialogInit && (
-
- {renderFunctionAndDialogInit[0]()}
-
+
+
+ {renderFunctionAndDialogInit[0]()}
+
+
)}
);
diff --git a/packages/component/src/providers/ModalDialog/private/Context.ts b/packages/component/src/providers/ModalDialog/private/Context.ts
index 7ba3a66a3c..064a6a6527 100644
--- a/packages/component/src/providers/ModalDialog/private/Context.ts
+++ b/packages/component/src/providers/ModalDialog/private/Context.ts
@@ -1,9 +1,12 @@
+import { type CSSModuleTheme } from '@msinternal/botframework-webchat-styles/react';
import { createContext, type ReactNode } from 'react';
type DialogInit = {
'aria-label'?: string;
'aria-labelledby'?: string;
className?: string;
+ /** Class names for the local names of the dialog's parts (e.g. "modal-dialog__box"), so the caller can style them. */
+ cssModuleTheme?: CSSModuleTheme | undefined;
};
type RenderFunction = () => ReactNode;
diff --git a/packages/component/src/providers/ModalDialog/private/Popover.module.css b/packages/component/src/providers/ModalDialog/private/Popover.module.css
new file mode 100644
index 0000000000..14242398d9
--- /dev/null
+++ b/packages/component/src/providers/ModalDialog/private/Popover.module.css
@@ -0,0 +1,104 @@
+/* TODO: Add a dark theme once we defined the link color and stuff for Markdown. */
+:global(.webchat) .modal-dialog {
+ font-family: var(--webchat__font--primary);
+ max-height: calc(100% - 32px);
+ max-width: calc(100% - 32px);
+ width: 100%;
+
+ @media (forced-colors: none) {
+ background-color: transparent;
+ border: 0;
+ }
+
+ /* Assume the content is being prepared, so we hide the whole dialog until content appears in the DOM. */
+ /* Use opacity to still be able to set focus on the first available control (close button). */
+ &:has(.modal-dialog__body:empty) {
+ opacity: 0;
+ }
+
+ .modal-dialog__box {
+ border-radius: 2px;
+ overflow: hidden;
+ margin: auto;
+
+ @media screen and (max-width: 639px) {
+ max-width: unset;
+ }
+
+ @media screen and (min-width: 640px) {
+ max-width: 60%;
+ min-width: calc(640px - 2em - 6px); /* Browser natively do "100% - 2em - 6px". */
+ width: 60%;
+ }
+
+ @media (forced-colors: none) {
+ /* From Power BI: */
+ /* box-shadow: 0 6.4px 14.4px rgb(0 0 0 / 13%), 0 1.2px 3.6px rgb(0 0 0 / 11%); */
+ /* From Fluent (depth-16 for teaching callouts): */
+ /* box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.14), 0px 0px 2px rgba(0, 0, 0, 0.12); */
+ background-color: White;
+ box-shadow:
+ 0px 8px 16px rgba(0, 0, 0, 0.14),
+ 0px 0px 2px rgba(0, 0, 0, 0.12);
+ }
+
+ @media (forced-colors: active) {
+ /* In high-contrast mode, we use "outline" instead of "box-shadow". */
+ outline-color: ButtonBorder;
+ outline-style: solid;
+ outline-width: 4px;
+ }
+ }
+
+ .modal-dialog__close-button-layout {
+ float: right;
+ padding: var(--webchat__padding--regular);
+ }
+
+ .modal-dialog__close-button {
+ height: 30px;
+ width: 30px;
+ padding: 0;
+
+ --webchat__component-icon--size: 16px;
+
+ @media (forced-colors: none) {
+ appearance: none;
+ background-color: transparent;
+ border-color: transparent;
+ border-radius: 4px;
+ border-style: solid;
+ border-width: 2px;
+ color: #999;
+ outline: 0;
+ }
+ }
+
+ .modal-dialog__close-button:focus {
+ @media (forced-colors: none) {
+ border-color: black;
+
+ &:active {
+ background-color: #edebe9; /* neutralLight (gray30) */
+ }
+
+ &:not(:active):hover {
+ background-color: #f3f2f1; /* neutralLighter (gray20) */
+ }
+ }
+ }
+
+ .modal-dialog__close-button-icon {
+ @media (forced-colors: none) {
+ color: #323130; /* neutralPrimary (gray160) */
+ }
+
+ @media (forced-colors: active) {
+ color: currentcolor;
+ }
+ }
+
+ .modal-dialog__body {
+ margin: calc(var(--webchat__padding--regular) * 2);
+ }
+}
diff --git a/packages/component/src/providers/ModalDialog/private/Popover.tsx b/packages/component/src/providers/ModalDialog/private/Popover.tsx
index 16d02e88aa..df46d23684 100644
--- a/packages/component/src/providers/ModalDialog/private/Popover.tsx
+++ b/packages/component/src/providers/ModalDialog/private/Popover.tsx
@@ -1,65 +1,81 @@
+import { reactNode, validateProps, type InferReactProps } from '@msinternal/botframework-webchat-react-valibot';
+import { useStyles, webChatPrefixedClassName } from '@msinternal/botframework-webchat-styles/react';
import { hooks } from 'botframework-webchat-api';
-import classNames from 'classnames';
-import React, { memo, type PropsWithChildren, useCallback, useLayoutEffect, useRef } from 'react';
+import cx from 'classnames';
+import React, { memo, useCallback, useLayoutEffect, useRef } from 'react';
+import { custom, function_, is, object, optional, pipe, readonly, string } from 'valibot';
import { ComponentIcon } from '../../../Icon';
-import useStyleSet from '../../../hooks/useStyleSet';
+
+import styles from './Popover.module.css';
const { useLocalizer } = hooks;
-type Props = Readonly<
- PropsWithChildren<{
- 'aria-label'?: string;
- 'aria-labelledby'?: string;
- className?: string;
- onDismiss?: () => void;
- }>
->;
+const modalDialogPropsSchema = pipe(
+ object({
+ 'aria-label': optional(string()),
+ 'aria-labelledby': optional(string()),
+ children: optional(reactNode()),
+ className: optional(string()),
+ onDismiss: optional(custom<() => void>(value => is(function_(), value)))
+ }),
+ readonly()
+);
+
+type ModalDialogProps = InferReactProps
;
-const ModalDialog = memo(
- ({ 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, children, className, onDismiss }: Props) => {
- const [{ modalDialog: modalDialogStyleSet }] = useStyleSet();
- const dialogRef = useRef(null);
- const localize = useLocalizer();
+function ModalDialog(props: ModalDialogProps) {
+ const {
+ 'aria-label': ariaLabel,
+ 'aria-labelledby': ariaLabelledBy,
+ children,
+ className,
+ onDismiss
+ } = validateProps(modalDialogPropsSchema, props);
- const closeButtonAlt = localize('CLOSE_BUTTON_ALT');
+ // Keeps the "webchat__modal-dialog" class names.
+ const classNames = useStyles(styles, webChatPrefixedClassName);
+ const dialogRef = useRef(null);
+ const localize = useLocalizer();
- const handleCloseButtonClick = useCallback(() => dialogRef.current?.close(), [dialogRef]);
+ const closeButtonAlt = localize('CLOSE_BUTTON_ALT');
- useLayoutEffect(() => dialogRef.current?.showModal(), [dialogRef]);
+ const handleCloseButtonClick = useCallback(() => dialogRef.current?.close(), [dialogRef]);
- return (
-
-
- {/* The __scrollable layer is for hiding scrollbar at corners.
- Without this layer, the scrollbar will show and overflow the border-radius.
+ useLayoutEffect(() => dialogRef.current?.showModal(), [dialogRef]);
+
+ return (
+
+
+ {/* The __scrollable layer is for hiding scrollbar at corners.
+ Without this layer, the scrollbar will show and overflow the border-radius.
This impact will be more visible if we temporarily set border-radius: 20px. */}
-
-
-
-
-
-
{children}
+
+
+
+
-
- );
- }
-);
+
{children}
+
+
+ );
+}
ModalDialog.displayName = 'ModalDialog';
-export default ModalDialog;
+export default memo(ModalDialog);
+export { modalDialogPropsSchema, type ModalDialogProps };
diff --git a/packages/fluent-theme/src/components/theme/Theme.module.css b/packages/fluent-theme/src/components/theme/Theme.module.css
index ebb0305177..c0e9344c1a 100644
--- a/packages/fluent-theme/src/components/theme/Theme.module.css
+++ b/packages/fluent-theme/src/components/theme/Theme.module.css
@@ -187,129 +187,13 @@
--webchat-curveDecelerateMid: var(--curveDecelerateMid, cubic-bezier(0, 0, 0, 1));
}
+.variant-copilot {
+ --webchat-preserve-reason: target-class;
+}
+
@media (prefers-reduced-motion) {
:global(.webchat-fluent).theme {
--webchat-durationUltraFast: 0.01ms;
--webchat-durationNormal: 0.01ms;
}
}
-
-/* Style Options override */
-:global(.webchat-fluent).theme :global(.webchat__css-custom-properties) {
- --webchat__color--accent: var(--webchat-colorBrandForeground1);
- --webchat__color--subtle: var(--webchat-colorNeutralForeground2);
- --webchat__color--timestamp: var(--webchat-colorNeutralForeground3);
- --webchat__font--primary: var(--webchat-fontFamilyBase);
- --webchat__font-size--small: var(--webchat-fontSizeBase200);
- --webchat__font-size--medium: var(--webchat-fontSizeBase300);
- --webchat__line-height--small: var(--webchat-lineHeightBase200);
- --webchat__line-height--medium: var(--webchat-lineHeightBase300);
- --webchat__max-width--bubble: var(--webchat-bubble-maxWidth);
- --webchat__max-width--attachment-bubble: var(--webchat-bubble-maxWidth);
- --webchat__min-height--bubble: var(--webchat-bubble-minHeight);
- --webchat__padding--regular: var(--webchat-spacingVerticalS);
- --webchat__padding--sendbox: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalMNudge)
- var(--webchat-spacingHorizontalMNudge);
- --webchat__transition-duration: var(--webchat-durationNormal);
- --webchat__transition-easing: var(--webchat-curveDecelerateMid);
- --webchat__border-animation--color-1: #464feb;
- --webchat__border-animation--color-2: #47cffa;
- --webchat__border-animation--color-3: #b47cf8;
-}
-
-:global(.webchat-fluent).theme.variant-copilot :global(.webchat__css-custom-properties) {
- --webchat__padding--sendbox: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalL)
- var(--webchat-spacingVerticalL) var(--webchat-spacingHorizontalSNudge);
- --webchat__max-height--image-bubble: max(480px, 90cqb);
-}
-
-/* Transcript surface */
-:global(.webchat-fluent).theme :global(.webchat__surface) {
- background-color: var(--webchat__color--surface);
-}
-
-:global(.webchat-fluent).theme.variant-copilot :global(.webchat__surface) {
- box-sizing: border-box;
- padding-inline-start: var(--webchat-spacingHorizontalMNudge);
-}
-
-:global(.webchat-fluent).theme.variant-copilot
- :global(.webchat__basic-transcript .webchat__basic-transcript__scrollable) {
- scrollbar-gutter: stable;
-}
-
-/* Transcript filer in copilot variant */
-:global(.webchat-fluent).theme.variant-copilot :global(.webchat__basic-transcript .webchat__basic-transcript__filler) {
- /* No filler unless pre-chat activity, see next rule */
- flex-grow: 0;
-}
-
-/* Transcript with a single pre-chat activity */
-:global(.webchat-fluent).theme
- :global(
- .webchat__basic-transcript:has(.part-grouping-decorator:only-child):has(.pre-chat-message-activity)
- .webchat__basic-transcript__filler
- ) {
- /* limit grow to half of the size to center the activity */
- flex-grow: 0.5;
-}
-
-/* Modal */
-:global(.webchat-fluent).theme :global(.webchat__modal-dialog) {
- height: 100%;
- display: grid;
- place-items: center;
-
- &::backdrop {
- background-color: var(--webchat-colorBackgroundOverlay);
- }
-
- :global(.webchat__modal-dialog__box) {
- background-color: var(--webchat-colorNeutralBackground1);
- border-radius: var(--webchat-borderRadiusXLarge);
- position: relative;
- color: var(--webchat-colorNeutralForeground1);
- box-shadow: var(--webchat-shadow64);
- }
-
- :global(.webchat__modal-dialog__close-button) {
- color: var(--webchat-colorNeutralForeground1);
- transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid);
- }
-
- /* TODO: align with original fluent modal styles in WebChat as they don't match v9 anymore */
- :global(.webchat__modal-dialog__close-button):not(:active):hover {
- background-color: var(--webchat-colorNeutralBackground3);
- }
-
- :global(.webchat__modal-dialog__close-button):focus:active {
- background-color: var(--webchat-colorNeutralBackground4);
- }
-
- :global(.webchat__modal-dialog__close-button):not(:focus-visible) {
- border: none;
- }
-
- :global(.webchat__modal-dialog__close-button):focus {
- border-color: var(--webchat-colorStrokeFocus2);
- }
-
- :global(.webchat__modal-dialog__close-button-icon) {
- color: currentColor;
- }
-}
-
-/* Tooltip */
-:global(.webchat-fluent).theme :global(.webchat__tooltip) {
- --webchat__tooltip-background: var(--tooltip-background, var(--webchat-colorNeutralBackground1));
-
- color: var(--webchat-colorNeutralForeground1);
- filter: drop-shadow(0 0 2px var(--webchat-colorNeutralShadowAmbient))
- drop-shadow(0 4px 8px var(--webchat-colorNeutralShadowKey));
- font-family: var(--webchat-fontFamilyBase);
- font-size: var(--webchat-fontSizeBase200);
- font-weight: var(--webchat-fontWeightRegular);
- line-height: var(--webchat-lineHeightBase200);
- padding: var(--webchat-spacingVerticalSNudge) var(--webchat-spacingHorizontalM);
- transition: opacity var(--webchat-durationNormal) var(--webchat-curveDecelerateMid);
-}
diff --git a/packages/fluent-theme/src/private/fluentCSSModuleTheme.module.css b/packages/fluent-theme/src/private/fluentCSSModuleTheme.module.css
index f7b4e3775d..64202c66ce 100644
--- a/packages/fluent-theme/src/private/fluentCSSModuleTheme.module.css
+++ b/packages/fluent-theme/src/private/fluentCSSModuleTheme.module.css
@@ -29,23 +29,23 @@
}
}
- .stacked-layout .bubble :global(.webchat__fileContent__badge) {
+ .stacked-layout .bubble .fileContent__badge {
cursor: default;
font-size: var(--webchat-fontSizeBase300);
line-height: var(--webchat-lineHeightBase300);
- :global(.webchat__fileContent__fileName) {
+ .fileContent__fileName {
color: var(--webchat-colorBrandForegroundLink);
font-family: inherit;
}
- :global(.webchat__fileContent__size) {
+ .fileContent__size {
color: var(--webchat-colorNeutralForeground2);
font-family: inherit;
}
}
- .stacked-layout .bubble :global(.webchat__fileContent__downloadIcon) {
+ .stacked-layout .bubble .fileContent__downloadIcon {
color: var(--webchat-colorBrandForegroundLink);
}
/* #endregion */
@@ -973,11 +973,50 @@
/* #region Theme */
+/* Style Options override */
+:global(.webchat-fluent).theme .css-custom-properties {
+ --webchat__color--accent: var(--webchat-colorBrandForeground1);
+ --webchat__color--subtle: var(--webchat-colorNeutralForeground2);
+ --webchat__color--timestamp: var(--webchat-colorNeutralForeground3);
+ --webchat__font--primary: var(--webchat-fontFamilyBase);
+ --webchat__font-size--small: var(--webchat-fontSizeBase200);
+ --webchat__font-size--medium: var(--webchat-fontSizeBase300);
+ --webchat__line-height--small: var(--webchat-lineHeightBase200);
+ --webchat__line-height--medium: var(--webchat-lineHeightBase300);
+ --webchat__max-width--bubble: var(--webchat-bubble-maxWidth);
+ --webchat__max-width--attachment-bubble: var(--webchat-bubble-maxWidth);
+ --webchat__min-height--bubble: var(--webchat-bubble-minHeight);
+ --webchat__padding--regular: var(--webchat-spacingVerticalS);
+ --webchat__padding--sendbox: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalMNudge)
+ var(--webchat-spacingHorizontalMNudge);
+ --webchat__transition-duration: var(--webchat-durationNormal);
+ --webchat__transition-easing: var(--webchat-curveDecelerateMid);
+ --webchat__border-animation--color-1: #464feb;
+ --webchat__border-animation--color-2: #47cffa;
+ --webchat__border-animation--color-3: #b47cf8;
+}
+
+:global(.webchat-fluent).theme.variant-copilot .css-custom-properties {
+ --webchat__padding--sendbox: var(--webchat-spacingVerticalNone) var(--webchat-spacingHorizontalL)
+ var(--webchat-spacingVerticalL) var(--webchat-spacingHorizontalSNudge);
+ --webchat__max-height--image-bubble: max(480px, 90cqb);
+}
+
+/* Transcript surface */
+:global(.webchat-fluent).theme .surface {
+ background-color: var(--webchat__color--surface);
+}
+
+:global(.webchat-fluent).theme.variant-copilot .surface {
+ box-sizing: border-box;
+ padding-inline-start: var(--webchat-spacingHorizontalMNudge);
+}
+
/* Scrollbars */
:global(.webchat-fluent).theme .code-block-content .code-block-content__code-block,
:global(.webchat-fluent).theme .send-box-attachment-bar.send-box-attachment-bar--as-list-item,
:global(.webchat-fluent).theme .text-area.text-area--scroll,
-:global(.webchat-fluent).theme :global(.webchat__basic-transcript .webchat__basic-transcript__scrollable),
+:global(.webchat-fluent).theme .basic-transcript .basic-transcript__scrollable,
:global(.webchat-fluent).theme .render-markdown [data-math-type='block'],
:global(.webchat-fluent).theme .view-code-dialog__body,
:global(.webchat-fluent).theme :global(.view-code-dialog__code-body) {
@@ -1029,6 +1068,10 @@
--webchat__transcript--spacing: var(--webchat-spacingVerticalM);
}
+:global(.webchat-fluent).theme.variant-copilot .basic-transcript .basic-transcript__scrollable {
+ scrollbar-gutter: stable;
+}
+
/* Transcript focus indicator color */
:global(.webchat-fluent).theme .transcript-focus-area__transcript-indicator {
--webchat__color--transcript-visual-keyboard-indicator: var(--webchat-colorStrokeFocus2);
@@ -1056,7 +1099,7 @@
}
/* Transcript activity focused directly */
- > :global(.webchat__focus-trap) > :global(.webchat__basic-transcript__activity-body) .bubble {
+ > .focus-trap > .basic-transcript__activity-body .bubble {
display: grid;
grid-template-areas: 'focused-content';
position: static;
@@ -1124,6 +1167,67 @@
}
}
+/* Transcript filer in copilot variant */
+:global(.webchat-fluent).theme.variant-copilot .basic-transcript .basic-transcript__filler {
+ /* No filler unless pre-chat activity, see next rule */
+ flex-grow: 0;
+}
+
+/* Transcript with a single pre-chat activity */
+:global(.webchat-fluent).theme
+ .basic-transcript:has(:global(.part-grouping-decorator):only-child):has(:global(.pre-chat-message-activity))
+ .basic-transcript__filler {
+ /* limit grow to half of the size to center the activity */
+ flex-grow: 0.5;
+}
+
+/* Modal */
+:global(.webchat-fluent).theme .modal-dialog {
+ height: 100%;
+ display: grid;
+ place-items: center;
+
+ &::backdrop {
+ background-color: var(--webchat-colorBackgroundOverlay);
+ }
+
+ .modal-dialog__box {
+ background-color: var(--webchat-colorNeutralBackground1);
+ border-radius: var(--webchat-borderRadiusXLarge);
+ position: relative;
+ color: var(--webchat-colorNeutralForeground1);
+ box-shadow: var(--webchat-shadow64);
+ }
+
+ .modal-dialog__close-button {
+ color: var(--webchat-colorNeutralForeground1);
+ transition: background-color var(--webchat-durationNormal) var(--webchat-curveDecelerateMid);
+ }
+
+ /* TODO: align with original fluent modal styles in WebChat as they don't match v9 anymore */
+ /* Popover.module.css can come later in the stylesheet: the ":focus" selector out-specifies its focused hover rule. */
+ .modal-dialog__close-button:not(:active):hover,
+ .modal-dialog__close-button:focus:not(:active):hover {
+ background-color: var(--webchat-colorNeutralBackground3);
+ }
+
+ .modal-dialog__close-button:focus:active {
+ background-color: var(--webchat-colorNeutralBackground4);
+ }
+
+ .modal-dialog__close-button:not(:focus-visible) {
+ border: none;
+ }
+
+ .modal-dialog__close-button:focus {
+ border-color: var(--webchat-colorStrokeFocus2);
+ }
+
+ .modal-dialog__close-button-icon {
+ color: currentColor;
+ }
+}
+
/* Code block */
:global(.webchat-fluent).theme .code-block {
border: none;
@@ -1205,13 +1309,13 @@
}
/* View Code dialog */
-:global(.webchat-fluent).theme :global(.webchat__modal-dialog).view-code-dialog {
- :global(.webchat__modal-dialog__close-button-layout) {
+:global(.webchat-fluent).theme .modal-dialog.view-code-dialog {
+ .modal-dialog__close-button-layout {
display: flex;
padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalS);
}
- :global(.webchat__modal-dialog__close-button) {
+ .modal-dialog__close-button {
align-items: center;
aspect-ratio: 1;
display: flex;
@@ -1231,11 +1335,11 @@
top: 0;
}
- :global(.webchat__modal-dialog__box) {
+ .modal-dialog__box {
width: fit-content;
}
- :global(.webchat__modal-dialog__body) {
+ .modal-dialog__body {
margin: 0;
padding: var(--webchat-spacingVerticalS) var(--webchat-spacingHorizontalM);
}
@@ -1355,6 +1459,21 @@
}
}
+/* Tooltip */
+:global(.webchat-fluent).theme .tooltip {
+ --webchat__tooltip-background: var(--tooltip-background, var(--webchat-colorNeutralBackground1));
+
+ color: var(--webchat-colorNeutralForeground1);
+ filter: drop-shadow(0 0 2px var(--webchat-colorNeutralShadowAmbient))
+ drop-shadow(0 4px 8px var(--webchat-colorNeutralShadowKey));
+ font-family: var(--webchat-fontFamilyBase);
+ font-size: var(--webchat-fontSizeBase200);
+ font-weight: var(--webchat-fontWeightRegular);
+ line-height: var(--webchat-lineHeightBase200);
+ padding: var(--webchat-spacingVerticalSNudge) var(--webchat-spacingHorizontalM);
+ transition: opacity var(--webchat-durationNormal) var(--webchat-curveDecelerateMid);
+}
+
/* Feedback Form */
:global(.webchat-fluent).theme .feedback-form {
.feedback-form__form-header {
diff --git a/packages/vibe-grep/src/rules/lightning-css.yaml b/packages/vibe-grep/src/rules/lightning-css.yaml
index a163f6b026..c1e5e2ea0b 100644
--- a/packages/vibe-grep/src/rules/lightning-css.yaml
+++ b/packages/vibe-grep/src/rules/lightning-css.yaml
@@ -3,6 +3,7 @@
# - Starting from .webchat or .webchat-
# - Followed by optional modifier class
# - Followed by zero or more spaces
+# - Followed by an optional ":has(> ", for a rule on the parent of a class (e.g. the tooltip)
# - Followed by a class selector with a 6-character file hash
# - Followed by an underscore
@@ -11,7 +12,7 @@ language: Css
rule:
kind: selectors > descendant_selector
not:
- regex: "\\.(webchat|webchat-\\w*)(\\.\\w+--[\\w-]+)?\\s*\\.w[a-zA-Z0-9\\-_]{6}_"
+ regex: "\\.(webchat|webchat-\\w*)(\\.\\w+--[\\w-]+)?\\s*(:has\\(>\\s*)?\\.w[a-zA-Z0-9\\-_]{6}_"
description: "Check if CSS is compiled by Lightning CSS plugin conforms to expected selector pattern"
message: "Output CSS does not conform to Lightning CSS selector pattern. Check that the CSS file is compiled correctly."