Add JSDoc comments to all ui components
This commit is contained in:
parent
999d5bd9f4
commit
b7d4411627
26 changed files with 168 additions and 9 deletions
|
@ -6,11 +6,15 @@ import StillImage from 'soapbox/components/still_image';
|
|||
const AVATAR_SIZE = 42;
|
||||
|
||||
interface IAvatar {
|
||||
/** URL to the avatar image. */
|
||||
src: string,
|
||||
/** Width and height of the avatar in pixels. */
|
||||
size?: number,
|
||||
/** Extra class names for the div surrounding the avatar image. */
|
||||
className?: string,
|
||||
}
|
||||
|
||||
/** Round profile avatar for accounts. */
|
||||
const Avatar = (props: IAvatar) => {
|
||||
const { src, size = AVATAR_SIZE, className } = props;
|
||||
|
||||
|
|
|
@ -8,20 +8,33 @@ import { useButtonStyles } from './useButtonStyles';
|
|||
import type { ButtonSizes, ButtonThemes } from './useButtonStyles';
|
||||
|
||||
interface IButton {
|
||||
/** Whether this button expands the width of its container. */
|
||||
block?: boolean,
|
||||
/** Elements inside the <button> */
|
||||
children?: React.ReactNode,
|
||||
/** @deprecated unused */
|
||||
classNames?: string,
|
||||
/** Prevent the button from being clicked. */
|
||||
disabled?: boolean,
|
||||
/** URL to an SVG icon to render inside the button. */
|
||||
icon?: string,
|
||||
/** Action when the button is clicked. */
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void,
|
||||
/** A predefined button size. */
|
||||
size?: ButtonSizes,
|
||||
/** @deprecated unused */
|
||||
style?: React.CSSProperties,
|
||||
/** Text inside the button. Takes precedence over `children`. */
|
||||
text?: React.ReactNode,
|
||||
/** Makes the button into a navlink, if provided. */
|
||||
to?: string,
|
||||
/** Styles the button visually with a predefined theme. */
|
||||
theme?: ButtonThemes,
|
||||
/** Whether this button should submit a form by default. */
|
||||
type?: 'button' | 'submit',
|
||||
}
|
||||
|
||||
/** Customizable button element with various themes. */
|
||||
const Button = React.forwardRef<HTMLButtonElement, IButton>((props, ref): JSX.Element => {
|
||||
const {
|
||||
block = false,
|
||||
|
|
|
@ -17,12 +17,17 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
interface ICard {
|
||||
/** The type of card. */
|
||||
variant?: 'rounded',
|
||||
/** Card size preset. */
|
||||
size?: 'md' | 'lg' | 'xl',
|
||||
/** Extra classnames for the <div> element. */
|
||||
className?: string,
|
||||
/** Elements inside the card. */
|
||||
children: React.ReactNode,
|
||||
}
|
||||
|
||||
/** An opaque backdrop to hold a collection of related elements. */
|
||||
const Card = React.forwardRef<HTMLDivElement, ICard>(({ children, variant, size = 'md', className, ...filteredProps }, ref): JSX.Element => (
|
||||
<div
|
||||
ref={ref}
|
||||
|
@ -42,6 +47,7 @@ interface ICardHeader {
|
|||
onBackClick?: (event: React.MouseEvent) => void
|
||||
}
|
||||
|
||||
/** Typically holds a CardTitle. */
|
||||
const CardHeader: React.FC<ICardHeader> = ({ children, backHref, onBackClick }): JSX.Element => {
|
||||
const intl = useIntl();
|
||||
|
||||
|
@ -74,10 +80,12 @@ interface ICardTitle {
|
|||
title: string | React.ReactNode
|
||||
}
|
||||
|
||||
/** A card's title. */
|
||||
const CardTitle = ({ title }: ICardTitle): JSX.Element => (
|
||||
<Text size='xl' weight='bold' tag='h1' data-testid='card-title'>{title}</Text>
|
||||
);
|
||||
|
||||
/** A card's body. */
|
||||
const CardBody: React.FC = ({ children }): JSX.Element => (
|
||||
<div data-testid='card-body'>{children}</div>
|
||||
);
|
||||
|
|
|
@ -7,13 +7,19 @@ import Helmet from 'soapbox/components/helmet';
|
|||
import { Card, CardBody, CardHeader, CardTitle } from '../card/card';
|
||||
|
||||
interface IColumn {
|
||||
/** Route the back button goes to. */
|
||||
backHref?: string,
|
||||
/** Column title text. */
|
||||
label?: string,
|
||||
/** Whether this column should have a transparent background. */
|
||||
transparent?: boolean,
|
||||
/** Whether this column should have a title and back button. */
|
||||
withHeader?: boolean,
|
||||
/** Extra class name for top <div> element. */
|
||||
className?: string,
|
||||
}
|
||||
|
||||
/** A backdrop for the main section of the UI. */
|
||||
const Column: React.FC<IColumn> = React.forwardRef((props, ref: React.ForwardedRef<HTMLDivElement>): JSX.Element => {
|
||||
const { backHref, children, label, transparent = false, withHeader = true, className } = props;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||
|
||||
interface ICounter {
|
||||
/** Number this counter should display. */
|
||||
count: number,
|
||||
}
|
||||
|
||||
|
|
|
@ -4,12 +4,17 @@ import React from 'react';
|
|||
import { Emoji, HStack } from 'soapbox/components/ui';
|
||||
|
||||
interface IEmojiButton {
|
||||
/** Unicode emoji character. */
|
||||
emoji: string,
|
||||
/** Event handler when the emoji is clicked. */
|
||||
onClick: React.EventHandler<React.MouseEvent>,
|
||||
/** Extra class name on the <button> element. */
|
||||
className?: string,
|
||||
/** Tab order of the button. */
|
||||
tabIndex?: number,
|
||||
}
|
||||
|
||||
/** Clickable emoji button that scales when hovered. */
|
||||
const EmojiButton: React.FC<IEmojiButton> = ({ emoji, className, onClick, tabIndex }): JSX.Element => {
|
||||
return (
|
||||
<button className={classNames(className)} onClick={onClick} tabIndex={tabIndex}>
|
||||
|
@ -19,12 +24,17 @@ const EmojiButton: React.FC<IEmojiButton> = ({ emoji, className, onClick, tabInd
|
|||
};
|
||||
|
||||
interface IEmojiSelector {
|
||||
/** List of Unicode emoji characters. */
|
||||
emojis: Iterable<string>,
|
||||
/** Event handler when an emoji is clicked. */
|
||||
onReact: (emoji: string) => void,
|
||||
/** Whether the selector should be visible. */
|
||||
visible?: boolean,
|
||||
/** Whether the selector should be focused. */
|
||||
focused?: boolean,
|
||||
}
|
||||
|
||||
/** Panel with a row of emoji buttons. */
|
||||
const EmojiSelector: React.FC<IEmojiSelector> = ({ emojis, onReact, visible = false, focused = false }): JSX.Element => {
|
||||
|
||||
const handleReact = (emoji: string): React.EventHandler<React.MouseEvent> => {
|
||||
|
|
|
@ -4,9 +4,11 @@ import { removeVS16s, toCodePoints } from 'soapbox/utils/emoji';
|
|||
import { joinPublicPath } from 'soapbox/utils/static';
|
||||
|
||||
interface IEmoji extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||
/** Unicode emoji character. */
|
||||
emoji: string,
|
||||
}
|
||||
|
||||
/** A single emoji image. */
|
||||
const Emoji: React.FC<IEmoji> = (props): JSX.Element | null => {
|
||||
const { emoji, alt, ...rest } = props;
|
||||
const codepoints = toCodePoints(removeVS16s(emoji));
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
/** Container element to house form actions. */
|
||||
const FormActions: React.FC = ({ children }) => (
|
||||
<div className='flex justify-end space-x-2'>
|
||||
{children}
|
||||
|
|
|
@ -2,11 +2,15 @@ import React, { useMemo } from 'react';
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
interface IFormGroup {
|
||||
/** Input label message. */
|
||||
hintText?: React.ReactNode,
|
||||
/** Input hint message. */
|
||||
labelText: React.ReactNode,
|
||||
/** Input errors. */
|
||||
errors?: string[]
|
||||
}
|
||||
|
||||
/** Input element with label and hint. */
|
||||
const FormGroup: React.FC<IFormGroup> = (props) => {
|
||||
const { children, errors = [], labelText, hintText } = props;
|
||||
const formFieldId: string = useMemo(() => `field-${uuidv4()}`, []);
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import * as React from 'react';
|
||||
|
||||
interface IForm {
|
||||
/** Form submission event handler. */
|
||||
onSubmit?: (event: React.FormEvent) => void,
|
||||
/** Class name override for the <form> element. */
|
||||
className?: string,
|
||||
}
|
||||
|
||||
/** Form element with custom styles. */
|
||||
const Form: React.FC<IForm> = ({ onSubmit, children, ...filteredProps }) => {
|
||||
const handleSubmit = React.useCallback((event) => {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -24,14 +24,21 @@ const spaces = {
|
|||
};
|
||||
|
||||
interface IHStack {
|
||||
/** Vertical alignment of children. */
|
||||
alignItems?: 'top' | 'bottom' | 'center' | 'start',
|
||||
/** Extra class names on the <div> element. */
|
||||
className?: string,
|
||||
/** Horizontal alignment of children. */
|
||||
justifyContent?: 'between' | 'center',
|
||||
/** Size of the gap between elements. */
|
||||
space?: 0.5 | 1 | 1.5 | 2 | 3 | 4 | 6,
|
||||
/** Whether to let the flexbox grow. */
|
||||
grow?: boolean,
|
||||
/** Extra CSS styles for the <div> */
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
/** Horizontal row of child elements. */
|
||||
const HStack: React.FC<IHStack> = (props) => {
|
||||
const { space, alignItems, grow, justifyContent, className, ...filteredProps } = props;
|
||||
|
||||
|
|
|
@ -5,12 +5,17 @@ import SvgIcon from '../icon/svg-icon';
|
|||
import Text from '../text/text';
|
||||
|
||||
interface IIconButton extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
/** Class name for the <svg> icon. */
|
||||
iconClassName?: string,
|
||||
/** URL to the svg icon. */
|
||||
src: string,
|
||||
/** Text to display next ot the button. */
|
||||
text?: string,
|
||||
/** Don't render a background behind the icon. */
|
||||
transparent?: boolean
|
||||
}
|
||||
|
||||
/** A clickable icon. */
|
||||
const IconButton = React.forwardRef((props: IIconButton, ref: React.ForwardedRef<HTMLButtonElement>): JSX.Element => {
|
||||
const { src, className, iconClassName, text, transparent = false, ...filteredProps } = props;
|
||||
|
||||
|
|
|
@ -4,16 +4,21 @@ import Counter from '../counter/counter';
|
|||
|
||||
import SvgIcon from './svg-icon';
|
||||
|
||||
|
||||
interface IIcon extends Pick<React.SVGAttributes<SVGAElement>, 'strokeWidth'> {
|
||||
/** Class name for the <svg> element. */
|
||||
className?: string,
|
||||
/** Number to display a counter over the icon. */
|
||||
count?: number,
|
||||
/** Tooltip text for the icon. */
|
||||
alt?: string,
|
||||
/** URL to the svg icon. */
|
||||
src: string,
|
||||
/** Width and height of the icon in pixels. */
|
||||
size?: number,
|
||||
}
|
||||
|
||||
const Icon = ({ src, alt, count, size, ...filteredProps }: IIcon): JSX.Element => (
|
||||
/** Renders and SVG icon with optional counter. */
|
||||
const Icon: React.FC<IIcon> = ({ src, alt, count, size, ...filteredProps }): JSX.Element => (
|
||||
<div className='relative' data-testid='icon'>
|
||||
{count ? (
|
||||
<span className='absolute -top-2 -right-3'>
|
||||
|
|
|
@ -2,9 +2,13 @@ import React from 'react';
|
|||
import InlineSVG from 'react-inlinesvg'; // eslint-disable-line no-restricted-imports
|
||||
|
||||
interface ISvgIcon {
|
||||
/** Class name for the <svg> */
|
||||
className?: string,
|
||||
/** Tooltip text for the icon. */
|
||||
alt?: string,
|
||||
/** URL to the svg file. */
|
||||
src: string,
|
||||
/** Width and height of the icon in pixels. */
|
||||
size?: number,
|
||||
}
|
||||
|
||||
|
|
|
@ -12,17 +12,27 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
interface IInput extends Pick<React.InputHTMLAttributes<HTMLInputElement>, 'maxLength' | 'onChange' | 'type' | 'autoComplete' | 'autoCorrect' | 'autoCapitalize' | 'required' | 'disabled'> {
|
||||
/** Put the cursor into the input on mount. */
|
||||
autoFocus?: boolean,
|
||||
/** The initial text in the input. */
|
||||
defaultValue?: string,
|
||||
/** Extra class names for the <input> element. */
|
||||
className?: string,
|
||||
/** URL to the svg icon. */
|
||||
icon?: string,
|
||||
/** Internal input name. */
|
||||
name?: string,
|
||||
/** Text to display before a value is entered. */
|
||||
placeholder?: string,
|
||||
/** Text in the input. */
|
||||
value?: string,
|
||||
/** Change event handler for the input. */
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void,
|
||||
/** HTML input type. */
|
||||
type: 'text' | 'email' | 'tel' | 'password',
|
||||
}
|
||||
|
||||
/** Form input element. */
|
||||
const Input = React.forwardRef<HTMLInputElement, IInput>(
|
||||
(props, ref) => {
|
||||
const intl = useIntl();
|
||||
|
|
|
@ -2,13 +2,14 @@ import classNames from 'classnames';
|
|||
import React from 'react';
|
||||
import StickyBox from 'react-sticky-box';
|
||||
|
||||
interface LayoutType extends React.FC {
|
||||
interface LayoutComponent extends React.FC {
|
||||
Sidebar: React.FC,
|
||||
Main: React.FC<React.HTMLAttributes<HTMLDivElement>>,
|
||||
Aside: React.FC,
|
||||
}
|
||||
|
||||
const Layout: LayoutType = ({ children }) => (
|
||||
/** Layout container, to hold Sidebar, Main, and Aside. */
|
||||
const Layout: LayoutComponent = ({ children }) => (
|
||||
<div className='sm:pt-4 relative'>
|
||||
<div className='max-w-3xl mx-auto sm:px-6 md:max-w-7xl md:px-8 md:grid md:grid-cols-12 md:gap-8'>
|
||||
{children}
|
||||
|
@ -16,6 +17,7 @@ const Layout: LayoutType = ({ children }) => (
|
|||
</div>
|
||||
);
|
||||
|
||||
/** Left sidebar container in the UI. */
|
||||
const Sidebar: React.FC = ({ children }) => (
|
||||
<div className='hidden lg:block lg:col-span-3'>
|
||||
<StickyBox offsetTop={80} className='pb-4'>
|
||||
|
@ -24,6 +26,7 @@ const Sidebar: React.FC = ({ children }) => (
|
|||
</div>
|
||||
);
|
||||
|
||||
/** Center column container in the UI. */
|
||||
const Main: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, className }) => (
|
||||
<main
|
||||
className={classNames({
|
||||
|
@ -34,6 +37,7 @@ const Main: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, classN
|
|||
</main>
|
||||
);
|
||||
|
||||
/** Right sidebar container in the UI. */
|
||||
const Aside: React.FC = ({ children }) => (
|
||||
<aside className='hidden xl:block xl:col-span-3'>
|
||||
<StickyBox offsetTop={80} className='space-y-6 pb-12' >
|
||||
|
|
|
@ -13,10 +13,12 @@ import React from 'react';
|
|||
import './menu.css';
|
||||
|
||||
interface IMenuList extends Omit<MenuPopoverProps, 'position'> {
|
||||
/** Position of the dropdown menu. */
|
||||
position?: 'left' | 'right'
|
||||
}
|
||||
|
||||
const MenuList = (props: IMenuList) => (
|
||||
/** Renders children as a dropdown menu. */
|
||||
const MenuList: React.FC<IMenuList> = (props) => (
|
||||
<MenuPopover position={props.position === 'left' ? positionDefault : positionRight}>
|
||||
<MenuItems
|
||||
onKeyDown={(event) => event.nativeEvent.stopImmediatePropagation()}
|
||||
|
@ -26,6 +28,7 @@ const MenuList = (props: IMenuList) => (
|
|||
</MenuPopover>
|
||||
);
|
||||
|
||||
/** Divides menu items. */
|
||||
const MenuDivider = () => <hr />;
|
||||
|
||||
export { Menu, MenuButton, MenuDivider, MenuItems, MenuItem, MenuList, MenuLink };
|
||||
|
|
|
@ -11,18 +11,29 @@ const messages = defineMessages({
|
|||
});
|
||||
|
||||
interface IModal {
|
||||
/** Callback when the modal is cancelled. */
|
||||
cancelAction?: () => void,
|
||||
/** Cancel button text. */
|
||||
cancelText?: string,
|
||||
/** Callback when the modal is confirmed. */
|
||||
confirmationAction?: () => void,
|
||||
/** Whether the confirmation button is disabled. */
|
||||
confirmationDisabled?: boolean,
|
||||
/** Confirmation button text. */
|
||||
confirmationText?: string,
|
||||
/** Confirmation button theme. */
|
||||
confirmationTheme?: 'danger',
|
||||
/** Callback when the modal is closed. */
|
||||
onClose?: () => void,
|
||||
/** Callback when the secondary action is chosen. */
|
||||
secondaryAction?: () => void,
|
||||
/** Secondary button text. */
|
||||
secondaryText?: string,
|
||||
/** Title text for the modal. */
|
||||
title: string | React.ReactNode,
|
||||
}
|
||||
|
||||
/** Displays a modal dialog box. */
|
||||
const Modal: React.FC<IModal> = ({
|
||||
cancelAction,
|
||||
cancelText,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as React from 'react';
|
||||
|
||||
/** Multiple-select dropdown. */
|
||||
const Select = React.forwardRef<HTMLSelectElement>((props, ref) => {
|
||||
const { children, ...filteredProps } = props;
|
||||
|
||||
|
|
|
@ -7,10 +7,13 @@ import Text from '../text/text';
|
|||
import './spinner.css';
|
||||
|
||||
interface ILoadingIndicator {
|
||||
/** Width and height of the spinner in pixels. */
|
||||
size?: number,
|
||||
/** Whether to display "Loading..." beneath the spinner. */
|
||||
withText?: boolean
|
||||
}
|
||||
|
||||
/** Spinning loading placeholder. */
|
||||
const LoadingIndicator = ({ size = 30, withText = true }: ILoadingIndicator) => (
|
||||
<Stack space={2} justifyContent='center' alignItems='center'>
|
||||
<div className='spinner' style={{ width: size, height: size }}>
|
||||
|
|
|
@ -22,13 +22,18 @@ const alignItemsOptions = {
|
|||
center: 'items-center',
|
||||
};
|
||||
|
||||
interface IStack extends React.HTMLAttributes<HTMLDivElement > {
|
||||
interface IStack extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/** Size of the gap between elements. */
|
||||
space?: SIZES,
|
||||
/** Horizontal alignment of children. */
|
||||
alignItems?: 'center',
|
||||
/** Vertical alignment of children. */
|
||||
justifyContent?: 'center',
|
||||
/** Extra class names on the <div> element. */
|
||||
className?: string,
|
||||
}
|
||||
|
||||
/** Vertical stack of child elements. */
|
||||
const Stack: React.FC<IStack> = (props) => {
|
||||
const { space, alignItems, justifyContent, className, ...filteredProps } = props;
|
||||
|
||||
|
|
|
@ -17,10 +17,13 @@ const HORIZONTAL_PADDING = 8;
|
|||
const AnimatedContext = React.createContext(null);
|
||||
|
||||
interface IAnimatedInterface {
|
||||
/** Callback when a tab is chosen. */
|
||||
onChange(index: number): void,
|
||||
/** Default tab index. */
|
||||
defaultIndex: number
|
||||
}
|
||||
|
||||
/** Tabs with a sliding active state. */
|
||||
const AnimatedTabs: React.FC<IAnimatedInterface> = ({ children, ...rest }) => {
|
||||
const [activeRect, setActiveRect] = React.useState(null);
|
||||
const ref = React.useRef();
|
||||
|
@ -58,13 +61,19 @@ const AnimatedTabs: React.FC<IAnimatedInterface> = ({ children, ...rest }) => {
|
|||
};
|
||||
|
||||
interface IAnimatedTab {
|
||||
/** ARIA role. */
|
||||
role: 'button',
|
||||
/** Element to represent the tab. */
|
||||
as: 'a' | 'button',
|
||||
/** Route to visit when the tab is chosen. */
|
||||
href?: string,
|
||||
/** Tab title text. */
|
||||
title: string,
|
||||
/** Index value of the tab. */
|
||||
index: number
|
||||
}
|
||||
|
||||
/** A single animated tab. */
|
||||
const AnimatedTab: React.FC<IAnimatedTab> = ({ index, ...props }) => {
|
||||
// get the currently selected index from useTabsContext
|
||||
const { selectedIndex } = useTabsContext();
|
||||
|
@ -91,20 +100,32 @@ const AnimatedTab: React.FC<IAnimatedTab> = ({ index, ...props }) => {
|
|||
);
|
||||
};
|
||||
|
||||
/** Structure to represent a tab. */
|
||||
type Item = {
|
||||
/** Tab text. */
|
||||
text: React.ReactNode,
|
||||
/** Tab tooltip text. */
|
||||
title?: string,
|
||||
/** URL to visit when the tab is selected. */
|
||||
href?: string,
|
||||
/** Route to visit when the tab is selected. */
|
||||
to?: string,
|
||||
/** Callback when the tab is selected. */
|
||||
action?: () => void,
|
||||
/** Display a counter over the tab. */
|
||||
count?: number,
|
||||
/** Unique name for this tab. */
|
||||
name: string
|
||||
}
|
||||
|
||||
interface ITabs {
|
||||
/** Array of structured tab items. */
|
||||
items: Item[],
|
||||
/** Name of the active tab item. */
|
||||
activeItem: string,
|
||||
}
|
||||
|
||||
/** Animated tabs component. */
|
||||
const Tabs = ({ items, activeItem }: ITabs) => {
|
||||
const defaultIndex = items.findIndex(({ name }) => name === activeItem);
|
||||
|
||||
|
|
|
@ -60,19 +60,29 @@ const families = {
|
|||
};
|
||||
|
||||
interface IText extends Pick<React.HTMLAttributes<HTMLParagraphElement>, 'dangerouslySetInnerHTML'> {
|
||||
/** How to align the text. */
|
||||
align?: Alignments,
|
||||
/** Extra class names for the outer element. */
|
||||
className?: string,
|
||||
dateTime?: string,
|
||||
/** Typeface of the text. */
|
||||
family?: Families,
|
||||
/** Font size of the text. */
|
||||
size?: Sizes,
|
||||
/** HTML element name of the outer element. */
|
||||
tag?: Tags,
|
||||
/** Theme for the text. */
|
||||
theme?: Themes,
|
||||
/** Letter-spacing of the text. */
|
||||
tracking?: TrackingSizes,
|
||||
/** Transform (eg uppercase) for the text. */
|
||||
transform?: TransformProperties,
|
||||
/** Whether to truncate the text if its container is too small. */
|
||||
truncate?: boolean,
|
||||
/** Font weight of the text. */
|
||||
weight?: Weights
|
||||
}
|
||||
|
||||
/** UI-friendly text container with dark mode support. */
|
||||
const Text: React.FC<IText> = React.forwardRef(
|
||||
(props: IText, ref: React.LegacyRef<any>) => {
|
||||
const {
|
||||
|
|
|
@ -2,15 +2,23 @@ import classNames from 'classnames';
|
|||
import React from 'react';
|
||||
|
||||
interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'maxLength' | 'onChange' | 'required'> {
|
||||
/** Put the cursor into the input on mount. */
|
||||
autoFocus?: boolean,
|
||||
/** The initial text in the input. */
|
||||
defaultValue?: string,
|
||||
/** Internal input name. */
|
||||
name?: string,
|
||||
/** Renders the textarea as a code editor. */
|
||||
isCodeEditor?: boolean,
|
||||
/** Text to display before a value is entered. */
|
||||
placeholder?: string,
|
||||
/** Text in the textarea. */
|
||||
value?: string,
|
||||
/** Whether the device should autocomplete text in this textarea. */
|
||||
autoComplete?: string,
|
||||
}
|
||||
|
||||
/** Textarea with custom styles. */
|
||||
const Textarea = React.forwardRef(
|
||||
({ isCodeEditor = false, ...props }: ITextarea, ref: React.ForwardedRef<HTMLTextAreaElement>) => {
|
||||
return (
|
||||
|
|
|
@ -4,9 +4,11 @@ import React from 'react';
|
|||
import './tooltip.css';
|
||||
|
||||
interface ITooltip {
|
||||
/** Text to display in the tooltip. */
|
||||
text: string,
|
||||
}
|
||||
|
||||
/** Hoverable tooltip element. */
|
||||
const Tooltip: React.FC<ITooltip> = ({
|
||||
children,
|
||||
text,
|
||||
|
|
|
@ -5,24 +5,32 @@ import HStack from 'soapbox/components/ui/hstack/hstack';
|
|||
import Stack from 'soapbox/components/ui/stack/stack';
|
||||
|
||||
interface IWidgetTitle {
|
||||
title: string | React.ReactNode,
|
||||
/** Title text for the widget. */
|
||||
title: React.ReactNode,
|
||||
}
|
||||
|
||||
/** Title of a widget. */
|
||||
const WidgetTitle = ({ title }: IWidgetTitle): JSX.Element => (
|
||||
<Text size='xl' weight='bold' tag='h1'>{title}</Text>
|
||||
);
|
||||
|
||||
/** Body of a widget. */
|
||||
const WidgetBody: React.FC = ({ children }): JSX.Element => (
|
||||
<Stack space={3}>{children}</Stack>
|
||||
);
|
||||
|
||||
interface IWidget {
|
||||
title: string | React.ReactNode,
|
||||
/** Widget title text. */
|
||||
title: React.ReactNode,
|
||||
/** Callback when the widget action is clicked. */
|
||||
onActionClick?: () => void,
|
||||
/** URL to the svg icon for the widget action. */
|
||||
actionIcon?: string,
|
||||
/** Text for the action. */
|
||||
actionTitle?: string,
|
||||
}
|
||||
|
||||
/** Sidebar widget. */
|
||||
const Widget: React.FC<IWidget> = ({
|
||||
title,
|
||||
children,
|
||||
|
|
Loading…
Reference in a new issue