diff --git a/UNRELEASED.md b/UNRELEASED.md
index f75f6f4aa88..a36999211ab 100644
--- a/UNRELEASED.md
+++ b/UNRELEASED.md
@@ -4,6 +4,7 @@
### Enhancements
+- Added `showFocusBorder` prop to the `TopBar.SearchField` to allow users to add show a border on focus ([#2886](https://github.com/Shopify/polaris-react/pull/2886)).
- Added a theme prop for `frameOffset` ([#2887](https://github.com/Shopify/polaris-react/pull/2887))
### Bug fixes
diff --git a/src/components/ThemeProvider/tests/ThemeProvider.test.tsx b/src/components/ThemeProvider/tests/ThemeProvider.test.tsx
index a6d458cb3a2..9469f811cae 100644
--- a/src/components/ThemeProvider/tests/ThemeProvider.test.tsx
+++ b/src/components/ThemeProvider/tests/ThemeProvider.test.tsx
@@ -93,6 +93,7 @@ describe('', () => {
'--top-bar-background': '#108043',
'--top-bar-background-lighter': 'hsla(147, 63%, 43%, 1)',
'--top-bar-color': 'rgb(255, 255, 255)',
+ '--top-bar-border': 'rgb(196, 205, 213)',
}),
});
});
@@ -127,6 +128,7 @@ describe('', () => {
'--top-bar-background': '#021123',
'--top-bar-background-lighter': 'hsla(213, 74%, 22%, 1)',
'--top-bar-color': 'rgb(255, 255, 255)',
+ '--top-bar-border': 'rgb(196, 205, 213)',
}),
});
});
diff --git a/src/components/TopBar/README.md b/src/components/TopBar/README.md
index 060d1e904b8..e3924aeca13 100644
--- a/src/components/TopBar/README.md
+++ b/src/components/TopBar/README.md
@@ -220,6 +220,7 @@ function TopBarExample() {
onChange={handleSearchChange}
value={searchValue}
placeholder="Search"
+ showFocusBorder
/>
);
diff --git a/src/components/TopBar/components/SearchField/SearchField.scss b/src/components/TopBar/components/SearchField/SearchField.scss
index 1ee8e9deae6..41f9694a43e 100644
--- a/src/components/TopBar/components/SearchField/SearchField.scss
+++ b/src/components/TopBar/components/SearchField/SearchField.scss
@@ -59,6 +59,10 @@ $stacking-order: (
background-color: var(--p-action-secondary, color('white'));
}
+ ~ .BackdropShowFocusBorder {
+ border: 1px solid var(--top-bar-border, transparent);
+ }
+
~ .Icon {
@include recolor-icon(var(--p-icon-subdued, color('ink', 'lightest')));
}
@@ -69,6 +73,10 @@ $stacking-order: (
background-color: var(--p-action-secondary, color('white'));
}
+ .BackdropShowFocusBorder {
+ border: 1px solid var(--top-bar-border, transparent);
+ }
+
.Icon {
@include recolor-icon(var(--p-icon-subdued, color('ink', 'lightest')));
}
diff --git a/src/components/TopBar/components/SearchField/SearchField.tsx b/src/components/TopBar/components/SearchField/SearchField.tsx
index b07bd12fcd0..2586ee7ee80 100644
--- a/src/components/TopBar/components/SearchField/SearchField.tsx
+++ b/src/components/TopBar/components/SearchField/SearchField.tsx
@@ -27,6 +27,8 @@ export interface SearchFieldProps {
onBlur?(): void;
/** Callback when search field cancel button is clicked */
onCancel?(): void;
+ /** Show a border when the search field is focused */
+ showFocusBorder?: boolean;
}
export function SearchField({
@@ -38,6 +40,7 @@ export function SearchField({
onFocus,
onBlur,
onCancel,
+ showFocusBorder,
}: SearchFieldProps) {
const i18n = useI18n();
const [forceActive, setForceActive] = useState(false);
@@ -131,7 +134,12 @@ export function SearchField({
{clearMarkup}
-
+
);
}
diff --git a/src/components/TopBar/components/SearchField/tests/SearchField.test.tsx b/src/components/TopBar/components/SearchField/tests/SearchField.test.tsx
index 60be3f3e62d..13b7acd83e4 100644
--- a/src/components/TopBar/components/SearchField/tests/SearchField.test.tsx
+++ b/src/components/TopBar/components/SearchField/tests/SearchField.test.tsx
@@ -131,6 +131,16 @@ describe('', () => {
});
});
+ it('adds a "BackdropShowFocusBorder" class when "showFocusBorder" is passed', () => {
+ const textField = mountWithAppProvider(
+ ,
+ );
+
+ expect(textField.find('div').last().prop('className')).toBe(
+ 'Backdrop BackdropShowFocusBorder',
+ );
+ });
+
describe('newDesignLanguage', () => {
it('does not render a container with newDesignLanguage className by default', () => {
const textField = mountWithApp(
diff --git a/src/utilities/custom-properties/custom-properties.ts b/src/utilities/custom-properties/custom-properties.ts
index e3e8bbff3e5..69df0a684c0 100644
--- a/src/utilities/custom-properties/custom-properties.ts
+++ b/src/utilities/custom-properties/custom-properties.ts
@@ -11,6 +11,7 @@ export const nonDesignLangaugeCustomProperties = [
'--Polaris-RangeSlider-output-factor',
'--top-bar-color',
'--top-bar-background-lighter',
+ '--top-bar-border',
'--p-frame-offset',
];
diff --git a/src/utilities/theme/tests/utils.test.ts b/src/utilities/theme/tests/utils.test.ts
index d45d9c61e6f..54e10cfaac9 100644
--- a/src/utilities/theme/tests/utils.test.ts
+++ b/src/utilities/theme/tests/utils.test.ts
@@ -4,6 +4,7 @@ import {needsVariantList} from '../config';
import {
needsVariant,
setTextColor,
+ setBorderColor,
setTheme,
buildThemeContext,
buildCustomProperties,
@@ -18,12 +19,34 @@ describe('setTextColor', () => {
expect(textColor).toStrictEqual(['topBar', tokens.colorWhite]);
});
+ it('sets a css variable to white if the variant has no value', () => {
+ const textColor = setTextColor('topBar');
+ expect(textColor).toStrictEqual(['topBar', tokens.colorWhite]);
+ });
+
it('sets a css variable to ink if the variant is light', () => {
const textColor = setTextColor('topBar', 'light');
expect(textColor).toStrictEqual(['topBar', tokens.colorInk]);
});
});
+describe('setBorderColor', () => {
+ it('sets a css variable to sky dark if the variant is dark', () => {
+ const textColor = setBorderColor('topBar', 'dark');
+ expect(textColor).toStrictEqual(['topBar', tokens.colorSkyDark]);
+ });
+
+ it('sets a css variable to sky dark if the variant has no value', () => {
+ const textColor = setBorderColor('topBar');
+ expect(textColor).toStrictEqual(['topBar', tokens.colorSkyDark]);
+ });
+
+ it('sets a css variable to ink lighter if the variant is light', () => {
+ const textColor = setBorderColor('topBar', 'light');
+ expect(textColor).toStrictEqual(['topBar', tokens.colorInkLighter]);
+ });
+});
+
describe('setTheme', () => {
it('returns a base theme', () => {
const theme = setTheme(
@@ -35,6 +58,7 @@ describe('setTheme', () => {
expect(theme).toStrictEqual([
['--top-bar-color', 'rgb(255, 255, 255)'],
+ ['--top-bar-border', 'rgb(196, 205, 213)'],
['--top-bar-background-lighter', 'hsla(184, 85%, 43%, 1)'],
]);
});
@@ -57,6 +81,7 @@ describe('buildCustomProperties', () => {
'--p-frame-offset': '0px',
'--top-bar-background': '#eeeeee',
'--top-bar-background-lighter': 'hsla(0, 10%, 100%, 1)',
+ '--top-bar-border': 'rgb(99, 115, 129)',
'--top-bar-color': 'rgb(33, 43, 54)',
};
diff --git a/src/utilities/theme/utils.ts b/src/utilities/theme/utils.ts
index 44c13ea0950..f2a2a925d35 100644
--- a/src/utilities/theme/utils.ts
+++ b/src/utilities/theme/utils.ts
@@ -122,6 +122,16 @@ export function setTextColor(
return [name, tokens.colorWhite];
}
+export function setBorderColor(
+ name: string,
+ variant: 'light' | 'dark' = 'dark',
+): string[] {
+ if (variant === 'light') {
+ return [name, tokens.colorInkLighter];
+ }
+ return [name, tokens.colorSkyDark];
+}
+
export function setTheme(
color: string | HSLColor,
baseName: string,
@@ -135,6 +145,10 @@ export function setTheme(
setTextColor(constructColorName(baseName, null, 'color'), 'light'),
);
+ colorPairs.push(
+ setBorderColor(constructColorName(baseName, null, 'border'), 'light'),
+ );
+
colorPairs.push([
constructColorName(baseName, key, 'lighter'),
lightenToString(color, 7, -10),
@@ -146,6 +160,10 @@ export function setTheme(
setTextColor(constructColorName(baseName, null, 'color'), 'dark'),
);
+ colorPairs.push(
+ setBorderColor(constructColorName(baseName, null, 'border'), 'dark'),
+ );
+
colorPairs.push([
constructColorName(baseName, key, 'lighter'),
lightenToString(color, 15, 15),