Skip to content

Commit

Permalink
Fleet UI: Software headers more responsive (#25212)
Browse files Browse the repository at this point in the history
  • Loading branch information
RachelElysia authored Jan 10, 2025
1 parent 464d99d commit 5873cb9
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 140 deletions.
21 changes: 11 additions & 10 deletions frontend/components/TableContainer/TableContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ interface ITableContainerProps<T = any> {
onQueryChange?:
| ((queryData: ITableQueryData) => void)
| ((queryData: ITableQueryData) => number);
customControl?: () => JSX.Element;
customControl?: () => JSX.Element | null;
/** Filter button right of the search rendering alternative responsive design where search bar moves to new line but filter button remains inline with other table headers */
customFiltersButton?: () => JSX.Element;
stackControls?: boolean;
Expand Down Expand Up @@ -288,11 +288,11 @@ const TableContainer = <T,>({
const opacity = isLoading ? { opacity: 0.4 } : { opacity: 1 };

// New preferred pattern uses grid container/box to allow for more dynamic responsiveness
// At low widths, search bar (3rd div of 4) moves above other 3 divs
if (customFiltersButton) {
// At low widths, right header stacks on top of left header
if (stackControls) {
return (
<div className="container">
<div className="box">
<div className="stackable-header">
{renderCount && !disableCount && (
<div
className={`${baseClass}__results-count ${
Expand All @@ -304,8 +304,9 @@ const TableContainer = <T,>({
</div>
)}
</div>
<div className="box">
{actionButton && !actionButton.hideButton && (

{actionButton && !actionButton.hideButton && (
<div className="stackable-header">
<Button
disabled={disableActionButton}
onClick={actionButton.onActionButtonClick}
Expand All @@ -317,10 +318,10 @@ const TableContainer = <T,>({
{actionButton.iconSvg && <Icon name={actionButton.iconSvg} />}
</>
</Button>
)}
</div>
)}
<div className="stackable-header top-shift-header">
{customControl && customControl()}
</div>
<div className="box search">
{searchable && !wideSearch && (
<div className={`${baseClass}__search`}>
<div
Expand All @@ -347,8 +348,8 @@ const TableContainer = <T,>({
</ReactTooltip>
</div>
)}
{customFiltersButton && customFiltersButton()}
</div>
<div className="box"> {customFiltersButton()} </div>
</div>
);
}
Expand Down
64 changes: 25 additions & 39 deletions frontend/components/TableContainer/_styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
// Container is responsive design used when customFilters is rendered
.container {
display: grid;
grid-template-columns: 1fr auto auto; /* First column takes all remaining space */
grid-template-rows: auto auto; /* Two rows */
grid-template-columns: 1fr auto; /* First column takes all remaining space */
grid-template-rows: auto auto; /* Two rows for smaller screens*/
width: 100%;
height: max-content;
gap: $pad-small $pad-medium;
}

.box {
.stackable-header {
min-width: max-content;
align-content: center;
display: flex;
Expand All @@ -24,56 +24,46 @@
display: flex;
flex-direction: row;
}

// only if in stackable header
.table-container__search {
width: 100%;
}
}

.search {
.top-shift-header {
grid-column: 1 / -1; /* Span across all columns */
grid-row: 1; /* Place in the first row */
}

.box:nth-child(1) {
grid-column: 1 / span 2; /* Make Box 1 expand across two columns */
grid-row: 2;
.Select-multi-value-wrapper {
height: 36px; // Fixes height issues
width: 236px;
}
}

.box:nth-child(2) {
grid-column: 2; /* Place Box 2 in the second row, second column */
.stackable-header:nth-child(1) {
grid-column: 1 / span 2; /* Make Header 1 expand across two columns */
grid-row: 2;
}

.box:nth-child(4) {
grid-column: 3; /* Place Box 4 in the second row, third column */
grid-row: 2;
max-width: min-content;
.form-field--dropdown {
width: 235px;
}
}

/* Media query for larger screens */
@media (min-width: $table-controls-break) {
@media (min-width: $break-md) {
.container {
grid-template-columns: 1fr auto auto auto; /* First column takes all remaining space */
grid-template-columns: 1fr auto; /* First column takes all remaining space */
grid-template-rows: auto; /* Single row */
}

.search {
grid-column: 1 / -1; /* Keep spanning across all columns if needed */
grid-row: auto;
.top-shift-header {
grid-column: 2; /* Single row */
}

.box:nth-child(1) {
grid-column: 1; /* Ensure Box 1 stays in the first column */
}

.box:nth-child(2) {
grid-column: 2; /* Place Box 2 in the second column */
}

.box:nth-child(3) {
grid-column: 3; /* Place Box 3 in the third column */
grid-row: 2;
}

.box:nth-child(4) {
grid-column: 4; /* Place Box 4 in the fourth column */
.stackable-header:nth-child(1) {
grid-column: 1; /* Ensure Header 1 stays in the first column */
grid-row: 1; /* Single row */
}
}

Expand Down Expand Up @@ -138,10 +128,6 @@
justify-content: space-between;
}
}

.Select-multi-value-wrapper {
height: 38px; // Fixes overlap with .Select outline
}
}

&__results-count {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,81 @@
// stories/DropdownWrapper.stories.tsx

import React from "react";
import { Meta, Story } from "@storybook/react";
import DropdownWrapper, {
IDropdownWrapper,
CustomOptionType,
} from "./DropdownWrapper";
import type { Meta, StoryObj } from "@storybook/react";
import DropdownWrapper, { CustomOptionType } from "./DropdownWrapper";

// Define metadata for the story
export default {
const meta: Meta<typeof DropdownWrapper> = {
title: "Components/DropdownWrapper",
component: DropdownWrapper,
argTypes: {
onChange: { action: "changed" },
},
} as Meta;
// Padding added to view tooltips
decorators: [
(Story) => (
<div style={{ overflow: "visible", padding: "150px" }}>
<Story />
</div>
),
],
};

export default meta;

// Define a template for the stories
const Template: Story<IDropdownWrapper> = (args) => (
<DropdownWrapper {...args} />
);
type Story = StoryObj<typeof DropdownWrapper>;

// Sample options to be used in the dropdown
const sampleOptions: CustomOptionType[] = [
{ label: "Option 1", value: "option1", helpText: "Help text for option 1" },
{
label: "Option 2",
label: "Option 1 - just help text",
value: "option1",
helpText: "Help text for option 1",
},
{
label: "Option 2 - just tooltip",
value: "option2",
tooltipContent: "Tooltip for option 2",
},
{ label: "Option 3", value: "option3", isDisabled: true },
{ label: "Option 3 - just disabled", value: "option3", isDisabled: true },
{
label: "Option 4 - help text, disabled, and tooltip",
value: "option4",
helpText: "Help text for option 4",
isDisabled: true,
tooltipContent: "Tooltip for option 4",
},
];

// Default story
export const Default = Template.bind({});
Default.args = {
options: sampleOptions,
name: "dropdown-example",
label: "Select an option",
export const Default: Story = {
args: {
options: sampleOptions,
name: "dropdown-example",
label: "Select an option",
},
};

// Disabled story
export const Disabled = Template.bind({});
Disabled.args = {
...Default.args,
isDisabled: true,
export const Disabled: Story = {
args: {
...Default.args,
isDisabled: true,
},
};

// With Help Text story
export const WithHelpText = Template.bind({});
WithHelpText.args = {
...Default.args,
helpText: "This is some help text for the dropdown",
export const WithHelpText: Story = {
args: {
...Default.args,
helpText: "This is some help text for the dropdown",
},
};

// With Error story
export const WithError = Template.bind({});
WithError.args = {
...Default.args,
error: "This is an error message",
export const WithError: Story = {
args: {
...Default.args,
error: "This is an error message",
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { PADDING } from "styles/var/padding";
import FormField from "components/forms/FormField";
import DropdownOptionTooltipWrapper from "components/forms/fields/Dropdown/DropdownOptionTooltipWrapper";
import Icon from "components/Icon";
import { IconNames } from "components/icons";

const getOptionBackgroundColor = (state: any) => {
return state.isSelected || state.isFocused
Expand All @@ -39,6 +40,7 @@ export interface CustomOptionType {
tooltipContent?: string;
helpText?: string;
isDisabled?: boolean;
iconName?: IconNames;
}

export interface IDropdownWrapper {
Expand All @@ -53,6 +55,7 @@ export interface IDropdownWrapper {
helpText?: JSX.Element | string;
isSearchable?: boolean;
isDisabled?: boolean;
iconName?: IconNames;
placeholder?: string;
/** E.g. scroll to view dropdown menu in a scrollable parent container */
onMenuOpen?: () => void;
Expand All @@ -70,8 +73,9 @@ const DropdownWrapper = ({
error,
label,
helpText,
isSearchable,
isSearchable = false,
isDisabled = false,
iconName,
placeholder,
onMenuOpen,
}: IDropdownWrapper) => {
Expand Down Expand Up @@ -120,7 +124,7 @@ const DropdownWrapper = ({
};

const CustomDropdownIndicator = (
props: DropdownIndicatorProps<any, false, any>
props: DropdownIndicatorProps<CustomOptionType, false, any>
) => {
const { isFocused, selectProps } = props;
const color =
Expand All @@ -142,6 +146,19 @@ const DropdownWrapper = ({
);
};

const ValueContainer = ({ children, ...props }: any) => {
return (
components.ValueContainer && (
<components.ValueContainer {...props}>
{!!children && iconName && (
<Icon name={iconName} className="filter-icon" />
)}
{children}
</components.ValueContainer>
)
);
};

const customStyles: StylesConfig<CustomOptionType, false> = {
container: (provided) => ({
...provided,
Expand Down Expand Up @@ -235,10 +252,13 @@ const DropdownWrapper = ({
menuList: (provided) => ({
...provided,
padding: PADDING["pad-small"],
maxHeight: "none",
}),
valueContainer: (provided) => ({
...provided,
padding: 0,
display: "flex",
gap: PADDING["pad-small"],
}),
option: (provided, state) => ({
...provided,
Expand All @@ -260,7 +280,6 @@ const DropdownWrapper = ({
color: COLORS["ui-fleet-black-50"],
fontStyle: "italic",
cursor: "not-allowed",
pointerEvents: "none",
}),
// Styles for custom option
".dropdown-wrapper__option": {
Expand Down Expand Up @@ -323,6 +342,7 @@ const DropdownWrapper = ({
Option: CustomOption,
DropdownIndicator: CustomDropdownIndicator,
IndicatorSeparator: () => null,
ValueContainer,
}}
value={getCurrentValue()}
onChange={handleChange}
Expand Down
Loading

0 comments on commit 5873cb9

Please sign in to comment.