D: Improve TSDocs for frontend components

This commit is contained in:
Ivan 2024-11-21 15:09:31 +03:00
parent c7df031041
commit 4172e387c2
40 changed files with 326 additions and 41 deletions

View File

@ -50,6 +50,7 @@ This readme file is used mostly to document project dependencies and conventions
- @uiw/react-codemirror - @uiw/react-codemirror
- @uiw/codemirror-themes - @uiw/codemirror-themes
- @lezer/lr - @lezer/lr
- @dagrejs/dagre
</pre> </pre>
</details> </details>
<details> <details>

View File

@ -24,7 +24,7 @@ function UserMenu() {
<AnimatePresence mode='wait'> <AnimatePresence mode='wait'>
{loading ? ( {loading ? (
<AnimateFade key='nav_user_badge_loader'> <AnimateFade key='nav_user_badge_loader'>
<Loader circular size={3} /> <Loader circular scale={1.5} />
</AnimateFade> </AnimateFade>
) : null} ) : null}
{!user && !loading ? ( {!user && !loading ? (

View File

@ -45,6 +45,7 @@ export interface DomIconProps<RequestData> extends IconProps {
value: RequestData; value: RequestData;
} }
/** Icon for library item type. */
export function ItemTypeIcon({ value, size = '1.25rem', className }: DomIconProps<LibraryItemType>) { export function ItemTypeIcon({ value, size = '1.25rem', className }: DomIconProps<LibraryItemType>) {
switch (value) { switch (value) {
case LibraryItemType.RSFORM: case LibraryItemType.RSFORM:
@ -54,6 +55,7 @@ export function ItemTypeIcon({ value, size = '1.25rem', className }: DomIconProp
} }
} }
/** Icon for access policy. */
export function PolicyIcon({ value, size = '1.25rem', className }: DomIconProps<AccessPolicy>) { export function PolicyIcon({ value, size = '1.25rem', className }: DomIconProps<AccessPolicy>) {
switch (value) { switch (value) {
case AccessPolicy.PRIVATE: case AccessPolicy.PRIVATE:
@ -65,6 +67,7 @@ export function PolicyIcon({ value, size = '1.25rem', className }: DomIconProps<
} }
} }
/** Icon for visibility. */
export function VisibilityIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) { export function VisibilityIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
if (value) { if (value) {
return <IconShow size={size} className={className ?? 'clr-text-green'} />; return <IconShow size={size} className={className ?? 'clr-text-green'} />;
@ -73,6 +76,7 @@ export function VisibilityIcon({ value, size = '1.25rem', className }: DomIconPr
} }
} }
/** Icon for subfolders. */
export function SubfoldersIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) { export function SubfoldersIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
if (value) { if (value) {
return <IconSubfolders size={size} className={className ?? 'clr-text-green'} />; return <IconSubfolders size={size} className={className ?? 'clr-text-green'} />;
@ -81,6 +85,7 @@ export function SubfoldersIcon({ value, size = '1.25rem', className }: DomIconPr
} }
} }
/** Icon for location. */
export function LocationIcon({ value, size = '1.25rem', className }: DomIconProps<string>) { export function LocationIcon({ value, size = '1.25rem', className }: DomIconProps<string>) {
switch (value.substring(0, 2) as LocationHead) { switch (value.substring(0, 2) as LocationHead) {
case LocationHead.COMMON: case LocationHead.COMMON:
@ -94,6 +99,7 @@ export function LocationIcon({ value, size = '1.25rem', className }: DomIconProp
} }
} }
/** Icon for term graph dependency mode. */
export function DependencyIcon({ value, size = '1.25rem', className }: DomIconProps<DependencyMode>) { export function DependencyIcon({ value, size = '1.25rem', className }: DomIconProps<DependencyMode>) {
switch (value) { switch (value) {
case DependencyMode.ALL: case DependencyMode.ALL:
@ -109,6 +115,7 @@ export function DependencyIcon({ value, size = '1.25rem', className }: DomIconPr
} }
} }
/** Icon for constituenta match mode. */
export function MatchModeIcon({ value, size = '1.25rem', className }: DomIconProps<CstMatchMode>) { export function MatchModeIcon({ value, size = '1.25rem', className }: DomIconProps<CstMatchMode>) {
switch (value) { switch (value) {
case CstMatchMode.ALL: case CstMatchMode.ALL:
@ -124,6 +131,7 @@ export function MatchModeIcon({ value, size = '1.25rem', className }: DomIconPro
} }
} }
/** Icon for expression status. */
export function StatusIcon({ value, size = '1.25rem', className }: DomIconProps<ExpressionStatus>) { export function StatusIcon({ value, size = '1.25rem', className }: DomIconProps<ExpressionStatus>) {
switch (value) { switch (value) {
case ExpressionStatus.VERIFIED: case ExpressionStatus.VERIFIED:
@ -141,6 +149,7 @@ export function StatusIcon({ value, size = '1.25rem', className }: DomIconProps<
} }
} }
/** Icon for constituenta type. */
export function CstTypeIcon({ value, size = '1.25rem', className }: DomIconProps<CstType>) { export function CstTypeIcon({ value, size = '1.25rem', className }: DomIconProps<CstType>) {
switch (value) { switch (value) {
case CstType.BASE: case CstType.BASE:
@ -162,6 +171,7 @@ export function CstTypeIcon({ value, size = '1.25rem', className }: DomIconProps
} }
} }
/** Icon for relocation direction. */
export function RelocateUpIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) { export function RelocateUpIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
if (value) { if (value) {
return <IconMoveUp size={size} className={className ?? 'clr-text-primary'} />; return <IconMoveUp size={size} className={className ?? 'clr-text-primary'} />;

View File

@ -7,11 +7,19 @@ import { CProps } from '../props';
import TooltipConstituenta from './TooltipConstituenta'; import TooltipConstituenta from './TooltipConstituenta';
interface BadgeConstituentaProps extends CProps.Styling { interface BadgeConstituentaProps extends CProps.Styling {
/** Prefix for tooltip ID. */
prefixID?: string; prefixID?: string;
/** Constituenta to display. */
value: IConstituenta; value: IConstituenta;
/** Color theme to use. */
theme: IColorTheme; theme: IColorTheme;
} }
/**
* Displays a badge with a constituenta alias and information tooltip.
*/
function BadgeConstituenta({ value, prefixID, className, style, theme }: BadgeConstituentaProps) { function BadgeConstituenta({ value, prefixID, className, style, theme }: BadgeConstituentaProps) {
return ( return (
<div <div

View File

@ -6,9 +6,13 @@ import { colorFgGrammeme } from '@/styling/color';
import { labelGrammeme } from '@/utils/labels'; import { labelGrammeme } from '@/utils/labels';
interface BadgeGrammemeProps { interface BadgeGrammemeProps {
/** Grammeme to display. */
grammeme: GramData; grammeme: GramData;
} }
/**
* Displays a badge with a grammeme tag.
*/
function BadgeGrammeme({ grammeme }: BadgeGrammemeProps) { function BadgeGrammeme({ grammeme }: BadgeGrammemeProps) {
const { colors } = useConceptOptions(); const { colors } = useConceptOptions();
return ( return (

View File

@ -8,12 +8,22 @@ import { IconHelp } from '../Icons';
import { CProps } from '../props'; import { CProps } from '../props';
interface BadgeHelpProps extends CProps.Styling { interface BadgeHelpProps extends CProps.Styling {
/** Topic to display in a tooltip. */
topic: HelpTopic; topic: HelpTopic;
/** Offset from the cursor to the tooltip. */
offset?: number; offset?: number;
/** Classname for padding. */
padding?: string; padding?: string;
/** Place of the tooltip in relation to the cursor. */
place?: PlacesType; place?: PlacesType;
} }
/**
* Display help icon with a manual page tooltip.
*/
function BadgeHelp({ topic, padding = 'p-1', ...restProps }: BadgeHelpProps) { function BadgeHelp({ topic, padding = 'p-1', ...restProps }: BadgeHelpProps) {
const { showHelp } = useConceptOptions(); const { showHelp } = useConceptOptions();

View File

@ -3,9 +3,13 @@ import { globals } from '@/utils/constants';
import { LocationIcon } from '../DomainIcons'; import { LocationIcon } from '../DomainIcons';
interface BadgeLocationProps { interface BadgeLocationProps {
/** Location to display. */
location: string; location: string;
} }
/**
* Displays location icon with a full text tooltip.
*/
function BadgeLocation({ location }: BadgeLocationProps) { function BadgeLocation({ location }: BadgeLocationProps) {
return ( return (
<div className='pl-2' data-tooltip-id={globals.tooltip} data-tooltip-content={location}> <div className='pl-2' data-tooltip-id={globals.tooltip} data-tooltip-content={location}>

View File

@ -3,10 +3,16 @@ import { IWordForm } from '@/models/language';
import BadgeGrammeme from './BadgeGrammeme'; import BadgeGrammeme from './BadgeGrammeme';
interface BadgeWordFormProps { interface BadgeWordFormProps {
keyPrefix?: string; /** Word form to display. */
form: IWordForm; form: IWordForm;
/** Prefix for grammemes keys. */
keyPrefix?: string;
} }
/**
* Displays a badge with grammemes of a word form.
*/
function BadgeWordForm({ keyPrefix, form }: BadgeWordFormProps) { function BadgeWordForm({ keyPrefix, form }: BadgeWordFormProps) {
return ( return (
<div className='flex flex-wrap justify-start gap-1 select-none w-fit'> <div className='flex flex-wrap justify-start gap-1 select-none w-fit'>

View File

@ -98,8 +98,8 @@ function PickConstituenta({
id={id ? `${id}__search` : undefined} id={id ? `${id}__search` : undefined}
className='clr-input rounded-t-md' className='clr-input rounded-t-md'
noBorder noBorder
value={filterText} query={filterText}
onChange={newValue => setFilterText(newValue)} onChangeQuery={newValue => setFilterText(newValue)}
/> />
<DataTable <DataTable
id={id} id={id}

View File

@ -132,8 +132,8 @@ function PickMultiConstituenta({
id='dlg_constituents_search' id='dlg_constituents_search'
noBorder noBorder
className='min-w-[6rem] pr-2 flex-grow' className='min-w-[6rem] pr-2 flex-grow'
value={filterText} query={filterText}
onChange={setFilterText} onChangeQuery={setFilterText}
/> />
<ToolbarGraphSelection <ToolbarGraphSelection
graph={foldedGraph} graph={foldedGraph}

View File

@ -129,8 +129,8 @@ function PickSchema({
id={id ? `${id}__search` : undefined} id={id ? `${id}__search` : undefined}
className='clr-input flex-grow rounded-t-md' className='clr-input flex-grow rounded-t-md'
noBorder noBorder
value={filterText} query={filterText}
onChange={newValue => setFilterText(newValue)} onChangeQuery={newValue => setFilterText(newValue)}
/> />
<div ref={locationMenu.ref}> <div ref={locationMenu.ref}>
<MiniButton <MiniButton

View File

@ -25,45 +25,84 @@ import TableHeader from './TableHeader';
export { type ColumnSort, createColumnHelper, type RowSelectionState, type VisibilityState }; export { type ColumnSort, createColumnHelper, type RowSelectionState, type VisibilityState };
/** Style to conditionally apply to rows. */
export interface IConditionalStyle<TData> { export interface IConditionalStyle<TData> {
/** Callback to determine if the style should be applied. */
when: (rowData: TData) => boolean; when: (rowData: TData) => boolean;
/** Style to apply. */
style: React.CSSProperties; style: React.CSSProperties;
} }
export interface DataTableProps<TData extends RowData> export interface DataTableProps<TData extends RowData>
extends CProps.Styling, extends CProps.Styling,
Pick<TableOptions<TData>, 'data' | 'columns' | 'onRowSelectionChange' | 'onColumnVisibilityChange'> { Pick<TableOptions<TData>, 'data' | 'columns' | 'onRowSelectionChange' | 'onColumnVisibilityChange'> {
/** Id of the component. */
id?: string; id?: string;
/** Indicates that padding should be minimal. */
dense?: boolean; dense?: boolean;
/** Number of rows to display. */
rows?: number; rows?: number;
/** Height of the content. */
contentHeight?: string; contentHeight?: string;
/** Top position of sticky header (0 if no other sticky elements are present). */
headPosition?: string; headPosition?: string;
/** Disable header. */
noHeader?: boolean; noHeader?: boolean;
/** Disable footer. */
noFooter?: boolean; noFooter?: boolean;
/** List of styles to conditionally apply to rows. */
conditionalRowStyles?: IConditionalStyle<TData>[]; conditionalRowStyles?: IConditionalStyle<TData>[];
/** Component to display when there is no data. */
noDataComponent?: React.ReactNode; noDataComponent?: React.ReactNode;
/** Callback to be called when a row is clicked. */
onRowClicked?: (rowData: TData, event: CProps.EventMouse) => void; onRowClicked?: (rowData: TData, event: CProps.EventMouse) => void;
/** Callback to be called when a row is double clicked. */
onRowDoubleClicked?: (rowData: TData, event: CProps.EventMouse) => void; onRowDoubleClicked?: (rowData: TData, event: CProps.EventMouse) => void;
/** Enable row selection. */
enableRowSelection?: boolean; enableRowSelection?: boolean;
/** Current row selection. */
rowSelection?: RowSelectionState; rowSelection?: RowSelectionState;
/** Enable hiding of columns. */
enableHiding?: boolean; enableHiding?: boolean;
/** Current column visibility. */
columnVisibility?: VisibilityState; columnVisibility?: VisibilityState;
/** Enable pagination. */
enablePagination?: boolean; enablePagination?: boolean;
/** Number of rows per page. */
paginationPerPage?: number; paginationPerPage?: number;
/** List of options to choose from for pagination. */
paginationOptions?: number[]; paginationOptions?: number[];
/** Callback to be called when the pagination option is changed. */
onChangePaginationOption?: (newValue: number) => void; onChangePaginationOption?: (newValue: number) => void;
/** Enable sorting. */
enableSorting?: boolean; enableSorting?: boolean;
/** Initial sorting. */
initialSorting?: ColumnSort; initialSorting?: ColumnSort;
} }
/** /**
* UI element: data representation as a table. * Dta representation as a table.
* *
* @param headPosition - Top position of sticky header (0 if no other sticky elements are present). * @param headPosition - Top position of sticky header (0 if no other sticky elements are present).
* No sticky header if omitted * No sticky header if omitted

View File

@ -7,18 +7,24 @@ import { useConceptOptions } from '@/context/ConceptOptionsContext';
import AnimateFade from '../wrap/AnimateFade'; import AnimateFade from '../wrap/AnimateFade';
interface LoaderProps { interface LoaderProps {
size?: number; /** Scale of the loader from 1 to 10. */
scale?: number;
/** Show a circular loader. */
circular?: boolean; circular?: boolean;
} }
function Loader({ size = 10, circular }: LoaderProps) { /**
* Displays animated loader.
*/
function Loader({ scale = 5, circular }: LoaderProps) {
const { colors } = useConceptOptions(); const { colors } = useConceptOptions();
return ( return (
<AnimateFade noFadeIn className='flex justify-center'> <AnimateFade noFadeIn className='flex justify-center'>
{circular ? ( {circular ? (
<ThreeCircles color={colors.bgPrimary} height={size * 10} width={size * 10} /> <ThreeCircles color={colors.bgPrimary} height={scale * 20} width={scale * 20} />
) : ( ) : (
<ThreeDots color={colors.bgPrimary} height={size * 10} width={size * 10} radius={size} /> <ThreeDots color={colors.bgPrimary} height={scale * 20} width={scale * 20} radius={scale * 2} />
)} )}
</AnimateFade> </AnimateFade>
); );

View File

@ -5,11 +5,19 @@ import { globals } from '@/utils/constants';
import { CProps } from '../props'; import { CProps } from '../props';
interface MiniButtonProps extends CProps.Button { interface MiniButtonProps extends CProps.Button {
/** Icon to display in the button. */
icon: React.ReactNode; icon: React.ReactNode;
/** Disable hover effect. */
noHover?: boolean; noHover?: boolean;
/** Disable padding. */
noPadding?: boolean; noPadding?: boolean;
} }
/**
* Displays small transparent button with an icon.
*/
function MiniButton({ function MiniButton({
icon, icon,
noHover, noHover,

View File

@ -18,23 +18,46 @@ import MiniButton from './MiniButton';
import Overlay from './Overlay'; import Overlay from './Overlay';
export interface ModalProps extends CProps.Styling { export interface ModalProps extends CProps.Styling {
/** Title of the modal window. */
header?: string; header?: string;
/** Text of the submit button. */
submitText?: string; submitText?: string;
/** Tooltip for the submit button when the form is invalid. */
submitInvalidTooltip?: string; submitInvalidTooltip?: string;
/** Indicates that form is readonly. */
readonly?: boolean; readonly?: boolean;
/** Indicates that submit button is enabled. */
canSubmit?: boolean; canSubmit?: boolean;
/** Indicates that the modal window should be scrollable. */
overflowVisible?: boolean; overflowVisible?: boolean;
/** Callback to be called when the modal window is closed. */
hideWindow: () => void; hideWindow: () => void;
/** Callback to be called before submit. */
beforeSubmit?: () => boolean; beforeSubmit?: () => boolean;
/** Callback to be called after submit. */
onSubmit?: () => void; onSubmit?: () => void;
/** Callback to be called after cancel. */
onCancel?: () => void; onCancel?: () => void;
/** Help topic to be displayed in the modal window. */
helpTopic?: HelpTopic; helpTopic?: HelpTopic;
/** Callback to determine if help should be displayed. */
hideHelpWhen?: () => boolean; hideHelpWhen?: () => boolean;
} }
/**
* Displays a customizable modal window.
*/
function Modal({ function Modal({
children, children,

View File

@ -2,6 +2,9 @@ import clsx from 'clsx';
import { CProps } from '../props'; import { CProps } from '../props';
/**
* Wraps content in a div with a centered text.
*/
function NoData({ className, children, ...restProps }: CProps.Div) { function NoData({ className, children, ...restProps }: CProps.Div) {
return ( return (
<div className={clsx('p-3 flex flex-col items-center text-center select-none w-full', className)} {...restProps}> <div className={clsx('p-3 flex flex-col items-center text-center select-none w-full', className)} {...restProps}>

View File

@ -3,11 +3,19 @@ import clsx from 'clsx';
import { CProps } from '../props'; import { CProps } from '../props';
interface OverlayProps extends CProps.Styling { interface OverlayProps extends CProps.Styling {
/** Id of the overlay. */
id?: string; id?: string;
/** Classnames for position of the overlay. */
position?: string; position?: string;
/** Classname for z-index of the overlay. */
layer?: string; layer?: string;
} }
/**
* Displays a transparent overlay over the main content.
*/
function Overlay({ function Overlay({
children, children,
className, className,

View File

@ -5,15 +5,26 @@ import { useMemo } from 'react';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useConceptOptions } from '@/context/ConceptOptionsContext';
import useWindowSize from '@/hooks/useWindowSize'; import useWindowSize from '@/hooks/useWindowSize';
/** Maximum width of the viewer. */
const MAXIMUM_WIDTH = 1600; const MAXIMUM_WIDTH = 1600;
/** Minimum width of the viewer. */
const MINIMUM_WIDTH = 300; const MINIMUM_WIDTH = 300;
interface PDFViewerProps { interface PDFViewerProps {
/** PDF file to display. */
file?: string; file?: string;
/** Offset from the left side of the window. */
offsetXpx?: number; offsetXpx?: number;
/** Minimum width of the viewer. */
minWidth?: number; minWidth?: number;
} }
/**
* Displays a PDF file using an embedded viewer.
*/
function PDFViewer({ file, offsetXpx, minWidth = MINIMUM_WIDTH }: PDFViewerProps) { function PDFViewer({ file, offsetXpx, minWidth = MINIMUM_WIDTH }: PDFViewerProps) {
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const { calculateHeight } = useConceptOptions(); const { calculateHeight } = useConceptOptions();

View File

@ -2,6 +2,9 @@ interface PrettyJsonProps {
data: unknown; data: unknown;
} }
/**
* Displays JSON data in a formatted string.
*/
function PrettyJson({ data }: PrettyJsonProps) { function PrettyJson({ data }: PrettyJsonProps) {
return <pre>{JSON.stringify(data, null, 2)}</pre>; return <pre>{JSON.stringify(data, null, 2)}</pre>;
} }

View File

@ -6,15 +6,37 @@ import Overlay from './Overlay';
import TextInput from './TextInput'; import TextInput from './TextInput';
interface SearchBarProps extends CProps.Styling { interface SearchBarProps extends CProps.Styling {
value: string; /** Id of the search bar. */
noIcon?: boolean;
id?: string; id?: string;
/** Search query. */
query: string;
/** Placeholder text. */
placeholder?: string; placeholder?: string;
onChange?: (newValue: string) => void;
/** Callback to be called when the search query changes. */
onChangeQuery?: (newValue: string) => void;
/** Disable search icon. */
noIcon?: boolean;
/** Disable border. */
noBorder?: boolean; noBorder?: boolean;
} }
function SearchBar({ id, value, noIcon, onChange, noBorder, placeholder = 'Поиск', ...restProps }: SearchBarProps) { /**
* Displays a search bar with a search icon and text input.
*/
function SearchBar({
id,
query,
noIcon,
onChangeQuery,
noBorder,
placeholder = 'Поиск',
...restProps
}: SearchBarProps) {
return ( return (
<div {...restProps}> <div {...restProps}>
{!noIcon ? ( {!noIcon ? (
@ -29,8 +51,8 @@ function SearchBar({ id, value, noIcon, onChange, noBorder, placeholder = 'По
type='search' type='search'
className={clsx('outline-none bg-transparent', !noIcon && 'pl-10')} className={clsx('outline-none bg-transparent', !noIcon && 'pl-10')}
noBorder={noBorder} noBorder={noBorder}
value={value} value={query}
onChange={event => (onChange ? onChange(event.target.value) : undefined)} onChange={event => (onChangeQuery ? onChangeQuery(event.target.value) : undefined)}
/> />
</div> </div>
); );

View File

@ -45,6 +45,9 @@ export interface SelectMultiProps<Option, Group extends GroupBase<Option> = Grou
noPortal?: boolean; noPortal?: boolean;
} }
/**
* Displays a multi-select component.
*/
function SelectMulti<Option, Group extends GroupBase<Option> = GroupBase<Option>>({ function SelectMulti<Option, Group extends GroupBase<Option> = GroupBase<Option>>({
noPortal, noPortal,
...restProps ...restProps

View File

@ -46,6 +46,9 @@ export interface SelectSingleProps<Option, Group extends GroupBase<Option> = Gro
noBorder?: boolean; noBorder?: boolean;
} }
/**
* Displays a single-select component.
*/
function SelectSingle<Option, Group extends GroupBase<Option> = GroupBase<Option>>({ function SelectSingle<Option, Group extends GroupBase<Option> = GroupBase<Option>>({
noPortal, noPortal,
noBorder, noBorder,

View File

@ -11,22 +11,38 @@ import MiniButton from './MiniButton';
import Overlay from './Overlay'; import Overlay from './Overlay';
interface SelectTreeProps<ItemType> extends CProps.Styling { interface SelectTreeProps<ItemType> extends CProps.Styling {
items: ItemType[]; /** Current value. */
value: ItemType; value: ItemType;
setValue: (newItem: ItemType) => void;
getParent: (item: ItemType) => ItemType; /** List of available items. */
getLabel: (item: ItemType) => string; items: ItemType[];
getDescription: (item: ItemType) => string;
/** Prefix for the ids of the elements. */
prefix: string; prefix: string;
/** Callback to be called when the value changes. */
onChangeValue: (newItem: ItemType) => void;
/** Callback providing the parent of the item. */
getParent: (item: ItemType) => ItemType;
/** Callback providing the label of the item. */
getLabel: (item: ItemType) => string;
/** Callback providing the description of the item. */
getDescription: (item: ItemType) => string;
} }
/**
* Displays a tree of items and allows user to select one.
*/
function SelectTree<ItemType>({ function SelectTree<ItemType>({
items, items,
value, value,
getParent, getParent,
getLabel, getLabel,
getDescription, getDescription,
setValue, onChangeValue,
prefix, prefix,
...restProps ...restProps
}: SelectTreeProps<ItemType>) { }: SelectTreeProps<ItemType>) {
@ -71,9 +87,9 @@ function SelectTree<ItemType>({
(event: CProps.EventMouse, target: ItemType) => { (event: CProps.EventMouse, target: ItemType) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
setValue(target); onChangeValue(target);
}, },
[setValue] [onChangeValue]
); );
return ( return (

View File

@ -5,13 +5,22 @@ import { globals } from '@/utils/constants';
import { CProps } from '../props'; import { CProps } from '../props';
interface SelectorButtonProps extends CProps.Button { interface SelectorButtonProps extends CProps.Button {
/** Text to display in the button. */
text?: string; text?: string;
/** Icon to display in the button. */
icon?: React.ReactNode; icon?: React.ReactNode;
/** Classnames for the colors of the button. */
colors?: string; colors?: string;
/** Indicates if button background should be transparent. */
transparent?: boolean; transparent?: boolean;
} }
/**
* Displays a button with an icon and text that opens a dropdown menu.
*/
function SelectorButton({ function SelectorButton({
text, text,
icon, icon,

View File

@ -3,11 +3,19 @@ import clsx from 'clsx';
import { CProps } from '../props'; import { CProps } from '../props';
interface SubmitButtonProps extends CProps.Button { interface SubmitButtonProps extends CProps.Button {
/** Text to display in the button. */
text?: string; text?: string;
loading?: boolean;
/** Icon to display in the button. */
icon?: React.ReactNode; icon?: React.ReactNode;
/** Indicates that loading is in progress. */
loading?: boolean;
} }
/**
* Displays submit type button with icon and text.
*/
function SubmitButton({ text = 'ОК', icon, disabled, loading, className, ...restProps }: SubmitButtonProps) { function SubmitButton({ text = 'ОК', icon, disabled, loading, className, ...restProps }: SubmitButtonProps) {
return ( return (
<button <button

View File

@ -7,9 +7,13 @@ import { globals } from '@/utils/constants';
import { CProps } from '../props'; import { CProps } from '../props';
interface TabLabelProps extends Omit<TabPropsImpl, 'children'>, CProps.Titled { interface TabLabelProps extends Omit<TabPropsImpl, 'children'>, CProps.Titled {
/** Label to display in the tab. */
label?: string; label?: string;
} }
/**
* Displays a tab header with a label.
*/
function TabLabel({ label, title, titleHtml, hideTitle, className, ...otherProps }: TabLabelProps) { function TabLabel({ label, title, titleHtml, hideTitle, className, ...otherProps }: TabLabelProps) {
return ( return (
<TabImpl <TabImpl

View File

@ -4,11 +4,19 @@ import { CProps } from '../props';
import Label from './Label'; import Label from './Label';
export interface TextAreaProps extends CProps.Editor, CProps.Colors, CProps.TextArea { export interface TextAreaProps extends CProps.Editor, CProps.Colors, CProps.TextArea {
/** Indicates that padding should be minimal. */
dense?: boolean; dense?: boolean;
/** Disable resize when content overflows. */
noResize?: boolean; noResize?: boolean;
/** Disable resize to fit content. */
fitContent?: boolean; fitContent?: boolean;
} }
/**
* Displays a customizable textarea with a label.
*/
function TextArea({ function TextArea({
id, id,
label, label,

View File

@ -6,11 +6,19 @@ import { truncateToLastWord } from '@/utils/utils';
import { CProps } from '../props'; import { CProps } from '../props';
export interface TextContentProps extends CProps.Styling { export interface TextContentProps extends CProps.Styling {
/** Text to display. */
text: string; text: string;
/** Maximum number of symbols to display. */
maxLength?: number; maxLength?: number;
/** Disable full text in a tooltip. */
noTooltip?: boolean; noTooltip?: boolean;
} }
/**
* Displays text limited to a certain number of symbols.
*/
function TextContent({ className, text, maxLength, noTooltip, ...restProps }: TextContentProps) { function TextContent({ className, text, maxLength, noTooltip, ...restProps }: TextContentProps) {
const truncated = maxLength ? truncateToLastWord(text, maxLength) : text; const truncated = maxLength ? truncateToLastWord(text, maxLength) : text;
const isTruncated = maxLength && text.length > maxLength; const isTruncated = maxLength && text.length > maxLength;

View File

@ -4,7 +4,10 @@ import { CProps } from '../props';
import Label from './Label'; import Label from './Label';
interface TextInputProps extends CProps.Editor, CProps.Colors, CProps.Input { interface TextInputProps extends CProps.Editor, CProps.Colors, CProps.Input {
/** Indicates that padding should be minimal. */
dense?: boolean; dense?: boolean;
/** Capture enter key. */
allowEnter?: boolean; allowEnter?: boolean;
} }
@ -14,6 +17,9 @@ function preventEnterCapture(event: React.KeyboardEvent<HTMLInputElement>) {
} }
} }
/**
* Displays a customizable input with a label.
*/
function TextInput({ function TextInput({
id, id,
label, label,

View File

@ -1,13 +1,25 @@
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
interface TextURLProps { interface TextURLProps {
/** Text to display. */
text: string; text: string;
/** Tooltip for the link. */
title?: string; title?: string;
/** URL to link to. */
href?: string; href?: string;
/** Color of the link. */
color?: string; color?: string;
/** Callback to be called when the link is clicked. */
onClick?: () => void; onClick?: () => void;
} }
/**
* Displays a text with a clickable link.
*/
function TextURL({ text, href, title, color = 'clr-text-url', onClick }: TextURLProps) { function TextURL({ text, href, title, color = 'clr-text-url', onClick }: TextURLProps) {
const design = `cursor-pointer hover:underline ${color}`; const design = `cursor-pointer hover:underline ${color}`;
if (href) { if (href) {

View File

@ -10,10 +10,16 @@ import { useConceptOptions } from '@/context/ConceptOptionsContext';
export type { PlacesType } from 'react-tooltip'; export type { PlacesType } from 'react-tooltip';
interface TooltipProps extends Omit<ITooltip, 'variant'> { interface TooltipProps extends Omit<ITooltip, 'variant'> {
layer?: string; /** Text to display in the tooltip. */
text?: string; text?: string;
/** Classname for z-index */
layer?: string;
} }
/**
* Displays content in a tooltip container.
*/
function Tooltip({ function Tooltip({
text, text,
children, children,

View File

@ -7,16 +7,34 @@ import { CProps } from '../props';
import MiniButton from './MiniButton'; import MiniButton from './MiniButton';
interface ValueIconProps extends CProps.Styling, CProps.Titled { interface ValueIconProps extends CProps.Styling, CProps.Titled {
/** Id of the component. */
id?: string; id?: string;
icon: React.ReactNode;
/** Value to display. */
value: string | number; value: string | number;
/** Icon to display. */
icon: React.ReactNode;
/** Classname for the text. */
textClassName?: string; textClassName?: string;
/** Callback to be called when the component is clicked. */
onClick?: (event: CProps.EventMouse) => void; onClick?: (event: CProps.EventMouse) => void;
/** Number of symbols to display in a small size. */
smallThreshold?: number; smallThreshold?: number;
/** Indicates that padding should be minimal. */
dense?: boolean; dense?: boolean;
/** Disable interaction. */
disabled?: boolean; disabled?: boolean;
} }
/**
* Displays a value with an icon that can be clicked.
*/
function ValueIcon({ function ValueIcon({
id, id,
dense, dense,

View File

@ -3,12 +3,22 @@ import clsx from 'clsx';
import { CProps } from '../props'; import { CProps } from '../props';
interface ValueLabeledProps extends CProps.Styling { interface ValueLabeledProps extends CProps.Styling {
/** Id of the component. */
id?: string; id?: string;
/** Label to display. */
label: string; label: string;
/** Value to display. */
text: string | number; text: string | number;
/** Tooltip for the component. */
title?: string; title?: string;
} }
/**
* Displays a labeled value.
*/
function ValueLabeled({ id, label, text, title, className, ...restProps }: ValueLabeledProps) { function ValueLabeled({ id, label, text, title, className, ...restProps }: ValueLabeledProps) {
return ( return (
<div className={clsx('flex justify-between gap-6', className)} {...restProps}> <div className={clsx('flex justify-between gap-6', className)} {...restProps}>

View File

@ -4,11 +4,19 @@ import { CProps } from '../props';
import ValueIcon from './ValueIcon'; import ValueIcon from './ValueIcon';
interface ValueStatsProps extends CProps.Styling, CProps.Titled { interface ValueStatsProps extends CProps.Styling, CProps.Titled {
/** Id of the component. */
id: string; id: string;
/** Icon to display. */
icon: React.ReactNode; icon: React.ReactNode;
/** Value to display. */
value: string | number; value: string | number;
} }
/**
* Displays statistics value with an icon.
*/
function ValueStats(props: ValueStatsProps) { function ValueStats(props: ValueStatsProps) {
return <ValueIcon dense smallThreshold={PARAMETER.statSmallThreshold} textClassName='min-w-[1.4rem]' {...props} />; return <ValueIcon dense smallThreshold={PARAMETER.statSmallThreshold} textClassName='min-w-[1.4rem]' {...props} />;
} }

View File

@ -190,8 +190,8 @@ function ToolbarSearch({
placeholder='Поиск' placeholder='Поиск'
noBorder noBorder
className={clsx('min-w-[7rem] sm:min-w-[10rem] max-w-[20rem]', folderMode && 'flex-grow')} className={clsx('min-w-[7rem] sm:min-w-[10rem] max-w-[20rem]', folderMode && 'flex-grow')}
value={query} query={query}
onChange={onChangeQuery} onChangeQuery={onChangeQuery}
/> />
{!folderMode ? ( {!folderMode ? (
<div ref={headMenu.ref} className='flex items-center h-full py-1 select-none'> <div ref={headMenu.ref} className='flex items-center h-full py-1 select-none'>
@ -249,8 +249,8 @@ function ToolbarSearch({
noIcon noIcon
noBorder noBorder
className='w-[4.5rem] sm:w-[5rem] flex-grow' className='w-[4.5rem] sm:w-[5rem] flex-grow'
value={path} query={path}
onChange={onChangePath} onChangeQuery={onChangePath}
/> />
) : null} ) : null}
</div> </div>

View File

@ -69,7 +69,7 @@ function TopicsDropdown({ activeTopic, onChangeTopic }: TopicsDropdownProps) {
<SelectTree <SelectTree
items={Object.values(HelpTopic).map(item => item as HelpTopic)} items={Object.values(HelpTopic).map(item => item as HelpTopic)}
value={activeTopic} value={activeTopic}
setValue={handleSelectTopic} onChangeValue={handleSelectTopic}
prefix={prefixes.topic_list} prefix={prefixes.topic_list}
getParent={item => topicParent.get(item) ?? item} getParent={item => topicParent.get(item) ?? item}
getLabel={labelHelpTopic} getLabel={labelHelpTopic}

View File

@ -17,7 +17,7 @@ function TopicsStatic({ activeTopic, onChangeTopic }: TopicsStaticProps) {
<SelectTree <SelectTree
items={Object.values(HelpTopic).map(item => item as HelpTopic)} items={Object.values(HelpTopic).map(item => item as HelpTopic)}
value={activeTopic} value={activeTopic}
setValue={onChangeTopic} onChangeValue={onChangeTopic}
prefix={prefixes.topic_list} prefix={prefixes.topic_list}
getParent={item => topicParent.get(item) ?? item} getParent={item => topicParent.get(item) ?? item}
getLabel={labelHelpTopic} getLabel={labelHelpTopic}

View File

@ -54,7 +54,7 @@ function StatusBar({ isModified, processing, activeCst, parseData, onAnalyze }:
onClick={onAnalyze} onClick={onAnalyze}
> >
<AnimatePresence mode='wait'> <AnimatePresence mode='wait'>
{processing ? <Loader key='status-loader' size={3} /> : null} {processing ? <Loader key='status-loader' scale={3} /> : null}
{!processing ? ( {!processing ? (
<> <>
<StatusIcon key='status-icon' size='1rem' value={status} /> <StatusIcon key='status-icon' size='1rem' value={status} />

View File

@ -152,8 +152,8 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
id='constituents_search' id='constituents_search'
noBorder noBorder
className='w-[8rem]' className='w-[8rem]'
value={filterText} query={filterText}
onChange={setFilterText} onChangeQuery={setFilterText}
/> />
</div> </div>
) : null} ) : null}

View File

@ -74,8 +74,8 @@ function ConstituentsSearch({ schema, activeID, activeExpression, dense, setFilt
id='constituents_search' id='constituents_search'
noBorder noBorder
className='min-w-[6rem] pr-2 flex-grow' className='min-w-[6rem] pr-2 flex-grow'
value={filterText} query={filterText}
onChange={setFilterText} onChangeQuery={setFilterText}
/> />
{selectMatchMode} {selectMatchMode}
{selectGraph} {selectGraph}