Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/components/DataDictionary/components/Filters/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Stable class name stamped on the data dictionary filters grid, so the filters
// layout can target it without `:first-child`, which Emotion's server render
// makes unreliable (inline <style> elements precede the grid).
export const FILTERS_CLASS_NAME = "data-dictionary-filters";
4 changes: 3 additions & 1 deletion src/components/DataDictionary/components/Filters/filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { JSX } from "react";
import { Attribute } from "../../../../common/entities";
import { GlobalFilter } from "../../../Table/components/TableFeatures/GlobalFilter/globalFilter";
import { ColumnFilters } from "./components/ColumnFilters/columnFilters";
import { FILTERS_CLASS_NAME } from "./constants";
import { StyledGrid } from "./filters.styles";
import { FiltersProps } from "./types";

/**
* Data dictionary filters: a global search over all attributes, followed by the column filters.
* The grid carries `FILTERS_CLASS_NAME`, so the filters layout can target it.
* @param props - Component props.
* @param props.table - TanStack table instance.
* @returns The filters.
Expand All @@ -16,7 +18,7 @@ export const Filters = <T extends RowData = Attribute>({
table,
}: FiltersProps<T>): JSX.Element => {
return (
<StyledGrid>
<StyledGrid className={FILTERS_CLASS_NAME}>
<GlobalFilter placeholder="Search all attributes..." table={table} />
<ColumnFilters table={table} />
</StyledGrid>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import styled from "@emotion/styled";
import { LayoutSpacing } from "../../../../../../hooks/UseLayoutSpacing/types";
import { bpDown1024 } from "../../../../../../styles/common/mixins/breakpoints";
import { LAYOUT_SPACING } from "../../constants";

const PB = LAYOUT_SPACING.CONTENT_PADDING_BOTTOM; /* bottom padding */
const PT = LAYOUT_SPACING.CONTENT_PADDING_TOP; /* top padding */

export const Layout = styled("div")<LayoutSpacing>`
export const Layout = styled("div")`
display: grid;
gap: 16px;
grid-column: 2;
grid-row: 1;
grid-area: entities; /* follows the filters, so its position needs no measurement */
padding-bottom: ${PB}px;
padding-top: ${({ top }) => top}px;
z-index: 1; /* not required, but helpful in that the entities are always on top */

