chore: Enable recommended React eslint rules (#5600)

This commit is contained in:
Tom Moor
2023-07-24 21:23:54 -04:00
committed by GitHub
parent 8865d394c6
commit e0289aed40
60 changed files with 586 additions and 494 deletions

View File

@@ -1,6 +1,7 @@
{
"extends": [
"../.eslintrc",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
],
"plugins": [

View File

@@ -61,8 +61,11 @@ export const openDocument = createAction({
// cache if the document is renamed
id: path.url,
name: path.title,
icon: () =>
stores.documents.get(path.id)?.isStarred ? <StarredIcon /> : null,
icon: function _Icon() {
return stores.documents.get(path.id)?.isStarred ? (
<StarredIcon />
) : null;
},
section: DocumentSection,
perform: () => history.push(path.url),
}));

View File

@@ -43,8 +43,9 @@ export const changeTheme = createAction({
isContextMenu ? t("Appearance") : t("Change theme"),
analyticsName: "Change theme",
placeholder: ({ t }) => t("Change theme to"),
icon: () =>
stores.ui.resolvedTheme === "light" ? <SunIcon /> : <MoonIcon />,
icon: function _Icon() {
return stores.ui.resolvedTheme === "light" ? <SunIcon /> : <MoonIcon />;
},
keywords: "appearance display",
section: SettingsSection,
children: [changeToLightTheme, changeToDarkTheme, changeToSystemTheme],

View File

@@ -16,18 +16,20 @@ export const createTeamsList = ({ stores }: { stores: RootStore }) =>
analyticsName: "Switch workspace",
section: TeamSection,
keywords: "change switch workspace organization team",
icon: () => (
<StyledTeamLogo
alt={session.name}
model={{
initial: session.name[0],
avatarUrl: session.avatarUrl,
id: session.id,
color: stringToColor(session.id),
}}
size={24}
/>
),
icon: function _Icon() {
return (
<StyledTeamLogo
alt={session.name}
model={{
initial: session.name[0],
avatarUrl: session.avatarUrl,
id: session.id,
color: stringToColor(session.id),
}}
size={24}
/>
);
},
visible: ({ currentTeamId }: ActionContext) => currentTeamId !== session.id,
perform: () => (window.location.href = session.url),
})) ?? [];

View File

@@ -1,8 +1,9 @@
/* eslint-disable react/prop-types */
import * as React from "react";
import Tooltip, { Props as TooltipProps } from "~/components/Tooltip";
import { Action, ActionContext } from "~/types";
export type Props = React.ComponentPropsWithoutRef<"button"> & {
export type Props = React.HTMLAttributes<HTMLButtonElement> & {
/** Show the button in a disabled state */
disabled?: boolean;
/** Hide the button entirely if action is not applicable */
@@ -18,11 +19,11 @@ export type Props = React.ComponentPropsWithoutRef<"button"> & {
/**
* Button that can be used to trigger an action definition.
*/
const ActionButton = React.forwardRef(
(
const ActionButton = React.forwardRef<HTMLButtonElement, Props>(
function _ActionButton(
{ action, context, tooltip, hideOnActionDisabled, ...rest }: Props,
ref: React.Ref<HTMLButtonElement>
) => {
) {
const [executing, setExecuting] = React.useState(false);
const disabled = rest.disabled;

View File

@@ -5,7 +5,11 @@ import * as React from "react";
import { IntegrationService } from "@shared/types";
import env from "~/env";
const Analytics: React.FC = ({ children }) => {
type Props = {
children?: React.ReactNode;
};
const Analytics: React.FC = ({ children }: Props) => {
// Google Analytics 3
React.useEffect(() => {
if (!env.GOOGLE_ANALYTICS_ID?.startsWith("UA-")) {

View File

@@ -37,7 +37,11 @@ const DocumentInsights = lazyWithRetry(
);
const CommandBar = lazyWithRetry(() => import("~/components/CommandBar"));
const AuthenticatedLayout: React.FC = ({ children }) => {
type Props = {
children?: React.ReactNode;
};
const AuthenticatedLayout: React.FC = ({ children }: Props) => {
const { ui, auth } = useStores();
const location = useLocation();
const can = usePolicy(ui.activeCollectionId);

View File

@@ -3,6 +3,7 @@ import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
type Props = {
children?: React.ReactNode;
withStickyHeader?: boolean;
};
@@ -26,7 +27,7 @@ const Content = styled.div`
`};
`;
const CenteredContent: React.FC<Props> = ({ children, ...rest }) => (
const CenteredContent: React.FC<Props> = ({ children, ...rest }: Props) => (
<Container {...rest}>
<Content>{children}</Content>
</Container>

View File

@@ -52,7 +52,11 @@ function CommandBar() {
);
}
const KBarPortal: React.FC = ({ children }) => {
type Props = {
children?: React.ReactNode;
};
const KBarPortal: React.FC = ({ children }: Props) => {
const { showing } = useKBar((state) => ({
showing: state.visualState !== "hidden",
}));

View File

@@ -17,6 +17,7 @@ type Props = {
danger?: boolean;
/** Keep the submit button disabled */
disabled?: boolean;
children?: React.ReactNode;
};
const ConfirmationDialog: React.FC<Props> = ({
@@ -26,7 +27,7 @@ const ConfirmationDialog: React.FC<Props> = ({
savingText,
danger,
disabled = false,
}) => {
}: Props) => {
const [isSaving, setIsSaving] = React.useState(false);
const { dialogs } = useStores();
const { showToast } = useToasts();

View File

@@ -30,144 +30,138 @@ export type RefHandle = {
* Defines a content editable component with the same interface as a native
* HTMLInputElement (or, as close as we can get).
*/
const ContentEditable = React.forwardRef(
(
{
disabled,
onChange,
onInput,
onBlur,
onKeyDown,
value,
children,
className,
maxLength,
autoFocus,
placeholder,
readOnly,
dir,
onClick,
...rest
}: Props,
ref: React.RefObject<RefHandle>
) => {
const contentRef = React.useRef<HTMLSpanElement>(null);
const [innerValue, setInnerValue] = React.useState<string>(value);
const lastValue = React.useRef(value);
const ContentEditable = React.forwardRef(function _ContentEditable(
{
disabled,
onChange,
onInput,
onBlur,
onKeyDown,
value,
children,
className,
maxLength,
autoFocus,
placeholder,
readOnly,
dir,
onClick,
...rest
}: Props,
ref: React.RefObject<RefHandle>
) {
const contentRef = React.useRef<HTMLSpanElement>(null);
const [innerValue, setInnerValue] = React.useState<string>(value);
const lastValue = React.useRef(value);
React.useImperativeHandle(ref, () => ({
focus: () => {
if (contentRef.current) {
contentRef.current.focus();
// looks unnecessary but required because of https://github.com/outline/outline/issues/5198
if (!contentRef.current.innerText) {
placeCaret(contentRef.current, true);
}
}
},
focusAtStart: () => {
if (contentRef.current) {
contentRef.current.focus();
React.useImperativeHandle(ref, () => ({
focus: () => {
if (contentRef.current) {
contentRef.current.focus();
// looks unnecessary but required because of https://github.com/outline/outline/issues/5198
if (!contentRef.current.innerText) {
placeCaret(contentRef.current, true);
}
},
focusAtEnd: () => {
if (contentRef.current) {
contentRef.current.focus();
placeCaret(contentRef.current, false);
}
},
getComputedDirection: () => {
if (contentRef.current) {
return window.getComputedStyle(contentRef.current).direction;
}
return "ltr";
},
}));
const wrappedEvent =
(
callback:
| React.FocusEventHandler<HTMLSpanElement>
| React.FormEventHandler<HTMLSpanElement>
| React.KeyboardEventHandler<HTMLSpanElement>
| undefined
) =>
(event: any) => {
if (readOnly) {
return;
}
const text = event.currentTarget.textContent || "";
if (
maxLength &&
isPrintableKeyEvent(event) &&
text.length >= maxLength
) {
event?.preventDefault();
return;
}
if (text !== lastValue.current) {
lastValue.current = text;
onChange?.(text);
}
callback?.(event);
};
// This is to account for being within a React.Suspense boundary, in this
// case the component may be rendered with display: none. React 18 may solve
// this in the future by delaying useEffect hooks:
// https://github.com/facebook/react/issues/14536#issuecomment-861980492
const isVisible = useOnScreen(contentRef);
React.useEffect(() => {
if (autoFocus && isVisible && !disabled && !readOnly) {
contentRef.current?.focus();
}
}, [autoFocus, disabled, isVisible, readOnly, contentRef]);
React.useEffect(() => {
if (contentRef.current && value !== contentRef.current.textContent) {
setInnerValue(value);
},
focusAtStart: () => {
if (contentRef.current) {
contentRef.current.focus();
placeCaret(contentRef.current, true);
}
}, [value, contentRef]);
},
focusAtEnd: () => {
if (contentRef.current) {
contentRef.current.focus();
placeCaret(contentRef.current, false);
}
},
getComputedDirection: () => {
if (contentRef.current) {
return window.getComputedStyle(contentRef.current).direction;
}
return "ltr";
},
}));
// Ensure only plain text can be pasted into input when pasting from another
// rich text source. Note: If `onPaste` prop is passed then it takes
// priority over this behavior.
const handlePaste = React.useCallback(
(event: React.ClipboardEvent<HTMLSpanElement>) => {
event.preventDefault();
const text = event.clipboardData.getData("text/plain");
window.document.execCommand("insertText", false, text);
},
[]
);
const wrappedEvent =
(
callback:
| React.FocusEventHandler<HTMLSpanElement>
| React.FormEventHandler<HTMLSpanElement>
| React.KeyboardEventHandler<HTMLSpanElement>
| undefined
) =>
(event: any) => {
if (readOnly) {
return;
}
return (
<div className={className} dir={dir} onClick={onClick}>
<Content
ref={contentRef}
contentEditable={!disabled && !readOnly}
onInput={wrappedEvent(onInput)}
onBlur={wrappedEvent(onBlur)}
onKeyDown={wrappedEvent(onKeyDown)}
onPaste={handlePaste}
data-placeholder={placeholder}
suppressContentEditableWarning
role="textbox"
{...rest}
>
{innerValue}
</Content>
{children}
</div>
);
}
);
const text = event.currentTarget.textContent || "";
if (maxLength && isPrintableKeyEvent(event) && text.length >= maxLength) {
event?.preventDefault();
return;
}
if (text !== lastValue.current) {
lastValue.current = text;
onChange?.(text);
}
callback?.(event);
};
// This is to account for being within a React.Suspense boundary, in this
// case the component may be rendered with display: none. React 18 may solve
// this in the future by delaying useEffect hooks:
// https://github.com/facebook/react/issues/14536#issuecomment-861980492
const isVisible = useOnScreen(contentRef);
React.useEffect(() => {
if (autoFocus && isVisible && !disabled && !readOnly) {
contentRef.current?.focus();
}
}, [autoFocus, disabled, isVisible, readOnly, contentRef]);
React.useEffect(() => {
if (contentRef.current && value !== contentRef.current.textContent) {
setInnerValue(value);
}
}, [value, contentRef]);
// Ensure only plain text can be pasted into input when pasting from another
// rich text source. Note: If `onPaste` prop is passed then it takes
// priority over this behavior.
const handlePaste = React.useCallback(
(event: React.ClipboardEvent<HTMLSpanElement>) => {
event.preventDefault();
const text = event.clipboardData.getData("text/plain");
window.document.execCommand("insertText", false, text);
},
[]
);
return (
<div className={className} dir={dir} onClick={onClick}>
<Content
ref={contentRef}
contentEditable={!disabled && !readOnly}
onInput={wrappedEvent(onInput)}
onBlur={wrappedEvent(onBlur)}
onKeyDown={wrappedEvent(onKeyDown)}
onPaste={handlePaste}
data-placeholder={placeholder}
suppressContentEditableWarning
role="textbox"
{...rest}
>
{innerValue}
</Content>
{children}
</div>
);
});
function placeCaret(element: HTMLElement, atStart: boolean) {
if (

View File

@@ -22,6 +22,7 @@ type Props = {
level?: number;
icon?: React.ReactElement;
children?: React.ReactNode;
ref?: React.LegacyRef<HTMLButtonElement> | undefined;
};
const MenuItem = (
@@ -80,7 +81,7 @@ const MenuItem = (
</MenuAnchor>
);
},
[active, as, hide, icon, onClick, ref, selected]
[active, as, hide, icon, onClick, ref, children, selected]
);
return (

View File

@@ -44,37 +44,35 @@ type SubMenuProps = MenuStateReturn & {
title: React.ReactNode;
};
const SubMenu = React.forwardRef(
(
{ templateItems, title, parentMenuState, ...rest }: SubMenuProps,
ref: React.LegacyRef<HTMLButtonElement>
) => {
const { t } = useTranslation();
const theme = useTheme();
const menu = useMenuState();
const SubMenu = React.forwardRef(function _Template(
{ templateItems, title, parentMenuState, ...rest }: SubMenuProps,
ref: React.LegacyRef<HTMLButtonElement>
) {
const { t } = useTranslation();
const theme = useTheme();
const menu = useMenuState();
return (
<>
<MenuButton ref={ref} {...menu} {...rest}>
{(props) => (
<MenuAnchor disclosure {...props}>
{title} <Disclosure color={theme.textTertiary} />
</MenuAnchor>
)}
</MenuButton>
<ContextMenu
{...menu}
aria-label={t("Submenu")}
onClick={parentMenuState.hide}
parentMenuState={parentMenuState}
>
<MouseSafeArea parentRef={menu.unstable_popoverRef} />
<Template {...menu} items={templateItems} />
</ContextMenu>
</>
);
}
);
return (
<>
<MenuButton ref={ref} {...menu} {...rest}>
{(props) => (
<MenuAnchor disclosure {...props}>
{title} <Disclosure color={theme.textTertiary} />
</MenuAnchor>
)}
</MenuButton>
<ContextMenu
{...menu}
aria-label={t("Submenu")}
onClick={parentMenuState.hide}
parentMenuState={parentMenuState}
>
<MouseSafeArea parentRef={menu.unstable_popoverRef} />
<Template {...menu} items={templateItems} />
</ContextMenu>
</>
);
});
export function filterTemplateItems(items: TMenuItem[]): TMenuItem[] {
return items

View File

@@ -46,6 +46,7 @@ type Props = MenuStateReturn & {
onClose?: () => void;
/** Called when the context menu is clicked. */
onClick?: (ev: React.MouseEvent) => void;
children?: React.ReactNode;
};
const ContextMenu: React.FC<Props> = ({
@@ -54,7 +55,7 @@ const ContextMenu: React.FC<Props> = ({
onClose,
parentMenuState,
...rest
}) => {
}: Props) => {
const previousVisible = usePrevious(rest.visible);
const maxHeight = useMenuHeight({
visible: rest.visible,

View File

@@ -17,6 +17,7 @@ import {
} from "~/utils/routeHelpers";
type Props = {
children?: React.ReactNode;
document: Document;
onlyText?: boolean;
};
@@ -58,7 +59,7 @@ const DocumentBreadcrumb: React.FC<Props> = ({
document,
children,
onlyText,
}) => {
}: Props) => {
const { collections } = useStores();
const { t } = useTranslation();
const category = useCategory(document);
@@ -129,7 +130,11 @@ const DocumentBreadcrumb: React.FC<Props> = ({
);
}
return <Breadcrumb items={items} children={children} highlightFirstItem />;
return (
<Breadcrumb items={items} highlightFirstItem>
{children}
</Breadcrumb>
);
};
const SmallSlash = styled(GoToIcon)`

View File

@@ -335,16 +335,21 @@ function DocumentExplorer({ onSubmit, onSelect, items }: Props) {
const innerElementType = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ style, ...rest }, ref) => (
<div
ref={ref}
style={{
...style,
height: `${parseFloat(style?.height + "") + VERTICAL_PADDING * 2}px`,
}}
{...rest}
/>
));
>(function innerElementType(
{ style, ...rest }: React.HTMLAttributes<HTMLDivElement>,
ref
) {
return (
<div
ref={ref}
style={{
...style,
height: `${parseFloat(style?.height + "") + VERTICAL_PADDING * 2}px`,
}}
{...rest}
/>
);
});
return (
<Container tabIndex={-1} onKeyDown={handleKeyDown}>

View File

@@ -15,6 +15,7 @@ import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
type Props = {
children?: React.ReactNode;
showCollection?: boolean;
showPublished?: boolean;
showLastViewed?: boolean;
@@ -36,7 +37,7 @@ const DocumentMeta: React.FC<Props> = ({
replace,
to,
...rest
}) => {
}: Props) => {
const { t } = useTranslation();
const { collections } = useStores();
const user = useCurrentUser();

View File

@@ -1,10 +1,11 @@
import * as React from "react";
type Props = {
children?: React.ReactNode;
className?: string;
};
const EventBoundary: React.FC<Props> = ({ children, className }) => {
const EventBoundary: React.FC<Props> = ({ children, className }: Props) => {
const handleClick = React.useCallback((event: React.SyntheticEvent) => {
event.preventDefault();
event.stopPropagation();

View File

@@ -160,15 +160,16 @@ const EventListItem = ({ event, latest, document, ...rest }: Props) => {
);
};
const BaseItem = React.forwardRef(
({ to, ...rest }: ItemProps, ref?: React.Ref<HTMLAnchorElement>) => {
if (to) {
return <CompositeListItem to={to} ref={ref} {...rest} />;
}
return <ListItem ref={ref} {...rest} />;
const BaseItem = React.forwardRef(function _BaseItem(
{ to, ...rest }: ItemProps,
ref?: React.Ref<HTMLAnchorElement>
) {
if (to) {
return <CompositeListItem to={to} ref={ref} {...rest} />;
}
);
return <ListItem ref={ref} {...rest} />;
});
const Subtitle = styled.span`
svg {

View File

@@ -99,7 +99,7 @@ function ExportDialog({ collection, onSubmit }: Props) {
)}
<Flex gap={12} column>
{items.map((item) => (
<Option>
<Option key={item.value}>
<input
type="radio"
name="format"

View File

@@ -6,6 +6,7 @@ import Scrollable from "~/components/Scrollable";
import usePrevious from "~/hooks/usePrevious";
type Props = {
children?: React.ReactNode;
isOpen: boolean;
title?: string;
onRequestClose: () => void;
@@ -17,7 +18,7 @@ const Guide: React.FC<Props> = ({
title = "Untitled",
onRequestClose,
...rest
}) => {
}: Props) => {
const dialog = useDialogState({
animated: 250,
});

View File

@@ -24,7 +24,7 @@ export default function MarkdownIcon({
<path
d="M19.2692 7H3.86538C3.38745 7 3 7.38476 3 7.85938V16.2812C3 16.7559 3.38745 17.1406 3.86538 17.1406H19.2692C19.7472 17.1406 20.1346 16.7559 20.1346 16.2812V7.85938C20.1346 7.38476 19.7472 7 19.2692 7Z"
stroke={color}
stroke-width="2"
strokeWidth="2"
/>
<path
d="M5.16345 14.9922V9.14844H6.89422L8.62499 11.2969L10.3558 9.14844H12.0865V14.9922H10.3558V11.6406L8.62499 13.7891L6.89422 11.6406V14.9922H5.16345ZM15.9808 14.9922L13.3846 12.1562H15.1154V9.14844H16.8461V12.1562H18.5769L15.9808 14.9922Z"

View File

@@ -16,7 +16,7 @@ type Props = Omit<InputProps, "onChange"> & {
onChange: (value: string) => void;
};
const InputColor: React.FC<Props> = ({ value, onChange, ...rest }) => {
const InputColor: React.FC<Props> = ({ value, onChange, ...rest }: Props) => {
const { t } = useTranslation();
const menu = useMenuState({
modal: true,

View File

@@ -5,10 +5,11 @@ import { s } from "@shared/styles";
import Flex from "~/components/Flex";
type Props = {
children?: React.ReactNode;
label: React.ReactNode | string;
};
const Labeled: React.FC<Props> = ({ label, children, ...props }) => (
const Labeled: React.FC<Props> = ({ label, children, ...props }: Props) => (
<Flex column {...props}>
<Label>{label}</Label>
{children}

View File

@@ -21,14 +21,14 @@ function Icon({ className }: { className?: string }) {
className={className}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M21 18H16L14 16V6C14 4.89543 14.8954 4 16 4H28C29.1046 4 30 4.89543 30 6V16C30 17.1046 29.1046 18 28 18H27L25.4142 19.5858C24.6332 20.3668 23.3668 20.3668 22.5858 19.5858L21 18ZM16 15.1716V6H28V16H27H26.1716L25.5858 16.5858L24 18.1716L22.4142 16.5858L21.8284 16H21H16.8284L16 15.1716Z"
fill="#2B2F35"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M16 13H4C2.89543 13 2 13.8954 2 15V25C2 26.1046 2.89543 27 4 27H5L6.58579 28.5858C7.36684 29.3668 8.63316 29.3668 9.41421 28.5858L11 27H16C17.1046 27 18 26.1046 18 25V15C18 13.8954 17.1046 13 16 13ZM9 17L6 16.9681C6 16.9681 5 17.016 5 18C5 18.984 6 19 6 19H8.5H10C10 19 9.57627 20.1885 8.38983 21.0831C7.20339 21.9777 5.7197 23 5.7197 23C5.7197 23 4.99153 23.6054 5.5 24.5C6.00847 25.3946 7 24.8403 7 24.8403L9.74576 22.8722L11.9492 24.6614C11.9492 24.6614 12.6271 25.3771 13.3051 24.4825C13.9831 23.5879 13.3051 23.0512 13.3051 23.0512L11.1017 21.262C11.1017 21.262 11.5 21 12 20L12.5 19H14C14 19 15 19.0319 15 18C15 16.9681 14 16.9681 14 16.9681L11 17V16C11 16 11.0169 15 10 15C8.98305 15 9 16 9 16V17Z"
fill="#2B2F35"
/>

View File

@@ -16,6 +16,7 @@ import useStores from "~/hooks/useStores";
import { isModKey } from "~/utils/keyboard";
type Props = {
children?: React.ReactNode;
title?: string;
sidebar?: React.ReactNode;
sidebarRight?: React.ReactNode;
@@ -26,7 +27,7 @@ const Layout: React.FC<Props> = ({
children,
sidebar,
sidebarRight,
}) => {
}: Props) => {
const { ui } = useStores();
const sidebarCollapsed = !sidebar || ui.sidebarIsClosed;

View File

@@ -2,10 +2,14 @@ import * as React from "react";
import Logger from "~/utils/Logger";
import { loadPolyfills } from "~/utils/polyfills";
type Props = {
children?: React.ReactNode;
};
/**
* Asyncronously load required polyfills. Should wrap the React tree.
*/
export const LazyPolyfill: React.FC = ({ children }) => {
export const LazyPolyfill: React.FC = ({ children }: Props) => {
const [isLoaded, setIsLoaded] = React.useState(false);
React.useEffect(() => {

View File

@@ -21,6 +21,7 @@ function eachMinute(fn: () => void) {
}
type Props = {
children?: React.ReactNode;
dateTime: string;
tooltipDelay?: number;
addSuffix?: boolean;
@@ -37,7 +38,7 @@ const LocaleTime: React.FC<Props> = ({
format,
relative,
tooltipDelay,
}) => {
}: Props) => {
const userLocale: string = useUserLocale() || "";
const dateFormatLong = {
en_US: "MMMM do, yyyy h:mm a",

View File

@@ -19,7 +19,9 @@ import Desktop from "~/utils/Desktop";
import ErrorBoundary from "./ErrorBoundary";
let openModals = 0;
type Props = {
children?: React.ReactNode;
isOpen: boolean;
isCentered?: boolean;
title?: React.ReactNode;
@@ -32,7 +34,7 @@ const Modal: React.FC<Props> = ({
isCentered,
title = "Untitled",
onRequestClose,
}) => {
}: Props) => {
const dialog = useDialogState({
animated: 250,
});

View File

@@ -5,11 +5,12 @@ import Flex from "./Flex";
import Text from "./Text";
type Props = {
children?: React.ReactNode;
icon?: JSX.Element;
description?: JSX.Element;
};
const Notice: React.FC<Props> = ({ children, icon, description }) => (
const Notice: React.FC<Props> = ({ children, icon, description }: Props) => (
<Container>
<Flex as="span" gap={8}>
{icon}

View File

@@ -7,7 +7,11 @@ import { depths } from "@shared/styles";
import Popover from "~/components/Popover";
import Notifications from "./Notifications";
const NotificationsPopover: React.FC = ({ children }) => {
type Props = {
children?: React.ReactNode;
};
const NotificationsPopover: React.FC = ({ children }: Props) => {
const { t } = useTranslation();
const scrollableRef = React.useRef<HTMLDivElement>(null);

View File

@@ -25,7 +25,7 @@ const Popover: React.FC<Props> = ({
flex,
mobilePosition,
...rest
}) => {
}: Props) => {
const isMobile = useMobile();
if (isMobile) {

View File

@@ -11,6 +11,7 @@ type Props = {
left?: React.ReactNode;
actions?: React.ReactNode;
centered?: boolean;
children?: React.ReactNode;
};
const Scene: React.FC<Props> = ({
@@ -21,7 +22,7 @@ const Scene: React.FC<Props> = ({
left,
children,
centered,
}) => (
}: Props) => (
<FillWidth>
<PageTitle title={textTitle || title} />
<Header

View File

@@ -17,7 +17,7 @@ import useStores from "~/hooks/useStores";
import { SearchResult } from "~/types";
import SearchListItem from "./SearchListItem";
type Props = { shareId: string };
type Props = React.HTMLAttributes<HTMLInputElement> & { shareId: string };
function SearchPopover({ shareId }: Props) {
const { t } = useTranslation();
@@ -32,6 +32,7 @@ function SearchPopover({ shareId }: Props) {
const [query, setQuery] = React.useState("");
const searchResults = documents.searchResults(query);
const { show, hide } = popover;
const [cachedQuery, setCachedQuery] = React.useState(query);
const [cachedSearchResults, setCachedSearchResults] = React.useState<
@@ -42,9 +43,9 @@ function SearchPopover({ shareId }: Props) {
if (searchResults) {
setCachedQuery(query);
setCachedSearchResults(searchResults);
popover.show();
show();
}
}, [searchResults, query, popover.show]);
}, [searchResults, query, show]);
const performSearch = React.useCallback(
async ({ query, ...options }) => {
@@ -141,12 +142,12 @@ function SearchPopover({ shareId }: Props) {
);
const handleSearchItemClick = React.useCallback(() => {
popover.hide();
hide();
if (searchInputRef.current) {
searchInputRef.current.value = "";
focusRef.current = document.getElementById(bodyContentId);
}
}, [popover.hide]);
}, [searchInputRef, hide]);
useKeyDown("/", (ev) => {
if (

View File

@@ -27,197 +27,198 @@ type Props = {
children: React.ReactNode;
};
const Sidebar = React.forwardRef<HTMLDivElement, Props>(
({ children }: Props, ref: React.RefObject<HTMLDivElement>) => {
const [isCollapsing, setCollapsing] = React.useState(false);
const theme = useTheme();
const { t } = useTranslation();
const { ui, auth } = useStores();
const location = useLocation();
const previousLocation = usePrevious(location);
const { isMenuOpen } = useMenuContext();
const { user } = auth;
const width = ui.sidebarWidth;
const collapsed = ui.sidebarIsClosed && !isMenuOpen;
const maxWidth = theme.sidebarMaxWidth;
const minWidth = theme.sidebarMinWidth + 16; // padding
const Sidebar = React.forwardRef<HTMLDivElement, Props>(function _Sidebar(
{ children }: Props,
ref: React.RefObject<HTMLDivElement>
) {
const [isCollapsing, setCollapsing] = React.useState(false);
const theme = useTheme();
const { t } = useTranslation();
const { ui, auth } = useStores();
const location = useLocation();
const previousLocation = usePrevious(location);
const { isMenuOpen } = useMenuContext();
const { user } = auth;
const width = ui.sidebarWidth;
const collapsed = ui.sidebarIsClosed && !isMenuOpen;
const maxWidth = theme.sidebarMaxWidth;
const minWidth = theme.sidebarMinWidth + 16; // padding
const setWidth = ui.setSidebarWidth;
const [offset, setOffset] = React.useState(0);
const [isAnimating, setAnimating] = React.useState(false);
const [isResizing, setResizing] = React.useState(false);
const isSmallerThanMinimum = width < minWidth;
const setWidth = ui.setSidebarWidth;
const [offset, setOffset] = React.useState(0);
const [isAnimating, setAnimating] = React.useState(false);
const [isResizing, setResizing] = React.useState(false);
const isSmallerThanMinimum = width < minWidth;
const handleDrag = React.useCallback(
(event: MouseEvent) => {
// suppresses text selection
event.preventDefault();
// this is simple because the sidebar is always against the left edge
const width = Math.min(event.pageX - offset, maxWidth);
const isSmallerThanCollapsePoint = width < minWidth / 2;
const handleDrag = React.useCallback(
(event: MouseEvent) => {
// suppresses text selection
event.preventDefault();
// this is simple because the sidebar is always against the left edge
const width = Math.min(event.pageX - offset, maxWidth);
const isSmallerThanCollapsePoint = width < minWidth / 2;
if (isSmallerThanCollapsePoint) {
setWidth(theme.sidebarCollapsedWidth);
} else {
setWidth(width);
}
},
[theme, offset, minWidth, maxWidth, setWidth]
);
const handleStopDrag = React.useCallback(() => {
setResizing(false);
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
if (isSmallerThanMinimum) {
const isSmallerThanCollapsePoint = width < minWidth / 2;
if (isSmallerThanCollapsePoint) {
setAnimating(false);
setCollapsing(true);
ui.collapseSidebar();
} else {
setWidth(minWidth);
setAnimating(true);
}
if (isSmallerThanCollapsePoint) {
setWidth(theme.sidebarCollapsedWidth);
} else {
setWidth(width);
}
}, [ui, isSmallerThanMinimum, minWidth, width, setWidth]);
},
[theme, offset, minWidth, maxWidth, setWidth]
);
const handleMouseDown = React.useCallback(
(event) => {
setOffset(event.pageX - width);
setResizing(true);
const handleStopDrag = React.useCallback(() => {
setResizing(false);
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
if (isSmallerThanMinimum) {
const isSmallerThanCollapsePoint = width < minWidth / 2;
if (isSmallerThanCollapsePoint) {
setAnimating(false);
},
[width]
);
React.useEffect(() => {
if (isAnimating) {
setTimeout(() => setAnimating(false), ANIMATION_MS);
setCollapsing(true);
ui.collapseSidebar();
} else {
setWidth(minWidth);
setAnimating(true);
}
}, [isAnimating]);
} else {
setWidth(width);
}
}, [ui, isSmallerThanMinimum, minWidth, width, setWidth]);
React.useEffect(() => {
if (isCollapsing) {
setTimeout(() => {
setWidth(minWidth);
setCollapsing(false);
}, ANIMATION_MS);
}
}, [setWidth, minWidth, isCollapsing]);
const handleMouseDown = React.useCallback(
(event) => {
setOffset(event.pageX - width);
setResizing(true);
setAnimating(false);
},
[width]
);
React.useEffect(() => {
if (isResizing) {
document.addEventListener("mousemove", handleDrag);
document.addEventListener("mouseup", handleStopDrag);
}
React.useEffect(() => {
if (isAnimating) {
setTimeout(() => setAnimating(false), ANIMATION_MS);
}
}, [isAnimating]);
return () => {
document.removeEventListener("mousemove", handleDrag);
document.removeEventListener("mouseup", handleStopDrag);
};
}, [isResizing, handleDrag, handleStopDrag]);
React.useEffect(() => {
if (isCollapsing) {
setTimeout(() => {
setWidth(minWidth);
setCollapsing(false);
}, ANIMATION_MS);
}
}, [setWidth, minWidth, isCollapsing]);
const handleReset = React.useCallback(() => {
ui.setSidebarWidth(theme.sidebarWidth);
}, [ui, theme.sidebarWidth]);
React.useEffect(() => {
if (isResizing) {
document.addEventListener("mousemove", handleDrag);
document.addEventListener("mouseup", handleStopDrag);
}
React.useEffect(() => {
ui.setSidebarResizing(isResizing);
}, [ui, isResizing]);
return () => {
document.removeEventListener("mousemove", handleDrag);
document.removeEventListener("mouseup", handleStopDrag);
};
}, [isResizing, handleDrag, handleStopDrag]);
React.useEffect(() => {
if (location !== previousLocation) {
ui.hideMobileSidebar();
}
}, [ui, location, previousLocation]);
const handleReset = React.useCallback(() => {
ui.setSidebarWidth(theme.sidebarWidth);
}, [ui, theme.sidebarWidth]);
const style = React.useMemo(
() => ({
width: `${width}px`,
}),
[width]
);
React.useEffect(() => {
ui.setSidebarResizing(isResizing);
}, [ui, isResizing]);
const toggleStyle = React.useMemo(
() => ({
right: "auto",
marginLeft: `${collapsed ? theme.sidebarCollapsedWidth : width}px`,
}),
[width, theme.sidebarCollapsedWidth, collapsed]
);
React.useEffect(() => {
if (location !== previousLocation) {
ui.hideMobileSidebar();
}
}, [ui, location, previousLocation]);
return (
<>
<Container
ref={ref}
style={style}
$isAnimating={isAnimating}
$isSmallerThanMinimum={isSmallerThanMinimum}
$mobileSidebarVisible={ui.mobileSidebarVisible}
$collapsed={collapsed}
column
>
{ui.mobileSidebarVisible && (
<Portal>
<Backdrop onClick={ui.toggleMobileSidebar} />
</Portal>
)}
{children}
const style = React.useMemo(
() => ({
width: `${width}px`,
}),
[width]
);
{user && (
<AccountMenu>
{(props: HeaderButtonProps) => (
<HeaderButton
{...props}
showMoreMenu
title={user.name}
image={
<StyledAvatar
alt={user.name}
model={user}
size={24}
showBorder={false}
/>
}
>
<NotificationsPopover>
{(rest: HeaderButtonProps) => (
<HeaderButton {...rest} image={<NotificationIcon />} />
)}
</NotificationsPopover>
</HeaderButton>
)}
</AccountMenu>
)}
<ResizeBorder
onMouseDown={handleMouseDown}
onDoubleClick={ui.sidebarIsClosed ? undefined : handleReset}
/>
{ui.sidebarIsClosed && (
<Toggle
onClick={ui.toggleCollapsedSidebar}
direction={"right"}
aria-label={t("Expand")}
/>
)}
</Container>
<Toggle
style={toggleStyle}
onClick={ui.toggleCollapsedSidebar}
direction={ui.sidebarIsClosed ? "right" : "left"}
aria-label={ui.sidebarIsClosed ? t("Expand") : t("Collapse")}
const toggleStyle = React.useMemo(
() => ({
right: "auto",
marginLeft: `${collapsed ? theme.sidebarCollapsedWidth : width}px`,
}),
[width, theme.sidebarCollapsedWidth, collapsed]
);
return (
<>
<Container
ref={ref}
style={style}
$isAnimating={isAnimating}
$isSmallerThanMinimum={isSmallerThanMinimum}
$mobileSidebarVisible={ui.mobileSidebarVisible}
$collapsed={collapsed}
column
>
{ui.mobileSidebarVisible && (
<Portal>
<Backdrop onClick={ui.toggleMobileSidebar} />
</Portal>
)}
{children}
{user && (
<AccountMenu>
{(props: HeaderButtonProps) => (
<HeaderButton
{...props}
showMoreMenu
title={user.name}
image={
<StyledAvatar
alt={user.name}
model={user}
size={24}
showBorder={false}
/>
}
>
<NotificationsPopover>
{(rest: HeaderButtonProps) => (
<HeaderButton {...rest} image={<NotificationIcon />} />
)}
</NotificationsPopover>
</HeaderButton>
)}
</AccountMenu>
)}
<ResizeBorder
onMouseDown={handleMouseDown}
onDoubleClick={ui.sidebarIsClosed ? undefined : handleReset}
/>
</>
);
}
);
{ui.sidebarIsClosed && (
<Toggle
onClick={ui.toggleCollapsedSidebar}
direction={"right"}
aria-label={t("Expand")}
/>
)}
</Container>
<Toggle
style={toggleStyle}
onClick={ui.toggleCollapsedSidebar}
direction={ui.sidebarIsClosed ? "right" : "left"}
aria-label={ui.sidebarIsClosed ? t("Expand") : t("Collapse")}
/>
</>
);
});
const StyledAvatar = styled(Avatar)`
margin-left: 4px;

View File

@@ -37,7 +37,7 @@ const CollectionLink: React.FC<Props> = ({
expanded,
onDisclosureClick,
isDraggingAnyCollection,
}) => {
}: Props) => {
const itemRef = React.useRef<
NavigationNode & { depth: number; active: boolean; collectionId: string }
>();

View File

@@ -3,9 +3,10 @@ import styled from "styled-components";
type Props = {
expanded: boolean;
children?: React.ReactNode;
};
const Folder: React.FC<Props> = ({ expanded, children }) => {
const Folder: React.FC<Props> = ({ expanded, children }: Props) => {
const [openedOnce, setOpenedOnce] = React.useState(expanded);
// allows us to avoid rendering all children when the folder hasn't been opened

View File

@@ -9,12 +9,13 @@ type Props = {
/** Unique header id if passed the header will become toggleable */
id?: string;
title: React.ReactNode;
children?: React.ReactNode;
};
/**
* Toggleable sidebar header
*/
export const Header: React.FC<Props> = ({ id, title, children }) => {
export const Header: React.FC<Props> = ({ id, title, children }: Props) => {
const [firstRender, setFirstRender] = React.useState(true);
const [expanded, setExpanded] = usePersistedState<boolean>(
`sidebar-header-${id}`,

View File

@@ -17,7 +17,7 @@ export type HeaderButtonProps = React.ComponentProps<typeof Button> & {
};
const HeaderButton = React.forwardRef<HTMLButtonElement, HeaderButtonProps>(
(
function _HeaderButton(
{
showDisclosure,
showMoreMenu,
@@ -28,25 +28,27 @@ const HeaderButton = React.forwardRef<HTMLButtonElement, HeaderButtonProps>(
...rest
}: HeaderButtonProps,
ref
) => (
<Flex justify="space-between" align="center" shrink={false}>
<Button
{...rest}
minHeight={minHeight}
as="button"
ref={ref}
role="button"
>
<Title gap={8} align="center">
{image}
{title}
</Title>
{showDisclosure && <ExpandedIcon />}
{showMoreMenu && <MoreIcon />}
</Button>
{children}
</Flex>
)
) {
return (
<Flex justify="space-between" align="center" shrink={false}>
<Button
{...rest}
minHeight={minHeight}
as="button"
ref={ref}
role="button"
>
<Title gap={8} align="center">
{image}
{title}
</Title>
{showDisclosure && <ExpandedIcon />}
{showMoreMenu && <MoreIcon />}
</Button>
{children}
</Flex>
);
}
);
const Title = styled(Flex)`

View File

@@ -12,41 +12,42 @@ type Props = {
onClick?: React.MouseEventHandler<HTMLButtonElement>;
};
const Toggle = React.forwardRef<HTMLButtonElement, Props>(
({ direction = "left", onClick, style }: Props, ref) => {
const { t } = useTranslation();
const [hovering, setHovering] = React.useState(false);
const positionRef = React.useRef<HTMLDivElement>(null);
const Toggle = React.forwardRef<HTMLButtonElement, Props>(function Toggle_(
{ direction = "left", onClick, style }: Props,
ref
) {
const { t } = useTranslation();
const [hovering, setHovering] = React.useState(false);
const positionRef = React.useRef<HTMLDivElement>(null);
// Not using CSS hover here so that we can disable pointer events on this
// div and allow click through to the editor elements behind.
useEventListener("mousemove", (event: MouseEvent) => {
if (!positionRef.current) {
return;
}
// Not using CSS hover here so that we can disable pointer events on this
// div and allow click through to the editor elements behind.
useEventListener("mousemove", (event: MouseEvent) => {
if (!positionRef.current) {
return;
}
const bound = positionRef.current.getBoundingClientRect();
const withinBounds =
event.clientX >= bound.left && event.clientX <= bound.right;
if (withinBounds !== hovering) {
setHovering(withinBounds);
}
});
const bound = positionRef.current.getBoundingClientRect();
const withinBounds =
event.clientX >= bound.left && event.clientX <= bound.right;
if (withinBounds !== hovering) {
setHovering(withinBounds);
}
});
return (
<Positioner style={style} ref={positionRef} $hovering={hovering}>
<ToggleButton
ref={ref}
$direction={direction}
onClick={onClick}
aria-label={t("Toggle sidebar")}
>
<Arrow />
</ToggleButton>
</Positioner>
);
}
);
return (
<Positioner style={style} ref={positionRef} $hovering={hovering}>
<ToggleButton
ref={ref}
$direction={direction}
onClick={onClick}
aria-label={t("Toggle sidebar")}
>
<Arrow />
</ToggleButton>
</Positioner>
);
});
export const ToggleButton = styled.button<{ $direction?: "left" | "right" }>`
opacity: 0;

View File

@@ -5,9 +5,10 @@ import Flex from "./Flex";
type Props = {
size?: number;
color?: string;
children?: React.ReactNode;
};
const Squircle: React.FC<Props> = ({ color, size = 28, children }) => (
const Squircle: React.FC<Props> = ({ color, size = 28, children }: Props) => (
<Wrapper
style={{ width: size, height: size }}
align="center"

View File

@@ -3,6 +3,7 @@ import styled from "styled-components";
import { s } from "@shared/styles";
type Props = {
children?: React.ReactNode;
sticky?: boolean;
};
@@ -34,7 +35,7 @@ const Background = styled.div<{ sticky?: boolean }>`
z-index: 1;
`;
const Subheading: React.FC<Props> = ({ children, sticky, ...rest }) => (
const Subheading: React.FC<Props> = ({ children, sticky, ...rest }: Props) => (
<Background sticky={sticky}>
<H3 {...rest}>
<Underline>{children}</Underline>

View File

@@ -8,6 +8,7 @@ import { hover } from "~/styles";
type Props = Omit<React.ComponentProps<typeof NavLink>, "children"> & {
to: string;
exact?: boolean;
children?: React.ReactNode;
};
const TabLink = styled(NavLink)`
@@ -44,7 +45,7 @@ const transition = {
damping: 30,
};
const Tab: React.FC<Props> = ({ children, ...rest }) => {
const Tab: React.FC<Props> = ({ children, ...rest }: Props) => {
const theme = useTheme();
const activeStyle = {
color: theme.textSecondary,

View File

@@ -121,9 +121,12 @@ function Table({
<InnerTable {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
<tr {...headerGroup.getHeaderGroupProps()} key={headerGroup.id}>
{headerGroup.headers.map((column) => (
<Head {...column.getHeaderProps(column.getSortByToggleProps())}>
<Head
{...column.getHeaderProps(column.getSortByToggleProps())}
key={column.id}
>
<SortWrapper
align="center"
$sortable={!column.disableSortBy}
@@ -146,7 +149,7 @@ function Table({
{rows.map((row) => {
prepareRow(row);
return (
<Row {...row.getRowProps()}>
<Row {...row.getRowProps()} key={row.id}>
{row.cells.map((cell) => (
<Cell
{...cell.getCellProps([
@@ -155,6 +158,7 @@ function Table({
className: cell.column.className,
},
])}
key={cell.column.id}
>
{cell.render("Cell")}
</Cell>

View File

@@ -57,7 +57,11 @@ export const Separator = styled.span`
margin-top: 6px;
`;
const Tabs: React.FC = ({ children }) => {
type Props = {
children?: React.ReactNode;
};
const Tabs: React.FC = ({ children }: Props) => {
const ref = React.useRef<any>();
const [shadowVisible, setShadow] = React.useState(false);
const { width } = useWindowSize();

View File

@@ -7,7 +7,11 @@ import useBuildTheme from "~/hooks/useBuildTheme";
import useStores from "~/hooks/useStores";
import { TooltipStyles } from "./Tooltip";
const Theme: React.FC = ({ children }) => {
type Props = {
children?: React.ReactNode;
};
const Theme: React.FC = ({ children }: Props) => {
const { auth, ui } = useStores();
const theme = useBuildTheme(
auth.team?.getPreference(TeamPreference.CustomTheme) ||

View File

@@ -3,10 +3,11 @@ import styled from "styled-components";
import Tooltip from "~/components/Tooltip";
type Props = {
children?: React.ReactNode;
tooltip?: string;
};
const WrappedTooltip: React.FC<Props> = ({ children, tooltip }) => (
const WrappedTooltip: React.FC<Props> = ({ children, tooltip }: Props) => (
<Tooltip offset={[0, 16]} delay={150} tooltip={tooltip} placement="top">
<TooltipContent>{children}</TooltipContent>
</Tooltip>

View File

@@ -863,11 +863,13 @@ const EditorContainer = styled(Styles)<{ focusedCommentId?: string }>`
`;
const LazyLoadedEditor = React.forwardRef<Editor, Props>(
(props: Props, ref) => (
<WithTheme>
{(theme) => <Editor theme={theme} {...props} ref={ref} />}
</WithTheme>
)
function _LazyLoadedEditor(props: Props, ref) {
return (
<WithTheme>
{(theme) => <Editor theme={theme} {...props} ref={ref} />}
</WithTheme>
);
}
);
const observe = (

View File

@@ -8,7 +8,11 @@ type MenuContextType = {
const MenuContext = React.createContext<MenuContextType | null>(null);
export const MenuProvider: React.FC = ({ children }) => {
type Props = {
children?: React.ReactNode;
};
export const MenuProvider: React.FC = ({ children }: Props) => {
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
const memoized = React.useMemo(
() => ({

View File

@@ -1,12 +1,13 @@
import * as React from "react";
const isSupported = "IntersectionObserver" in window;
/**
* Hook to return if a given ref is visible on screen.
*
* @returns boolean if the node is visible
*/
export default function useOnScreen(ref: React.RefObject<HTMLElement>) {
const isSupported = "IntersectionObserver" in window;
const [isIntersecting, setIntersecting] = React.useState(!isSupported);
React.useEffect(() => {
@@ -28,7 +29,7 @@ export default function useOnScreen(ref: React.RefObject<HTMLElement>) {
observer?.unobserve(element);
}
};
}, []);
}, [ref]);
return isIntersecting;
}

View File

@@ -20,7 +20,11 @@ import usePrevious from "~/hooks/usePrevious";
import useStores from "~/hooks/useStores";
import separator from "~/menus/separator";
const AccountMenu: React.FC = ({ children }) => {
type Props = {
children?: React.ReactNode;
};
const AccountMenu: React.FC = ({ children }: Props) => {
const menu = useMenuState({
placement: "bottom-end",
modal: true,

View File

@@ -11,7 +11,11 @@ import usePrevious from "~/hooks/usePrevious";
import useStores from "~/hooks/useStores";
import separator from "~/menus/separator";
const OrganizationMenu: React.FC = ({ children }) => {
type Props = {
children?: React.ReactNode;
};
const OrganizationMenu: React.FC = ({ children }: Props) => {
const menu = useMenuState({
unstable_offset: [4, -4],
placement: "bottom-start",

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import { useTranslation } from "react-i18next";
import Button from "~/components/Button";
import Flex from "~/components/Flex";
import Input from "~/components/Input";
@@ -51,11 +51,9 @@ function APITokenNew({ onSubmit }: Props) {
return (
<form onSubmit={handleSubmit}>
<Text type="secondary">
<Trans>
Name your token something that will help you to remember it's use in
the future, for example "local development", "production", or
"continuous integration".
</Trans>
{t(
`Name your token something that will help you to remember it's use in the future, for example "local development", "production", or "continuous integration".`
)}
</Text>
<Flex>
<Input

View File

@@ -12,6 +12,7 @@ type Props = {
disabled: boolean;
accept: string;
collectionId: string;
children?: React.ReactNode;
};
const DropToImport: React.FC<Props> = ({
@@ -19,7 +20,7 @@ const DropToImport: React.FC<Props> = ({
disabled,
accept,
collectionId,
}) => {
}: Props) => {
const { handleFiles, isImporting } = useImportDocument(collectionId);
const { showToast } = useToasts();
const { t } = useTranslation();

View File

@@ -5,6 +5,7 @@ import { MenuInternalLink } from "~/types";
import { sharedDocumentPath } from "~/utils/routeHelpers";
type Props = {
children?: React.ReactNode;
documentId: string;
shareId: string;
sharedTree: NavigationNode | undefined;
@@ -44,7 +45,7 @@ const PublicBreadcrumb: React.FC<Props> = ({
shareId,
sharedTree,
children,
}) => {
}: Props) => {
const items: MenuInternalLink[] = React.useMemo(
() =>
pathToDocument(sharedTree, documentId)
@@ -57,7 +58,7 @@ const PublicBreadcrumb: React.FC<Props> = ({
[sharedTree, shareId, documentId]
);
return <Breadcrumb items={items} children={children} />;
return <Breadcrumb items={items}>{children}</Breadcrumb>;
};
export default PublicBreadcrumb;

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/no-unescaped-entities */
import { WarningIcon } from "outline-icons";
import * as React from "react";
import { Trans } from "react-i18next";

View File

@@ -50,7 +50,11 @@ function ApiKeys() {
For more details see the <em>developer documentation</em>."
components={{
em: (
<a href="https://www.getoutline.com/developers" target="_blank" />
<a
href="https://www.getoutline.com/developers"
target="_blank"
rel="noreferrer"
/>
),
}}
/>

View File

@@ -10,10 +10,11 @@ import Button from "~/components/Button";
import Text from "~/components/Text";
type Props = {
children?: React.ReactNode;
title: React.ReactNode;
};
const HelpDisclosure: React.FC<Props> = ({ title, children }) => {
const HelpDisclosure: React.FC<Props> = ({ title, children }: Props) => {
const disclosure = useDisclosureState({ animated: true });
const theme = useTheme();

View File

@@ -1,5 +1,5 @@
import * as React from "react";
import { Trans } from "react-i18next";
import { Trans, useTranslation } from "react-i18next";
import { FileOperationFormat } from "@shared/types";
import Flex from "~/components/Flex";
import Text from "~/components/Text";
@@ -8,6 +8,7 @@ import DropToImport from "./DropToImport";
import HelpDisclosure from "./HelpDisclosure";
function ImportNotionDialog() {
const { t } = useTranslation();
const { dialogs } = useStores();
return (
@@ -17,10 +18,11 @@ function ImportNotionDialog() {
onSubmit={dialogs.closeAllModals}
format={FileOperationFormat.Notion}
>
<Trans>
Drag and drop the zip file from Notion's HTML export option, or
click to upload
</Trans>
<>
{t(
`Drag and drop the zip file from Notion's HTML export option, or click to upload`
)}
</>
</DropToImport>
</Text>
<HelpDisclosure title={<Trans>Where do I find the file?</Trans>}>

View File

@@ -6,6 +6,7 @@ import Flex from "~/components/Flex";
import Text from "~/components/Text";
type Props = {
children?: React.ReactNode;
label: React.ReactNode;
description?: React.ReactNode;
name: string;
@@ -63,7 +64,7 @@ const SettingRow: React.FC<Props> = ({
label,
border,
children,
}) => {
}: Props) => {
if (visible === false) {
return null;
}