refactor: update UniversalSearch components with TypeScript and TextStatus

This commit is contained in:
Ahmed Bouhuolia
2026-02-09 19:26:26 +02:00
parent 1972861c97
commit 7375512fec
10 changed files with 484 additions and 316 deletions

View File

@@ -10,12 +10,17 @@ const TextStatusRoot = styled.span`
${(props) =>
props.intent === 'warning' &&
`
color: #ec5b0a;`}
color: #c87619;`}
${(props) =>
props.intent === 'danger' &&
`
color: #f17377;`}
${(props) =>
props.intent === 'success' &&
`
color: #2ba01d;`}
color: #238551;`}
${(props) =>
props.intent === 'none' &&

View File

@@ -1,7 +1,5 @@
// @ts-nocheck
import React from 'react';
import React, { KeyboardEvent, ReactNode } from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { isUndefined } from 'lodash';
import {
Overlay,
@@ -10,11 +8,14 @@ import {
MenuItem,
Spinner,
Intent,
OverlayProps,
Button,
} from '@blueprintjs/core';
import { QueryList } from '@blueprintjs/select';
import { CLASSES } from '@/constants/classes';
import { Icon, If, ListSelect, FormattedMessage as T } from '@/components';
import { QueryList, ItemRenderer } from '@blueprintjs/select';
import { x } from '@xstyled/emotion';
import { css } from '@emotion/css';
import { Icon, If, FormattedMessage as T } from '@/components';
import { Select } from '@blueprintjs-formik/select';
import {
UniversalSearchProvider,
useUniversalSearchContext,
@@ -22,59 +23,297 @@ import {
import { filterItemsByResourceType } from './utils';
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
// Resource type from RESOURCES_TYPES constant
type ResourceType = string;
// Search type option item
interface SearchTypeOption {
key: ResourceType;
label: string;
}
// Universal search item
interface UniversalSearchItem {
id: number | string;
_type: ResourceType;
text: string;
subText?: string;
label?: string;
[key: string]: any;
}
// CSS styles for complex selectors
const overlayStyles = css`
.bp4-overlay-appear,
.bp4-overlay-enter {
filter: blur(20px);
opacity: 0.2;
}
.bp4-overlay-appear-active,
.bp4-overlay-enter-active {
filter: blur(0);
opacity: 1;
transition:
filter 0.2s cubic-bezier(0.4, 1, 0.75, 0.9),
opacity 0.2s cubic-bezier(0.4, 1, 0.75, 0.9);
}
.bp4-overlay-exit {
filter: blur(0);
opacity: 1;
}
.bp4-overlay-exit-active {
filter: blur(20px);
opacity: 0.2;
transition:
filter 0.2s cubic-bezier(0.4, 1, 0.75, 0.9),
opacity 0.2s cubic-bezier(0.4, 1, 0.75, 0.9);
}
`;
const containerStyles = css`
position: fixed;
filter: blur(0);
opacity: 1;
background-color: var(--color-universal-search-background);
border-radius: 3px;
box-shadow:
0 0 0 1px rgba(16, 22, 26, 0.1),
0 4px 8px rgba(16, 22, 26, 0.2),
0 18px 46px 6px rgba(16, 22, 26, 0.2);
left: calc(50% - 250px);
top: 20vh;
width: 500px;
z-index: 20;
.bp4-input-group {
.bp4-icon {
margin: 16px;
color: var(--color-universal-search-icon);
svg {
stroke: currentColor;
fill: none;
fill-rule: evenodd;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2;
--text-opacity: 1;
}
}
}
.bp4-input-group .bp4-input {
border: 0;
box-shadow: 0 0 0 0;
height: 50px;
line-height: 50px;
font-size: 20px;
}
.bp4-input-group.bp4-large .bp4-input:not(:first-child) {
padding-left: 50px !important;
}
.bp4-input-group.bp4-large .bp4-input:not(:last-child) {
padding-right: 130px !important;
}
.bp4-menu {
border-top: 1px solid var(--color-universal-search-menu-border);
max-height: calc(60vh - 20px);
overflow: auto;
.bp4-menu-item {
.bp4-text-muted {
font-size: 12px;
.bp4-icon {
color: var(--bp4-gray-600);
}
}
&.bp4-intent-primary {
&.bp4-active {
background-color: var(--bp4-blue-100);
color: var(--bp4-dark-gray-800);
.bp4-menu-item-label {
color: var(--bp4-gray-600);
}
}
}
&-label {
flex-direction: row;
text-align: right;
}
}
}
.bp4-input-action {
height: 100%;
display: flex;
flex-direction: row;
align-items: center;
}
`;
const inputRightElementsStyles = css`
display: flex;
margin: 10px;
.bp4-spinner {
margin-right: 6px;
}
`;
const footerStyles = css`
padding: 12px 12px;
border-top: 1px solid var(--color-universal-search-footer-divider);
`;
const actionBaseStyles = css`
&:not(:first-of-type) {
margin-left: 14px;
}
.bp4-tag {
background: var(--color-universal-search-tag-background);
color: var(--color-universal-search-tag-text);
}
`;
const actionArrowsStyles = css`
&:not(:first-of-type) {
margin-left: 14px;
}
.bp4-tag {
background: var(--color-universal-search-tag-background);
color: var(--color-universal-search-tag-text);
padding: 0;
text-align: center;
line-height: 16px;
margin-left: 4px;
svg {
fill: var(--color-universal-search-tag-text);
height: 100%;
display: block;
width: 100%;
padding: 2px;
}
}
`;
// UniversalSearchInputRightElements props
interface UniversalSearchInputRightElementsProps {
/** Callback when search type changes */
onSearchTypeChange?: (option: SearchTypeOption) => void;
}
/**
* Universal search input action.
*/
function UniversalSearchInputRightElements({ onSearchTypeChange }) {
const { isLoading, searchType, defaultSearchResource, searchTypeOptions } =
function UniversalSearchInputRightElements({
onSearchTypeChange,
}: UniversalSearchInputRightElementsProps) {
const { isLoading, searchType, searchTypeOptions } =
useUniversalSearchContext();
// Find the currently selected item object.
const selectedItem = searchTypeOptions.find(
(item) => item.key === searchType,
);
// Handle search type option change.
const handleSearchTypeChange = (option) => {
onSearchTypeChange && onSearchTypeChange(option);
const handleSearchTypeChange = (option: SearchTypeOption) => {
onSearchTypeChange?.(option);
};
// Item renderer for the select dropdown.
const itemRenderer: ItemRenderer<SearchTypeOption> = (
item,
{ handleClick },
) => {
return <MenuItem text={item.label} key={item.key} onClick={handleClick} />;
};
return (
<div className={CLASSES.UNIVERSAL_SEARCH_INPUT_RIGHT_ELEMENTS}>
<x.div display="flex" m="10px" className={inputRightElementsStyles}>
<If condition={isLoading}>
<Spinner tagName="div" intent={Intent.NONE} size={18} value={null} />
<Spinner tagName="div" intent={Intent.NONE} size={18} />
</If>
<ListSelect
<Select<SearchTypeOption>
items={searchTypeOptions}
itemRenderer={itemRenderer}
onItemSelect={handleSearchTypeChange}
selectedValue={selectedItem?.key}
valueAccessor={'key'}
labelAccessor={'label'}
filterable={false}
initialSelectedItem={defaultSearchResource}
selectedItem={searchType}
selectedItemProp={'key'}
textProp={'label'}
// defaultText={intl.get('type')}
popoverProps={{
minimal: true,
captureDismiss: true,
className: CLASSES.UNIVERSAL_SEARCH_TYPE_SELECT_OVERLAY,
}}
buttonProps={{
minimal: true,
className: CLASSES.UNIVERSAL_SEARCH_TYPE_SELECT_BTN,
}}
input={({ activeItem }) => (
<Button minimal={true} text={activeItem?.label} />
)}
/>
</div>
</x.div>
);
}
// QueryList renderer props
interface QueryListRendererProps {
/** Current query string */
query: string;
/** Callback when query changes */
handleQueryChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
/** Item list element */
itemList: ReactNode;
/** Class name */
className?: string;
/** Handle key down */
handleKeyDown?: (event: KeyboardEvent<HTMLDivElement>) => void;
/** Handle key up */
handleKeyUp?: (event: KeyboardEvent<HTMLDivElement>) => void;
}
// UniversalSearchQueryList props
interface UniversalSearchQueryListProps {
/** Whether the search is open */
isOpen: boolean;
/** Whether the search is loading */
isLoading: boolean;
/** Callback when search type changes */
onSearchTypeChange?: (option: SearchTypeOption) => void;
/** Current search type */
searchType: ResourceType;
/** Items to display */
items: UniversalSearchItem[];
/** Renderer for items */
itemRenderer?: ItemRenderer<UniversalSearchItem>;
/** Callback when an item is selected */
onItemSelect?: (item: UniversalSearchItem, event?: any) => void;
/** Current query string */
query: string;
/** Callback when query changes */
onQueryChange?: (query: string) => void;
}
/**
* Universal search query list.
*/
function UniversalSearchQueryList(props) {
const { isOpen, isLoading, onSearchTypeChange, searchType, ...restProps } =
props;
function UniversalSearchQueryList({
isOpen,
isLoading,
onSearchTypeChange,
...restProps
}: UniversalSearchQueryListProps) {
return (
<QueryList
{...restProps}
<QueryList<UniversalSearchItem>
{...(restProps as any)}
initialContent={null}
renderer={(listProps) => (
renderer={(listProps: QueryListRendererProps) => (
<UniversalSearchBar
isOpen={isOpen}
onSearchTypeChange={onSearchTypeChange}
@@ -100,47 +339,53 @@ function UniversalSearchQueryList(props) {
*/
function UniversalQuerySearchActions() {
return (
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTIONS)}>
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_SELECT)}>
<x.div display="flex">
<x.div className={actionBaseStyles}>
<Tag>ENTER</Tag>
<span class={'text'}>{intl.get('universal_search.enter_text')}</span>
</div>
<x.span ml="6px">{intl.get('universal_search.enter_text')}</x.span>
</x.div>
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_CLOSE)}>
<x.div className={actionBaseStyles}>
<Tag>ESC</Tag>{' '}
<span class={'text'}>{intl.get('universal_search.close_text')}</span>
</div>
<x.span ml="6px">{intl.get('universal_search.close_text')}</x.span>
</x.div>
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_ACTION_ARROWS)}>
<x.div className={actionArrowsStyles}>
<Tag>
<Icon icon={'arrow-up-24'} iconSize={16} />
</Tag>
<Tag>
<Icon icon={'arrow-down-24'} iconSize={16} />
</Tag>
<span class="text">{intl.get('universal_seach.navigate_text')}</span>
</div>
</div>
<x.span ml="6px">{intl.get('universal_seach.navigate_text')}</x.span>
</x.div>
</x.div>
);
}
// UniversalSearchBar props
interface UniversalSearchBarProps extends QueryListRendererProps {
/** Whether the search is open */
isOpen: boolean;
/** Callback when search type changes */
onSearchTypeChange?: (option: SearchTypeOption) => void;
}
/**
* Universal search input bar with items list.
*/
function UniversalSearchBar({ isOpen, onSearchTypeChange, ...listProps }) {
function UniversalSearchBar({
isOpen,
onSearchTypeChange,
...listProps
}: UniversalSearchBarProps) {
const { handleKeyDown, handleKeyUp } = listProps;
const handlers = isOpen
? { onKeyDown: handleKeyDown, onKeyUp: handleKeyUp }
: {};
return (
<div
className={classNames(
CLASSES.UNIVERSAL_SEARCH_OMNIBAR,
listProps.className,
)}
{...handlers}
>
<x.div {...handlers}>
<InputGroup
large={true}
leftIcon={<Icon icon={'universal-search'} iconSize={20} />}
@@ -155,17 +400,44 @@ function UniversalSearchBar({ isOpen, onSearchTypeChange, ...listProps }) {
autoFocus={true}
/>
{listProps.itemList}
</div>
</x.div>
);
}
// UniversalSearch props
export interface UniversalSearchProps {
/** Default search resource type */
defaultSearchResource?: ResourceType;
/** Controlled search resource type */
searchResource?: ResourceType;
/** Overlay props */
overlayProps?: OverlayProps;
/** Whether the search overlay is open */
isOpen: boolean;
/** Whether the search is loading */
isLoading: boolean;
/** Callback when search type changes */
onSearchTypeChange?: (resource: SearchTypeOption) => void;
/** Items to display */
items: UniversalSearchItem[];
/** Available search type options */
searchTypeOptions: SearchTypeOption[];
/** Renderer for items */
itemRenderer?: ItemRenderer<UniversalSearchItem>;
/** Callback when an item is selected */
onItemSelect?: (item: UniversalSearchItem, event?: any) => void;
/** Current query string */
query: string;
/** Callback when query changes */
onQueryChange?: (query: string) => void;
}
/**
* Universal search.
*/
export function UniversalSearch({
defaultSearchResource,
searchResource,
overlayProps,
isOpen,
isLoading,
@@ -173,9 +445,9 @@ export function UniversalSearch({
items,
searchTypeOptions,
...queryListProps
}) {
}: UniversalSearchProps) {
// Search type state.
const [searchType, setSearchType] = React.useState(
const [searchType, setSearchType] = React.useState<ResourceType>(
defaultSearchResource || RESOURCES_TYPES.CUSTOMER,
);
// Handle search resource type controlled mode.
@@ -189,9 +461,9 @@ export function UniversalSearch({
}, [searchResource, defaultSearchResource]);
// Handle search type change.
const handleSearchTypeChange = (searchTypeResource) => {
const handleSearchTypeChange = (searchTypeResource: SearchTypeOption) => {
setSearchType(searchTypeResource.key);
onSearchTypeChange && onSearchTypeChange(searchTypeResource);
onSearchTypeChange?.(searchTypeResource);
};
// Filters query list items based on the given search type.
const filteredItems = filterItemsByResourceType(items, searchType);
@@ -200,7 +472,7 @@ export function UniversalSearch({
<Overlay
hasBackdrop={true}
isOpen={isOpen}
className={classNames(CLASSES.UNIVERSAL_SEARCH_OVERLAY)}
className={overlayStyles}
{...overlayProps}
>
<UniversalSearchProvider
@@ -209,7 +481,7 @@ export function UniversalSearch({
defaultSearchResource={defaultSearchResource}
searchTypeOptions={searchTypeOptions}
>
<div className={classNames(CLASSES.UNIVERSAL_SEARCH)}>
<x.div className={containerStyles}>
<UniversalSearchQueryList
isOpen={isOpen}
isLoading={isLoading}
@@ -218,10 +490,10 @@ export function UniversalSearch({
{...queryListProps}
items={filteredItems}
/>
<div className={classNames(CLASSES.UNIVERSAL_SEARCH_FOOTER)}>
<x.div className={footerStyles}>
<UniversalQuerySearchActions />
</div>
</div>
</x.div>
</x.div>
</UniversalSearchProvider>
</Overlay>
);

View File

@@ -1,30 +1,82 @@
// @ts-nocheck
import React, { createContext } from 'react';
import React, { createContext, ReactNode, useContext } from 'react';
const UniversalSearchContext = createContext();
// The resource type value from RESOURCES_TYPES constant
type ResourceType = string;
// Search type option item
interface SearchTypeOption {
key: ResourceType;
label: string;
}
// Context value type
interface UniversalSearchContextValue {
/** Whether the search is loading */
isLoading: boolean;
/** Current search type/resource type */
searchType: ResourceType;
/** Default search resource type */
defaultSearchResource?: ResourceType;
/** List of available search type options */
searchTypeOptions: SearchTypeOption[];
}
// Create the context with undefined as initial value
const UniversalSearchContext = createContext<
UniversalSearchContextValue | undefined
>(undefined);
// Provider props interface
interface UniversalSearchProviderProps {
/** Whether the search is loading */
isLoading: boolean;
/** Default search resource type */
defaultSearchResource?: ResourceType;
/** Current search type/resource type */
searchType: ResourceType;
/** List of available search type options */
searchTypeOptions: SearchTypeOption[];
/** Child elements */
children: ReactNode;
}
/**
* Universal search data provider.
*/
function UniversalSearchProvider({
export function UniversalSearchProvider({
isLoading,
defaultSearchResource,
searchType,
searchTypeOptions,
...props
}) {
children,
}: UniversalSearchProviderProps) {
// Provider payload.
const provider = {
const provider: UniversalSearchContextValue = {
isLoading,
searchType,
defaultSearchResource,
searchTypeOptions,
};
return <UniversalSearchContext.Provider value={provider} {...props} />;
return (
<UniversalSearchContext.Provider value={provider}>
{children}
</UniversalSearchContext.Provider>
);
}
const useUniversalSearchContext = () =>
React.useContext(UniversalSearchContext);
/**
* Hook to access the universal search context.
* @throws Error if used outside of UniversalSearchProvider
*/
export const useUniversalSearchContext = (): UniversalSearchContextValue => {
const context = useContext(UniversalSearchContext);
export { UniversalSearchProvider, useUniversalSearchContext };
if (context === undefined) {
throw new Error(
'useUniversalSearchContext must be used within a UniversalSearchProvider',
);
}
return context;
};

View File

@@ -1,10 +1,10 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { MenuItem } from '@blueprintjs/core';
import { MenuItem, Intent } from '@blueprintjs/core';
import { formattedAmount } from '@/utils';
import { T, Icon, Choose, If } from '@/components';
import { T, Icon, Choose, If, TextStatus } from '@/components';
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
import { AbilitySubject, BillAction } from '@/constants/abilityOption';
@@ -41,35 +41,35 @@ export function BillStatus({ bill }) {
return (
<Choose>
<Choose.When condition={bill.is_fully_paid && bill.is_open}>
<span class="fully-paid-text">
<TextStatus intent={Intent.SUCCESS}>
<T id={'paid'} />
</span>
</TextStatus>
</Choose.When>
<Choose.When condition={bill.is_open}>
<Choose>
<Choose.When condition={bill.is_overdue}>
<span className={'overdue-status'}>
<TextStatus intent={Intent.DANGER}>
{intl.get('overdue_by', { overdue: bill.overdue_days })}
</span>
</TextStatus>
</Choose.When>
<Choose.Otherwise>
<span className={'due-status'}>
<TextStatus intent={Intent.WARNING}>
{intl.get('due_in', { due: bill.remaining_days })}
</span>
</TextStatus>
</Choose.Otherwise>
</Choose>
<If condition={bill.is_partially_paid}>
<span className="partial-paid">
<TextStatus intent={Intent.WARNING}>
{intl.get('day_partially_paid', {
due: formattedAmount(bill.due_amount, bill.currency_code),
})}
</span>
</TextStatus>
</If>
</Choose.When>
<Choose.Otherwise>
<span class="draft">
<TextStatus intent={Intent.NONE}>
<T id={'draft'} />
</span>
</TextStatus>
</Choose.Otherwise>
</Choose>
);

View File

@@ -1,9 +1,9 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { MenuItem } from '@blueprintjs/core';
import { MenuItem, Intent } from '@blueprintjs/core';
import { Choose, T, Icon } from '@/components';
import { Choose, T, Icon, TextStatus } from '@/components';
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
import { AbilitySubject, SaleEstimateAction } from '@/constants/abilityOption';
@@ -37,28 +37,28 @@ export const EstimateUniversalSearchSelect = withDrawerActions(
export const EstimateStatus = ({ estimate }) => (
<Choose>
<Choose.When condition={estimate.is_delivered && estimate.is_approved}>
<span class="approved">
<TextStatus intent={Intent.SUCCESS}>
<T id={'approved'} />
</span>
</TextStatus>
</Choose.When>
<Choose.When condition={estimate.is_delivered && estimate.is_rejected}>
<span class="reject">
<TextStatus intent={Intent.DANGER}>
<T id={'rejected'} />
</span>
</TextStatus>
</Choose.When>
<Choose.When
condition={
estimate.is_delivered && !estimate.is_rejected && !estimate.is_approved
}
>
<span class="delivered">
<TextStatus intent={Intent.SUCCESS}>
<T id={'delivered'} />
</span>
</TextStatus>
</Choose.When>
<Choose.Otherwise>
<span class="draft">
<TextStatus intent={Intent.NONE}>
<T id={'draft'} />
</span>
</TextStatus>
</Choose.Otherwise>
</Choose>
);

View File

@@ -1,9 +1,9 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { MenuItem } from '@blueprintjs/core';
import { MenuItem, Intent } from '@blueprintjs/core';
import { T, Choose, Icon } from '@/components';
import { T, Choose, Icon, TextStatus } from '@/components';
import { highlightText } from '@/utils';
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
@@ -39,29 +39,29 @@ function InvoiceStatus({ customer }) {
return (
<Choose>
<Choose.When condition={customer.is_fully_paid && customer.is_delivered}>
<span class="status status-success">
<TextStatus intent={Intent.SUCCESS}>
<T id={'paid'} />
</span>
</TextStatus>
</Choose.When>
<Choose.When condition={customer.is_delivered}>
<Choose>
<Choose.When condition={customer.is_overdue}>
<span className={'status status-warning'}>
<TextStatus intent={Intent.DANGER}>
{intl.get('overdue_by', { overdue: customer.overdue_days })}
</span>
</TextStatus>
</Choose.When>
<Choose.Otherwise>
<span className={'status status-warning'}>
<TextStatus intent={Intent.WARNING}>
{intl.get('due_in', { due: customer.remaining_days })}
</span>
</TextStatus>
</Choose.Otherwise>
</Choose>
</Choose.When>
<Choose.Otherwise>
<span class="status status--gray">
<TextStatus intent={Intent.NONE}>
<T id={'draft'} />
</span>
</TextStatus>
</Choose.Otherwise>
</Choose>
);
@@ -94,7 +94,6 @@ export function InvoiceUniversalSearchItem(
</>
}
onClick={handleClick}
className={'universal-search__item--invoice'}
/>
);
}

View File

@@ -1,9 +1,8 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { MenuItem } from '@blueprintjs/core';
import { Icon, Choose, T } from '@/components';
import { MenuItem, Intent } from '@blueprintjs/core';
import { Icon, Choose, T, TextStatus } from '@/components';
import { RESOURCES_TYPES } from '@/constants/resourcesTypes';
import { AbilitySubject, SaleReceiptAction } from '@/constants/abilityOption';
import { withDrawerActions } from '@/containers/Drawer/withDrawerActions';
@@ -39,15 +38,15 @@ function ReceiptStatus({ receipt }) {
return (
<Choose>
<Choose.When condition={receipt.is_closed}>
<span class="closed">
<TextStatus intent={Intent.SUCCESS}>
<T id={'closed'} />
</span>
</TextStatus>
</Choose.When>
<Choose.Otherwise>
<span class="draft">
<TextStatus intent={Intent.NONE}>
<T id={'draft'} />
</span>
</TextStatus>
</Choose.Otherwise>
</Choose>
);

View File

@@ -31,7 +31,6 @@
@import 'components/Overlay';
@import 'components/Menu';
@import 'components/SidebarOverlay';
@import 'components/UniversalSearch';
// Pages
@import 'pages/view-form';

View File

@@ -7,6 +7,27 @@ $ns: bp4;
--color-primary: #8abbff;
--color-danger: red;
// Green colors
--color-green-500: #165a36;
--color-green-400: #1c6e42;
--color-green-300: #238551;
--color-green-200: #32a467;
--color-green-100: #72ca9b;
// Red colors
--color-red-500: #8e292c;
--color-red-400: #ac2f33;
--color-red-300: #cd4246;
--color-red-200: #e76a6e;
--color-red-100: #fa999c;
// Orange colors
--color-orange-500: #77450d;
--color-orange-400: #935610;
--color-orange-300: #c87619;
--color-orange-200: #ec9a3c;
--color-orange-100: #fbb360;
--color-dark-gray5: #404854;
--color-dark-gray4: #383e47;
--color-dark-gray3: #2f343c;
@@ -301,6 +322,27 @@ body.bp4-dark {
--color-primary: #8abbff;
--color-danger: rgb(213, 103, 103);
// Green colors (dark mode - lighter variants)
--color-green-500: #72ca9b;
--color-green-400: #32a467;
--color-green-300: #238551;
--color-green-200: #1c6e42;
--color-green-100: #165a36;
// Red colors (dark mode - lighter variants)
--color-red-500: #fa999c;
--color-red-400: #e76a6e;
--color-red-300: #cd4246;
--color-red-200: #ac2f33;
--color-red-100: #8e292c;
// Orange colors (dark mode - lighter variants)
--color-orange-500: #fbb360;
--color-orange-400: #ec9a3c;
--color-orange-300: #c87619;
--color-orange-200: #935610;
--color-orange-100: #77450d;
--color-dark-gray5: #404854;
--color-dark-gray4: #383e47;
--color-dark-gray3: #2f343c;

View File

@@ -1,200 +0,0 @@
.universal-search {
position: fixed;
filter: blur(0);
opacity: 1;
background-color: var(--color-universal-search-background);
border-radius: 3px;
box-shadow: 0 0 0 1px rgba(16, 22, 26, 0.1),
0 4px 8px rgba(16, 22, 26, 0.2),
0 18px 46px 6px rgba(16, 22, 26, 0.2);
left: calc(50% - 250px);
top: 20vh;
width: 500px;
z-index: 20;
&.bp4-overlay-appear,
&.bp4-overlay-enter {
filter: blur(20px);
opacity: 0.2;
}
&.bp4-overlay-appear-active,
&.bp4-overlay-enter-active {
filter: blur(0);
opacity: 1;
transition-delay: 0;
transition-duration: 0.2s;
transition-property: filter, opacity;
transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);
}
&.bp4-overlay-exit {
filter: blur(0);
opacity: 1;
}
&.bp4-overlay-exit-active {
filter: blur(20px);
opacity: 0.2;
transition-delay: 0;
transition-duration: 0.2s;
transition-property: filter, opacity;
transition-timing-function: cubic-bezier(0.4, 1, 0.75, 0.9);
}
&__omnibar {
.bp4-input-group {
.bp4-icon {
svg {
stroke: currentColor;
fill: none;
fill-rule: evenodd;
stroke-linecap: round;
stroke-linejoin: round;
}
}
}
.bp4-input-group .bp4-input {
border: 0;
box-shadow: 0 0 0 0;
height: 50px;
line-height: 50px;
font-size: 20px;
}
.bp4-input-group.bp4-large .bp4-input:not(:first-child) {
padding-left: 50px !important;
}
.bp4-input-group.bp4-large .bp4-input:not(:last-child) {
padding-right: 130px !important;
}
.bp4-input-group {
.bp4-icon {
margin: 16px;
color: var(--color-universal-search-icon);
svg {
stroke-width: 2;
--text-opacity: 1;
}
}
}
.bp4-menu {
border-top: 1px solid var(--color-universal-search-menu-border);
max-height: calc(60vh - 20px);
overflow: auto;
.bp4-menu-item {
.bp4-text-muted {
font-size: 12px;
.bp4-icon {
color: #8499a7;
}
}
&.bp4-intent-primary {
&.bp4-active {
background-color: rgb(235, 241, 246);
color: #252b30;
.bp4-menu-item-label {
color: #5c7080;
}
}
}
&-label {
flex-direction: row;
text-align: right;
}
}
}
.bp4-input-action {
height: 100%;
display: flex;
flex-direction: row;
align-items: center;
}
}
&__type-select-overlay {
.bp4-button {
margin: 0 !important;
}
}
&__footer {
padding: 12px 12px;
border-top: 1px solid var(--color-universal-search-footer-divider);
}
&__actions {
display: flex;
}
&__action {
&:not(:first-of-type) {
margin-left: 14px;
}
.bp4-tag {
background: var(--color-universal-search-tag-background);
color: var(--color-universal-search-tag-text);
}
&--arrows {
.bp4-tag {
padding: 0;
text-align: center;
line-height: 16px;
margin-left: 4px;
svg {
fill: var(--color-universal-search-tag-text);
height: 100%;
display: block;
width: 100%;
padding: 2px;
}
}
}
.text {
margin-left: 6px;
}
}
&__footer {
}
&-input-right-elements {
display: flex;
margin: 10px;
.bp4-spinner {
margin-right: 6px;
}
}
&__item {
&--invoice,
&--estimate,
&--bill,
&--receipt {
.amount {
color: #252b30;
}
.status {
font-size: 13px;
&.status-warning {
color: rgb(236, 91, 10);
}
&.status-success {
color: #249017;
}
}
}
}
}