${bpDown1024} {
grid-column: 1;
grid-row: auto;
padding-top: ${PT}px;
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@ import { Layout } from "./entitiesLayout.styles";
import { EntitiesLayoutProps } from "./types";

/**
* Layout for the data dictionary entities, padded at the top to sit below the header and the sticky filters.
* Layout for the data dictionary entities, in the grid row below the sticky filters.
* @param props - Component props.
* @param props.children - Entities content.
* @param props.spacing - Layout spacing.
* @param ref - Ref forwarded to the layout element.
* @returns The entities layout.
*/
export const EntitiesLayout = forwardRef<HTMLDivElement, EntitiesLayoutProps>(
function EntitiesLayout(
{ children, spacing, ...props }: EntitiesLayoutProps,
{ children, ...props }: EntitiesLayoutProps,
ref,
): JSX.Element {
return (
<Layout ref={ref} {...spacing} {...props}>
<Layout ref={ref} {...props}>
{children}
</Layout>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { CSSProperties, ReactNode } from "react";
import { LayoutSpacing } from "../../../../../../hooks/UseLayoutSpacing/types";

/**
* Props for the EntitiesLayout component.
*/
export interface EntitiesLayoutProps {
children: ReactNode;
spacing: LayoutSpacing;
style?: CSSProperties; // Required for Fade component. See https://mui.com/material-ui/transitions/#child-requirement.
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,42 @@
import styled from "@emotion/styled";
import { LayoutSpacing } from "../../../../../../hooks/UseLayoutSpacing/types";
import { PALETTE } from "../../../../../../styles/common/constants/palette";
import {
bpDown1024,
bpDownSm,
bpUp1024,
} from "../../../../../../styles/common/mixins/breakpoints";
import { LAYOUT_SPACING } from "../../constants";
import {
pinned,
pinnedFocus,
} from "../../../../../../styles/common/mixins/pinned";
import { FILTERS_CLASS_NAME } from "../../../Filters/constants";
import { LAYOUT_SPACING, TITLE_BOTTOM } from "../../constants";

const PB = LAYOUT_SPACING.FILTERS_PADDING_BOTTOM; /* bottom padding */
const PT = LAYOUT_SPACING.FILTERS_PADDING_TOP; /* top padding */
const TITLE_HEIGHT = LAYOUT_SPACING.TITLE_HEIGHT; /* title height */

export const Layout = styled("div")<LayoutSpacing>`
align-self: flex-start;
export const Layout = styled("div")`
background-color: ${PALETTE.BACKGROUND_DEFAULT};
display: grid;
gap: 12px;
grid-column: 2;
grid-row: 1;
padding-bottom: ${PB}px;
padding-top: ${({ top }) => top + TITLE_HEIGHT + PT}px;
position: sticky;
top: 0;
grid-area: filters;
z-index: 2; /* required, filters should be on top of entities */

${bpDown1024} {
grid-column: 1;
grid-row: auto;
padding-top: ${PT}px;
position: relative;
/* Spacing sits on the children, so a dictionary with no filters collapses the band to nothing. */
> .${FILTERS_CLASS_NAME} {
margin-top: ${PT}px;

&:empty {
display: none;
}
}

> :last-child {
margin-bottom: ${PB}px;
}

${bpUp1024} {
${pinned(TITLE_BOTTOM)}; /* below the sticky title */
${pinnedFocus};
}

${bpDownSm} {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FiltersLayoutProps } from "./types";

/**
* Layout for the data dictionary filters, sticky below the header and title; at narrow widths it is not sticky.
* @param props - Component props, the layout spacing plus children.
* @param props - Component props.
* @param props.children - Filters content.
* @param ref - Ref forwarded to the layout element.
* @returns The filters layout.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { ReactNode } from "react";
import { LayoutSpacing } from "../../../../../../hooks/UseLayoutSpacing/types";

/**
* Props for the FiltersLayout component.
*/
export interface FiltersLayoutProps extends LayoutSpacing {
export interface FiltersLayoutProps {
children: ReactNode;
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import styled from "@emotion/styled";
import { LayoutSpacing } from "../../../../../../hooks/UseLayoutSpacing/types";
import { bpDown1024 } from "../../../../../../styles/common/mixins/breakpoints";
import { LAYOUT_SPACING } from "../../constants";
import { pinned } from "../../../../../../styles/common/mixins/pinned";
import { FOOTER_HEIGHT_VAR } from "../../../../../Layout/components/Footer/common/constants";
import { LAYOUT_SPACING, TITLE_BOTTOM } from "../../constants";

const PB = LAYOUT_SPACING.CONTENT_PADDING_BOTTOM; /* bottom padding */
const PT = LAYOUT_SPACING.OUTLINE_PADDING_TOP; /* top padding */
const TITLE_HEIGHT = LAYOUT_SPACING.TITLE_HEIGHT; /* title height */

export const Layout = styled("div")<LayoutSpacing>`
grid-column: 1;
grid-row: 1;
margin-bottom: ${({ bottom }) =>
-bottom}px; /* required; prevents sticky element from scrolling when footer scrolls into viewport */
max-height: 100vh;
export const Layout = styled("div")`
grid-area: outline;
margin-bottom: calc(
-1 * ${FOOTER_HEIGHT_VAR}
); /* with the matching padding, keeps the outline pinned whole while the footer scrolls into view */
max-height: calc(100vh - ${TITLE_BOTTOM});
overflow: hidden;
padding-bottom: ${({ bottom }) => bottom}px; /* footer height */
padding-top: ${({ top }) => top + TITLE_HEIGHT + PT}px;
position: sticky;
top: 0;
padding-bottom: ${FOOTER_HEIGHT_VAR};
padding-top: ${PT}px;
${pinned(TITLE_BOTTOM)}; /* below the sticky title */

${bpDown1024} {
display: none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { OutlineLayoutProps } from "./types";

/**
* Scrollable layout for the data dictionary outline, sticky below the header and title and clear of the footer; hidden at narrow widths.
* @param props - Component props, the layout spacing plus children.
* @param props - Component props.
* @param props.children - Outline content.
* @returns The outline layout.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { ReactNode } from "react";
import { LayoutSpacing } from "../../../../../../hooks/UseLayoutSpacing/types";

/**
* Props for the OutlineLayout component.
*/
export interface OutlineLayoutProps extends LayoutSpacing {
export interface OutlineLayoutProps {
children: ReactNode;
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import styled from "@emotion/styled";
import { LayoutSpacing } from "../../../../../../hooks/UseLayoutSpacing/types";
import { PALETTE } from "../../../../../../styles/common/constants/palette";
import {
bpDown1024,
bpDownSm,
bpUp1024,
} from "../../../../../../styles/common/mixins/breakpoints";
import {
pinned,
pinnedFocus,
} from "../../../../../../styles/common/mixins/pinned";
import { HEADER_HEIGHT_VAR } from "../../../../../Layout/components/Header/common/constants";

export const Layout = styled("div")<LayoutSpacing>`
grid-column: 1 / -1;
grid-row: 1;
height: fit-content;
padding-top: ${({ top }) => top}px; /* header height */
position: sticky;
top: 0;
export const Layout = styled("div")`
background-color: ${PALETTE.BACKGROUND_DEFAULT};
grid-area: title;
min-width: 0;
z-index: 4;

${bpDown1024} {
grid-column: 1;
grid-row: auto;
position: relative;
/* From 1024px up the title fills a fixed-height grid row, so it pins below the header and a long title truncates rather than wrapping under the filters. */
${bpUp1024} {
${pinned(HEADER_HEIGHT_VAR)};
${pinnedFocus};
overflow: hidden; /* a custom title taller than the row is clipped, never painted over the filters */

> * {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

${bpDownSm} {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { TitleLayoutProps } from "./types";

/**
* Layout for the data dictionary title, sticky below the header; at narrow widths it is not sticky.
* @param props - Component props, the layout spacing plus children.
* @param props - Component props.
* @param props.children - Title content.
* @returns The title layout.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { ReactNode } from "react";
import { LayoutSpacing } from "../../../../../../hooks/UseLayoutSpacing/types";

/**
* Props for the TitleLayout component.
*/
export interface TitleLayoutProps extends LayoutSpacing {
export interface TitleLayoutProps {
children: ReactNode;
}
16 changes: 16 additions & 0 deletions src/components/DataDictionary/components/Layout/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { HEADER_HEIGHT_VAR } from "../../../Layout/components/Header/common/constants";

export const LAYOUT_SPACING = {
CONTENT_PADDING_BOTTOM: 24,
CONTENT_PADDING_TOP: 16,
Expand All @@ -7,3 +9,17 @@ export const LAYOUT_SPACING = {
OUTLINE_PADDING_TOP: 16,
TITLE_HEIGHT: 74,
};

/**
* Height of the filters with no filter tags, which targets scroll to clear
* before the filters are measured.
*/
export const FILTERS_FALLBACK_HEIGHT =
LAYOUT_SPACING.FILTERS_PADDING_TOP +
LAYOUT_SPACING.FILTERS_HEIGHT +
LAYOUT_SPACING.FILTERS_PADDING_BOTTOM;

/**
* Where the filters and outline pin: below the sticky header and the title row.
*/
export const TITLE_BOTTOM = `calc(${HEADER_HEIGHT_VAR} + ${LAYOUT_SPACING.TITLE_HEIGHT}px)`;
48 changes: 47 additions & 1 deletion src/components/DataDictionary/dataDictionary.styles.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,62 @@
import type { SerializedStyles, Theme } from "@emotion/react";
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import { bpDown1024, bpDownSm } from "../../styles/common/mixins/breakpoints";
import { PROPERTY } from "../../hooks/useHtmlStyle/constants";
import {
bpDown1024,
bpDownSm,
bpUp1024,
} from "../../styles/common/mixins/breakpoints";
import {
FILTERS_FALLBACK_HEIGHT,
LAYOUT_SPACING,
TITLE_BOTTOM,
} from "./components/Layout/constants";

/**
* Server-rendered scroll-padding-top, so from 1024px up a deep link clears the
* sticky header, title and filters on first load. Until they are measured, the
* header falls back to its height without announcements and the filters to
* their height without tags. Below 1024px the title and filters scroll with the
* page, so the header's own scroll-padding-top applies. `:root` outranks the
* header's `html` rule.
* @param theme - MUI theme.
* @returns Global scroll-padding-top styles.
*/
export const scrollPadding = (theme: Theme): SerializedStyles => css`
${bpUp1024({ theme })} {
:root {
scroll-padding-top: calc(
${TITLE_BOTTOM} +
var(
${PROPERTY.DATA_DICTIONARY_FILTERS_HEIGHT},
${FILTERS_FALLBACK_HEIGHT}px
)
);
}
}
`;

export const View = styled("div")`
column-gap: 24px;
display: grid;
flex: 1;
grid-template-areas:
"title title"
"outline filters"
"outline entities";
grid-template-columns: 242px 1fr;
grid-template-rows: ${LAYOUT_SPACING.TITLE_HEIGHT}px auto 1fr;
margin: 0 24px;
position: relative;

${bpDown1024} {
grid-template-areas:
"title"
"filters"
"entities";
grid-template-columns: 1fr;
grid-template-rows: none;
margin: 0 16px;
}

Expand Down
Loading
Loading