Skip to content

feat(compass-components): add dark mode flag for using theme in components instead of darkreader COMPASS-5520 #2856

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Mar 3, 2022
Merged
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
2 changes: 1 addition & 1 deletion packages/compass-components/src/components/accordion.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { spacing } from '@leafygreen-ui/tokens';
import { css } from '@leafygreen-ui/emotion';
import Icon from '@leafygreen-ui/icon';
import { uiColors } from '@leafygreen-ui/palette';
import { useId } from '@react-aria/utils';

import { Icon } from './leafygreen';
import { defaultFontSize } from '../compass-font-sizes';

const buttonStyles = css({
Expand Down
13 changes: 2 additions & 11 deletions packages/compass-components/src/components/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import React from 'react';
import LeafyGreenCheckbox from '@leafygreen-ui/checkbox';
import { css } from '@leafygreen-ui/emotion';

import { Checkbox as LeafyGreenCheckbox } from './leafygreen';
import { Theme, useTheme } from '../hooks/use-theme';

const checkboxOverrideStyles = css({
fontWeight: 'bold',
});

function Checkbox(
props: React.ComponentProps<typeof LeafyGreenCheckbox>
): ReturnType<typeof LeafyGreenCheckbox> {
const theme = useTheme();

return (
<LeafyGreenCheckbox
className={checkboxOverrideStyles}
darkMode={theme?.theme === Theme.Dark}
{...props}
/>
<LeafyGreenCheckbox darkMode={theme?.theme === Theme.Dark} {...props} />
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useEffect } from 'react';
import LeafyGreenConfirmationModal from '@leafygreen-ui/confirmation-modal';
import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
const { track } = createLoggerAndTelemetry('COMPASS-UI');

import { ConfirmationModal as LeafyGreenConfirmationModal } from './leafygreen';

function ConfirmationModal({
trackingId,
...props
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { ErrorInfo } from 'react';
import React from 'react';
import Banner from '@leafygreen-ui/banner';
import { css } from '@leafygreen-ui/emotion';
import { spacing } from '@leafygreen-ui/tokens';

import { Banner } from './leafygreen';

const errorContainerStyles = css({
padding: spacing[3],
width: '100%',
Expand Down
26 changes: 22 additions & 4 deletions packages/compass-components/src/components/file-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@ import path from 'path';
import { css, cx } from '@leafygreen-ui/emotion';
import { uiColors } from '@leafygreen-ui/palette';
import { spacing } from '@leafygreen-ui/tokens';
import { withTheme } from '../hooks/use-theme';

import { Button, Icon, IconButton, Label, Link, Description } from '..';
import {
Button,
Icon,
IconButton,
Label,
Link,
Description,
} from './leafygreen';

const { base: redBaseColor } = uiColors.red;

Expand Down Expand Up @@ -79,10 +87,14 @@ const labelIconStyles = css({
},
});

const disabledDescriptionStyles = css({
const disabledDescriptionLightStyles = css({
color: uiColors.gray.dark1,
});

const disabledDescriptionDarkStyles = css({
color: uiColors.gray.light1,
});

export enum Variant {
Horizontal = 'HORIZONTAL',
Vertical = 'VERTICAL',
Expand All @@ -98,6 +110,7 @@ function FileInput({
id,
label,
dataTestId,
darkMode,
onChange,
disabled,
multi = false,
Expand All @@ -115,6 +128,7 @@ function FileInput({
label: string;
dataTestId?: string;
onChange: (files: string[]) => void;
darkMode?: boolean;
disabled?: boolean;
multi?: boolean;
optional?: boolean;
Expand Down Expand Up @@ -171,6 +185,8 @@ function FileInput({
);
};

const applyTheme = global?.process?.env?.COMPASS_LG_DARKMODE === 'true';

return (
<div>
<div
Expand All @@ -187,7 +203,9 @@ function FileInput({
<Label htmlFor={`${id}_file_input`} disabled={disabled}>
<span
className={cx({
[disabledDescriptionStyles]: disabled,
[applyTheme && darkMode
? disabledDescriptionDarkStyles
: disabledDescriptionLightStyles]: disabled,
})}
>
{label}
Expand Down Expand Up @@ -261,4 +279,4 @@ function FileInput({
);
}

export default FileInput;
export default withTheme(FileInput);
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { Body } from '@leafygreen-ui/typography';
import { css } from '@leafygreen-ui/emotion';
import { Body } from './leafygreen';
import { Tooltip } from './tooltip';
import { mergeProps } from '../utils/merge-props';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import { css } from '@leafygreen-ui/emotion';
import { spacing } from '@leafygreen-ui/tokens';
import IconButton from '@leafygreen-ui/icon-button';
import Icon from '@leafygreen-ui/icon';

import { Icon, IconButton } from './leafygreen';

const infoButtonStyles = css({
marginTop: -spacing[2],
Expand Down
188 changes: 188 additions & 0 deletions packages/compass-components/src/components/leafygreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import type React from 'react';

import { withTheme } from '../hooks/use-theme';

// This file exports `@leafygreen-ui` components and wraps some of
// them with a listener to Compass' theme in the react context.

// 1. Import the components we use from leafygreen.
import { default as Badge } from '@leafygreen-ui/badge';
import { default as Banner } from '@leafygreen-ui/banner';
import { default as LeafyGreenButton } from '@leafygreen-ui/button';
import { default as LeafyGreenCheckbox } from '@leafygreen-ui/checkbox';
import { default as LeafyGreenCard } from '@leafygreen-ui/card';
import { default as LeafyGreenConfirmationModal } from '@leafygreen-ui/confirmation-modal';
import { default as Icon } from '@leafygreen-ui/icon';
import { default as LeafyGreenIconButton } from '@leafygreen-ui/icon-button';
import {
AtlasLogoMark,
MongoDBLogoMark,
MongoDBLogo,
} from '@leafygreen-ui/logo';
import { Menu, MenuSeparator, MenuItem } from '@leafygreen-ui/menu';
import {
default as LeafyGreenModal,
Footer as LeafyGreenFooter,
} from '@leafygreen-ui/modal';
import { RadioBox, RadioBoxGroup } from '@leafygreen-ui/radio-box-group';
import {
Radio,
RadioGroup as LeafyGreenRadioGroup,
} from '@leafygreen-ui/radio-group';
import {
SegmentedControl as LeafyGreenSegmentedControl,
SegmentedControlOption,
} from '@leafygreen-ui/segmented-control';
import {
Select as LeafyGreenSelect,
Option,
OptionGroup,
} from '@leafygreen-ui/select';
import {
Table as LeafyGreenTable,
TableHeader,
Row,
Cell,
} from '@leafygreen-ui/table';
import { Tabs as LeafyGreenTabs, Tab } from '@leafygreen-ui/tabs';
import { default as LeafyGreenTextArea } from '@leafygreen-ui/text-area';
import { default as LeafyGreenTextInput } from '@leafygreen-ui/text-input';
import { default as Toast } from '@leafygreen-ui/toast';
import { default as LeafyGreenToggle } from '@leafygreen-ui/toggle';
import { default as LeafyGreenTooltip } from '@leafygreen-ui/tooltip';
import {
H1,
H2,
H3,
Subtitle,
Body,
InlineCode,
InlineKeyCode,
Disclaimer,
Overline,
Label as LeafyGreenLabel,
Link,
Description as LeafyGreenDescription,
} from '@leafygreen-ui/typography';

// 2. Wrap the components that accept darkMode with Compass' theme.
const Button = withTheme(
LeafyGreenButton as React.ComponentType<
React.ComponentProps<typeof LeafyGreenButton>
>
) as typeof LeafyGreenButton;
Copy link
Member Author

@Anemy Anemy Mar 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working on a cleaner way to type the high order component that wraps these so we don't have to do this type casting.
We'll want to use this wrapper with some of our own custom components - checking to make sure those types work well too.

Copy link
Collaborator

@mcasimir mcasimir Mar 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok! let me know when i can review again

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added withTheme to the FileInput as an example/test of using the theme in a custom component, works smooth. I spent a while trying to get these types for these leafygreen components to work nicely yesterday and was having trouble finding a clean way without casting explicitly here. Do you think it's a blocker for merging? I'd be down to pair on looking at the withTheme hoc to see if there's anything we can do to avoid casts. If we do merge I'd definitely want to solidify these types before the epic is done, but for now we would be able to unblock theming other parts of this epic.

It appears typescript tries to compare the inferred react prop types with the typescript prop definition and that results in a typing/compile error. To get around this we explicitly cast them to their leafygreen types.

Here's examples of the two errors we were seeing:

Exported variable 'Toggle' has or is using name 'BaseToggleProps' from external module "/Users/rhys/Documents/mongodb/compass/node_modules/@leafygreen-ui/toggle/dist/Toggle" but cannot be named.
Argument of type 'typeof Toggle' is not assignable to parameter of type 'ComponentType<ToggleProps>'.
  Type 'typeof Toggle' is not assignable to type 'FunctionComponent<ToggleProps>'.
    Types of property 'propTypes' are incompatible.
      Type '{ size: Requireable<string>; darkMode: Requireable<boolean>; checked: Requireable<boolean>; disabled: Requireable<boolean>; className: Requireable<...>; onChange: Requireable<...>; onClick: Requireable<...>; }' is not assignable to type 'WeakValidationMap<ToggleProps> | undefined'.
        Type '{ size: Requireable<string>; darkMode: Requireable<boolean>; checked: Requireable<boolean>; disabled: Requireable<boolean>; className: Requireable<...>; onChange: Requireable<...>; onClick: Requireable<...>; }' is not assignable to type 'WeakValidationMap<Omit<BaseToggleProps & Omit<HTMLElementProps<"button", never>, keyof BaseToggleProps>, "aria-label" | "aria-labelledby"> & Required<...> & Partial<...>>'.
          The types of 'size[nominalTypeHack]' are incompatible between these types.
            Type '{ type: string | null | undefined; } | undefined' is not assignable to type '{ type: Size | null | undefined; } | undefined'.
              Type '{ type: string | null | undefined; }' is not assignable to type '{ type: Size | null | undefined; }'.
                Types of property 'type' are incompatible.
                  Type 'string | null | undefined' is not assignable to type 'Size | null | undefined'.
                    Type 'string' is not assignable to type 'Size | null | undefined'.

Copy link
Member Author

@Anemy Anemy Mar 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcasimir Pushed an update to make the components that use the withTheme wrapper require an optional darkMode prop. Also added an explicit cast to the generic type in the hoc.
There's a good answer on it here: https://stackoverflow.com/a/58473012

const Card: typeof LeafyGreenCard = withTheme(
LeafyGreenCard as React.ComponentType<
React.ComponentProps<typeof LeafyGreenCard>
>
) as typeof LeafyGreenCard;
const Checkbox = withTheme(
LeafyGreenCheckbox as React.ComponentType<
React.ComponentProps<typeof LeafyGreenCheckbox>
>
) as typeof LeafyGreenCheckbox;
const ConfirmationModal: typeof LeafyGreenConfirmationModal = withTheme(
LeafyGreenConfirmationModal as React.ComponentType<
React.ComponentProps<typeof LeafyGreenConfirmationModal>
>
) as typeof LeafyGreenConfirmationModal;
const IconButton: typeof LeafyGreenIconButton = withTheme(
LeafyGreenIconButton as React.ComponentType<
React.ComponentProps<typeof LeafyGreenIconButton>
>
) as typeof LeafyGreenIconButton;
const Footer: typeof LeafyGreenFooter = withTheme(
LeafyGreenFooter
) as typeof LeafyGreenFooter;
const Modal = withTheme(
LeafyGreenModal as React.ComponentType<
React.ComponentProps<typeof LeafyGreenModal>
>
) as typeof LeafyGreenModal;
const RadioGroup: typeof LeafyGreenRadioGroup = withTheme(
LeafyGreenRadioGroup as React.ComponentType<
React.ComponentProps<typeof LeafyGreenRadioGroup>
>
) as typeof LeafyGreenRadioGroup;
const SegmentedControl: typeof LeafyGreenSegmentedControl = withTheme(
LeafyGreenSegmentedControl
) as typeof LeafyGreenSegmentedControl;
const Select: typeof LeafyGreenSelect = withTheme(
LeafyGreenSelect as React.ComponentType<
React.ComponentProps<typeof LeafyGreenSelect>
>
) as typeof LeafyGreenSelect;
const Table = withTheme(LeafyGreenTable) as typeof LeafyGreenTable;
const Tabs = withTheme(
LeafyGreenTabs as React.ComponentType<
React.ComponentProps<typeof LeafyGreenTabs>
>
) as typeof LeafyGreenTabs;
const TextArea: typeof LeafyGreenTextArea = withTheme(LeafyGreenTextArea);
const TextInput: typeof LeafyGreenTextInput = withTheme(LeafyGreenTextInput);
const Toggle = withTheme(
LeafyGreenToggle as React.ComponentType<
React.ComponentProps<typeof LeafyGreenToggle>
>
) as typeof LeafyGreenToggle;
const Tooltip = withTheme(
LeafyGreenTooltip as React.ComponentType<
React.ComponentProps<typeof LeafyGreenTooltip>
>
) as typeof LeafyGreenTooltip;
const Label = withTheme(LeafyGreenLabel) as typeof LeafyGreenLabel;
const Description = withTheme(
LeafyGreenDescription
) as typeof LeafyGreenDescription;

// 3. Export the leafygreen components.
export {
AtlasLogoMark,
Badge,
Banner,
Button,
Card,
Checkbox,
ConfirmationModal,
Icon,
IconButton,
Footer,
Menu,
MenuItem,
MenuSeparator,
Modal,
MongoDBLogoMark,
MongoDBLogo,
RadioBox,
RadioBoxGroup,
Radio,
RadioGroup,
SegmentedControl,
SegmentedControlOption,
Select,
Option,
OptionGroup,
Table,
TableHeader,
Row,
Cell,
Tab,
Tabs,
TextArea,
TextInput,
Toast,
Toggle,
Tooltip,
H1,
H2,
H3,
Subtitle,
Body,
InlineCode,
InlineKeyCode,
Disclaimer,
Overline,
Label,
Link,
Description,
};
3 changes: 2 additions & 1 deletion packages/compass-components/src/components/modal.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useEffect } from 'react';
import LeafyGreenModal from '@leafygreen-ui/modal';
import { createLoggerAndTelemetry } from '@mongodb-js/compass-logging';
const { track } = createLoggerAndTelemetry('COMPASS-UI');

import { Modal as LeafyGreenModal } from './leafygreen';

function Modal({
trackingId,
...props
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import { css } from '@leafygreen-ui/emotion';
import { spacing } from '@leafygreen-ui/tokens';
import { RadioBoxGroup as LeafyGreenRadioBoxGroup } from '@leafygreen-ui/radio-box-group';

import { RadioBoxGroup as LeafyGreenRadioBoxGroup } from './leafygreen';

const radioBoxGroupStyles = css({
marginTop: spacing[1],
Expand Down
2 changes: 1 addition & 1 deletion packages/compass-components/src/components/toggle.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import LeafyGreenToggle from '@leafygreen-ui/toggle';

import { Toggle as LeafyGreenToggle } from './leafygreen';
import { Theme, useTheme } from '../hooks/use-theme';

function Toggle(
Expand Down
2 changes: 1 addition & 1 deletion packages/compass-components/src/components/tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint-disable react/prop-types */
import { useTooltipTriggerState } from '@react-stately/tooltip';
import { useTooltipTrigger } from '@react-aria/tooltip';
import LeafyGreenTooltip from '@leafygreen-ui/tooltip';
import React, { useCallback, useRef } from 'react';
import { Tooltip as LeafyGreenTooltip } from './leafygreen';
import { mergeProps } from '../utils/merge-props';

/**
Expand Down
Loading