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),