New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@snack-uikit/list

Package Overview
Dependencies
Maintainers
3
Versions
214
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@snack-uikit/list - npm Package Compare versions

Comparing version 0.1.3 to 0.2.0

16

CHANGELOG.md

@@ -6,2 +6,18 @@ # Change Log

# 0.2.0 (2024-02-02)
### Features
* **FF-4075:** add ref, onKeyDown props into list; add className, inactive props into items ([3ee860c](https://github.com/cloud-ru-tech/snack-uikit/commit/3ee860c5257be999ecf3a77d261d74240e7713f2))
### BREAKING CHANGES
* **FF-4075:** hide all selection props into 1 prop; add collapse contolled state; list fill container ([e1f8e66](https://github.com/cloud-ru-tech/snack-uikit/commit/e1f8e663a43a1261916513237f3b96b6c1146a67))
## 0.1.3 (2024-02-01)

@@ -8,0 +24,0 @@

6

dist/components/Items/BaseItem/BaseItem.d.ts

@@ -1,3 +0,3 @@

import { BaseItemPrivateProps, BaseItemProps, SwitchProps } from '../types';
type AllBaseItemProps = BaseItemProps & BaseItemPrivateProps & SwitchProps & {
import { BaseItemPrivateProps, BaseItemProps } from '../types';
type AllBaseItemProps = BaseItemProps & BaseItemPrivateProps & {
indeterminate?: boolean;

@@ -7,3 +7,3 @@ onSelect?(): void;

};
export declare function BaseItem({ beforeContent, afterContent, content, onClick, id, expandIcon, disabled, open, itemRef, switch: switchProp, onKeyDown, onFocus, indeterminate, onSelect, isParentNode, ...rest }: AllBaseItemProps): import("react/jsx-runtime").JSX.Element;
export declare function BaseItem({ beforeContent, afterContent, content, onClick, id, expandIcon, disabled, open, itemRef, switch: switchProp, onKeyDown, onFocus, indeterminate, onSelect, isParentNode, className, inactive, ...rest }: AllBaseItemProps): import("react/jsx-runtime").JSX.Element;
export {};

@@ -22,8 +22,9 @@ var __rest = (this && this.__rest) || function (s, e) {

export function BaseItem(_a) {
var { beforeContent, afterContent, content, onClick, id, expandIcon, disabled, open, itemRef, switch: switchProp, onKeyDown, onFocus, indeterminate, onSelect, isParentNode } = _a, rest = __rest(_a, ["beforeContent", "afterContent", "content", "onClick", "id", "expandIcon", "disabled", "open", "itemRef", "switch", "onKeyDown", "onFocus", "indeterminate", "onSelect", "isParentNode"]);
var { beforeContent, afterContent, content, onClick, id, expandIcon, disabled, open, itemRef, switch: switchProp, onKeyDown, onFocus, indeterminate, onSelect, isParentNode, className, inactive } = _a, rest = __rest(_a, ["beforeContent", "afterContent", "content", "onClick", "id", "expandIcon", "disabled", "open", "itemRef", "switch", "onKeyDown", "onFocus", "indeterminate", "onSelect", "isParentNode", "className", "inactive"]);
const { option, caption, description } = content || {};
const interactive = !inactive;
const { parentResetActiveFocusIndex } = useParentListContext();
const { size, marker } = useListContext();
const { level = 0 } = useCollapseContext();
const { value, onChange, selection, isSelectionSingle, isSelectionMultiple } = useSelectionContext();
const { value, onChange, mode, isSelectionSingle, isSelectionMultiple } = useSelectionContext();
const isChecked = isSelectionSingle ? value === id : value === null || value === void 0 ? void 0 : value.includes(id);

@@ -34,5 +35,7 @@ const handleChange = () => {

const handleItemClick = (e) => {
parentResetActiveFocusIndex === null || parentResetActiveFocusIndex === void 0 ? void 0 : parentResetActiveFocusIndex();
if (!isParentNode) {
handleChange();
if (interactive) {
parentResetActiveFocusIndex === null || parentResetActiveFocusIndex === void 0 ? void 0 : parentResetActiveFocusIndex();
if (!isParentNode) {
handleChange();
}
}

@@ -70,3 +73,3 @@ onClick === null || onClick === void 0 ? void 0 : onClick(e);

const props = extractSupportProps(rest);
return (_jsx("li", { role: 'menuitem', "data-test-id": props['data-test-id'] || 'list__base-item_' + id, children: _jsxs("button", { ref: itemRef, className: cn(commonStyles.listItem, styles.droplistItem), "data-size": size, onClick: handleItemClick, tabIndex: -1, "data-checked": (isParentNode && (indeterminate || isChecked)) || (isChecked && !switchProp) || undefined, "data-variant": selection || undefined, "data-open": open || undefined, disabled: disabled, onKeyDown: handleItemKeyDown, onFocus: handleItemFocus, style: { '--level': level }, children: [!switchProp && isSelectionSingle && marker && !isParentNode && (_jsx("div", { className: styles.markerContainer, "data-test-id": 'list__base-item-marker' })), !switchProp && isSelectionMultiple && (_jsx("div", { className: styles.checkbox, children: _jsx(Checkbox, { size: CHECKBOX_SIZE_MAP[size !== null && size !== void 0 ? size : 's'], disabled: disabled, tabIndex: -1, onChange: handleCheckboxChange, checked: isChecked, "data-test-id": 'list__base-item-checkbox', onClick: handleCheckboxClick, indeterminate: indeterminate }) })), beforeContent && _jsx("div", { className: styles.beforeContent, children: beforeContent }), _jsxs("div", { className: styles.content, children: [_jsxs("div", { className: styles.headline, children: [_jsx("span", { className: styles.option, children: _jsx(TruncateString, { text: option, maxLines: 1 }) }), caption && _jsx("span", { className: styles.caption, children: caption })] }), description && (_jsx("div", { className: styles.description, children: _jsx(TruncateString, { text: description, maxLines: 2 }) }))] }), afterContent, switchProp && _jsx(Switch, { disabled: disabled, checked: isChecked, "data-test-id": 'list__base-item-switch' }), !switchProp && expandIcon && _jsx("span", { className: styles.expandableIcon, children: expandIcon })] }) }));
return (_jsx("li", { role: 'menuitem', "data-test-id": props['data-test-id'] || 'list__base-item_' + id, children: _jsxs("button", { ref: itemRef, className: cn(commonStyles.listItem, styles.droplistItem, className), "data-size": size, onClick: handleItemClick, tabIndex: -1, "data-non-pointer": inactive && !onClick, "data-inactive": inactive || undefined, "data-checked": (isParentNode && (indeterminate || isChecked)) || (isChecked && !switchProp) || undefined, "data-variant": mode || undefined, "data-open": open || undefined, disabled: disabled, onKeyDown: handleItemKeyDown, onFocus: handleItemFocus, style: { '--level': level }, children: [!switchProp && isSelectionSingle && marker && !isParentNode && interactive && (_jsx("div", { className: styles.markerContainer, "data-test-id": 'list__base-item-marker' })), !switchProp && isSelectionMultiple && interactive && (_jsx("div", { className: styles.checkbox, children: _jsx(Checkbox, { size: CHECKBOX_SIZE_MAP[size !== null && size !== void 0 ? size : 's'], disabled: disabled, tabIndex: -1, onChange: handleCheckboxChange, checked: isChecked, "data-test-id": 'list__base-item-checkbox', onClick: handleCheckboxClick, indeterminate: indeterminate }) })), beforeContent && _jsx("div", { className: styles.beforeContent, children: beforeContent }), _jsxs("div", { className: styles.content, children: [_jsxs("div", { className: styles.headline, children: [_jsx("span", { className: styles.option, children: _jsx(TruncateString, { text: option, maxLines: 1 }) }), caption && _jsx("span", { className: styles.caption, children: caption })] }), description && (_jsx("div", { className: styles.description, children: _jsx(TruncateString, { text: description, maxLines: 2 }) }))] }), afterContent, switchProp && interactive && (_jsx(Switch, { disabled: disabled, checked: isChecked, "data-test-id": 'list__base-item-switch' })), !switchProp && expandIcon && _jsx("span", { className: styles.expandableIcon, children: expandIcon })] }) }));
}

@@ -65,3 +65,3 @@ var __rest = (this && this.__rest) || function (s, e) {

toggleOpenCollapsedItems: id => setOpenCollapsedItems(items => items.includes(id) ? items.filter(item => item !== id) : items.concat([id])),
}, children: _jsx(ListPrivate, { onKeyDown: handleListKeyDown, items: items, nested: true, search: search, scroll: scroll, scrollRef: scrollRef }) }), trigger: 'hover', open: (open || parentIds[parentOpenNestedIndex] === id) && !disabled, onOpenChange: handleOpenChange, placement: placement, children: _jsx(BaseItem, Object.assign({}, option, { disabled: disabled, open: open, expandIcon: _jsx(ChevronRightSVG, {}), id: id, isParentNode: true, indeterminate: isIndeterminate && !checked, onSelect: handleOnSelect })) }) }));
}, children: _jsx(ListPrivate, { onKeyDown: handleListKeyDown, items: items, nested: true, search: search, scroll: scroll, scrollRef: scrollRef, limitedScrollHeight: true }) }), trigger: 'hover', open: (open || parentIds[parentOpenNestedIndex] === id) && !disabled, onOpenChange: handleOpenChange, placement: placement, children: _jsx(BaseItem, Object.assign({}, option, { disabled: disabled, open: open, expandIcon: _jsx(ChevronRightSVG, {}), id: id, isParentNode: true, indeterminate: isIndeterminate && !checked, onSelect: handleOnSelect })) }) }));
}

@@ -15,22 +15,46 @@ import { FocusEvent, KeyboardEvent, MouseEvent, ReactNode, RefObject } from 'react';

export type BaseItemProps = WithSupportProps<{
/**
* Слот до основного контента
* @type ReactElement
*/
beforeContent?: ReactNode;
/**
* Слот после основного контента
* @type ReactElement
*/
afterContent?: ReactNode;
/** Основной контент айтема */
content: ItemContentProps;
/** Колбек обработки клика */
onClick?(e: MouseEvent<HTMLButtonElement>): void;
/** Колбек обработки нажатия клавиши */
onKeyDown?(e: KeyboardEvent<HTMLButtonElement>): void;
/** Колбек обработки фокуса */
onFocus?(e: FocusEvent<HTMLButtonElement>): void;
/** Колбек обработки блюра */
onBlur?(e: FocusEvent<HTMLButtonElement>): void;
/** Уникальный идентификатор */
id?: string | number;
/** Флаг неактивности элемента */
disabled?: boolean;
itemRef?: RefObject<HTMLButtonElement>;
className?: string;
/**
* Флаг отображения отключения реакции на любое css состояние (hover/focus и тд)
* <br>
* Так же элемент пропадает из навигации с клавиатуры, и не может быть выбран (selection)
*/
inactive?: boolean;
/**
* Флаг отображения состояния выбранного элемента через switch
*/
switch?: boolean;
}>;
export type SwitchProps = {
switch?: boolean;
};
export type ItemProps = (BaseItemProps & SwitchProps) | AccordionItemProps | NextListItemProps | GroupItemProps;
export type AccordionItemProps = BaseItemProps & {
type BaseItemsWithoutNonGroupProps = Omit<BaseItemProps, 'switch' | 'inactive'>;
export type ItemProps = BaseItemProps | AccordionItemProps | NextListItemProps | GroupItemProps;
export type AccordionItemProps = BaseItemsWithoutNonGroupProps & {
items: ItemProps[];
type: 'collapse';
};
export type NextListItemProps = BaseItemProps & {
export type NextListItemProps = BaseItemsWithoutNonGroupProps & {
items: ItemProps[];

@@ -41,5 +65,6 @@ type: 'next-list';

} & ScrollProps;
export type GroupItemProps = SeparatorProps & {
export type GroupItemProps = Omit<SeparatorProps, 'size'> & {
items: ItemProps[];
id?: string | number;
};
export {};
import { ReactNode } from 'react';
type SelectionSingleValueType = string | number | undefined;
export type SelectionSingleProps = {
type SelectionSingleState = {
/** Начальное состояние */

@@ -10,5 +10,7 @@ defaultValue?: SelectionSingleValueType;

onChange?(value: any): void;
/** Режим выбора */
mode: 'single';
};
export type SelectionSingleProps = {
setValue?(value: any): void;
/** Режим выбора */
selection: 'single';
/** Режим выбора single */

@@ -18,4 +20,4 @@ isSelectionSingle: true;

isSelectionMultiple: false;
};
export type SelectionMultipleProps = {
} & SelectionSingleState;
type SelectionMultipleState = {
/** Начальное состояние */

@@ -27,5 +29,7 @@ defaultValue?: SelectionSingleValueType[];

onChange?(value: any): void;
/** Режим выбора */
mode: 'multiple';
};
export type SelectionMultipleProps = {
setValue?(value: any): void;
/** Режим выбора */
selection: 'multiple';
/** Режим выбора single */

@@ -35,5 +39,5 @@ isSelectionSingle: false;

isSelectionMultiple: true;
};
} & SelectionMultipleState;
type SelectionNoneProps = {
selection?: undefined;
mode?: undefined;
value?: undefined;

@@ -46,4 +50,7 @@ onChange?: undefined;

};
export type SelectionProviderProps = SelectionSingleProps | SelectionMultipleProps | SelectionNoneProps;
type SelectionProviderProps = SelectionSingleProps | SelectionMultipleProps | SelectionNoneProps;
type SelectionContextType = Omit<SelectionNoneProps, 'defaultValue'> | Omit<SelectionSingleProps, 'defaultValue'> | Omit<SelectionMultipleProps, 'defaultValue'>;
export type SelectionState = {
selection?: SelectionSingleState | SelectionMultipleState;
};
export declare const SelectionContext: import("react").Context<SelectionContextType>;

@@ -55,5 +62,5 @@ export declare function isSelectionMultipleProps(props: any): props is SelectionMultipleProps;

};
export declare function SelectionProvider(props: SelectionProviderProps & Child): import("react/jsx-runtime").JSX.Element;
export declare function SelectionProvider({ children, ...props }: SelectionProviderProps & Child): import("react/jsx-runtime").JSX.Element;
export declare const useSelectionContext: () => SelectionContextType;
export declare function extractSelectionProps<T extends SelectionProviderProps>({ selection, value, defaultValue, onChange, }: T): SelectionProviderProps;
export declare function extractSelectionProps<T extends SelectionState>({ selection }: T): SelectionProviderProps;
export {};

@@ -0,1 +1,12 @@

var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { jsx as _jsx } from "react/jsx-runtime";

@@ -7,11 +18,11 @@ import { createContext, useCallback, useContext } from 'react';

onChange: undefined,
selection: undefined,
mode: undefined,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isSelectionMultipleProps(props) {
return 'selection' in props && props['selection'] === 'multiple';
return 'mode' in props && props['mode'] === 'multiple';
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isSelectionSingleProps(props) {
return 'selection' in props && props['selection'] === 'single';
return 'mode' in props && props['mode'] === 'single';
}

@@ -28,3 +39,10 @@ function SelectionSingleProvider({ value: valueProp, defaultValue, onChange: onChangeProp, children, }) {

}), [setValue]);
return (_jsx(SelectionContext.Provider, { value: { value, onChange, selection: 'single', isSelectionSingle: true, isSelectionMultiple: false, setValue }, children: children }));
return (_jsx(SelectionContext.Provider, { value: {
value,
onChange,
mode: 'single',
isSelectionSingle: true,
isSelectionMultiple: false,
setValue,
}, children: children }));
}

@@ -44,21 +62,24 @@ function SelectionMultipleProvider({ value: valueProp, defaultValue, onChange: onChangeProp, children, }) {

}), [setValue]);
return (_jsx(SelectionContext.Provider, { value: { value, onChange, selection: 'multiple', isSelectionSingle: false, isSelectionMultiple: true, setValue }, children: children }));
return (_jsx(SelectionContext.Provider, { value: {
value,
onChange,
mode: 'multiple',
isSelectionSingle: false,
isSelectionMultiple: true,
setValue,
}, children: children }));
}
export function SelectionProvider(props) {
export function SelectionProvider(_a) {
var { children } = _a, props = __rest(_a, ["children"]);
if (isSelectionSingleProps(props)) {
return _jsx(SelectionSingleProvider, Object.assign({}, props));
return _jsx(SelectionSingleProvider, Object.assign({}, props, { children: children }));
}
if (isSelectionMultipleProps(props)) {
return _jsx(SelectionMultipleProvider, Object.assign({}, props));
return _jsx(SelectionMultipleProvider, Object.assign({}, props, { children: children }));
}
return _jsx(SelectionContext.Provider, { value: {}, children: props.children });
return _jsx(SelectionContext.Provider, { value: {}, children: children });
}
export const useSelectionContext = () => useContext(SelectionContext);
export function extractSelectionProps({ selection, value, defaultValue, onChange, }) {
return {
selection,
value,
defaultValue,
onChange,
};
export function extractSelectionProps({ selection }) {
return Object.assign({}, (selection !== null && selection !== void 0 ? selection : {}));
}
import { DroplistProps } from '../types';
export declare function Droplist({ items: itemsProp, search, pinBottom, pinTop, footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, ...props }: DroplistProps): import("react/jsx-runtime").JSX.Element;
export declare function Droplist({ items: itemsProp, search, pinBottom, pinTop, footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, collapse, ...props }: DroplistProps): import("react/jsx-runtime").JSX.Element;

@@ -13,3 +13,3 @@ var __rest = (this && this.__rest) || function (s, e) {

import { jsx as _jsx } from "react/jsx-runtime";
import { cloneElement, isValidElement, useCallback, useMemo, useRef, useState } from 'react';
import { cloneElement, isValidElement, useCallback, useMemo, useRef } from 'react';
import { useUncontrolledProp } from 'uncontrollable';

@@ -23,6 +23,12 @@ import { Dropdown } from '@snack-uikit/dropdown';

export function Droplist(_a) {
var { items: itemsProp, search, pinBottom, pinTop, footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange } = _a, props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs", "children", "trigger", "placement", "widthStrategy", "triggerElemRef", "open", "onOpenChange"]);
var _b;
var { items: itemsProp, search, pinBottom, pinTop, footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, collapse = {} } = _a, props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs", "children", "trigger", "placement", "widthStrategy", "triggerElemRef", "open", "onOpenChange", "collapse"]);
const hasSearch = useMemo(() => Boolean(search), [search]);
const memorizedItems = useMemo(() => addItemsIds((pinBottom !== null && pinBottom !== void 0 ? pinBottom : []).concat(itemsProp).concat(pinTop !== null && pinTop !== void 0 ? pinTop : [])), [itemsProp, pinBottom, pinTop]);
const [openCollapsedItems, setOpenCollapsedItems] = useState([]);
const [openCollapsedItems, setOpenCollapsedItems] = useUncontrolledProp(collapse.value, (_b = collapse.defaultValue) !== null && _b !== void 0 ? _b : [], collapse.onChange
? cb => {
var _a;
(_a = collapse.onChange) === null || _a === void 0 ? void 0 : _a.call(collapse, cb(collapse.value));
}
: undefined);
const { search: searchItem, footerRefs } = useItemsWithIds({

@@ -93,4 +99,4 @@ search: hasSearch,

parentResetActiveFocusIndex: resetActiveFocusIndex,
toggleOpenCollapsedItems: id => setOpenCollapsedItems(items => items.includes(id) ? items.filter(item => item !== id) : items.concat([id])),
}, children: _jsx(Dropdown, Object.assign({ content: _jsx(ListPrivate, Object.assign({ onKeyDown: handleListKeyDown, tabIndex: 0, ref: listRef, search: search }, slicedItems, props)), trigger: trigger, placement: placement, widthStrategy: widthStrategy }, (triggerElemRefProp
toggleOpenCollapsedItems: id => setOpenCollapsedItems((items = []) => items.includes(id) ? items.filter(item => item !== id) : items === null || items === void 0 ? void 0 : items.concat([id])),
}, children: _jsx(Dropdown, Object.assign({ content: _jsx(ListPrivate, Object.assign({ onKeyDown: handleListKeyDown, tabIndex: 0, ref: listRef, search: search, limitedScrollHeight: true }, slicedItems, props)), trigger: trigger, placement: placement, widthStrategy: widthStrategy }, (triggerElemRefProp
? {}

@@ -97,0 +103,0 @@ : {

@@ -1,2 +0,2 @@

/// <reference types="react" />
import { KeyboardEvent } from 'react';
export declare const List: import("react").ForwardRefExoticComponent<{

@@ -14,2 +14,10 @@ 'data-test-id'?: string | undefined;

noResults?: string | undefined;
} & Pick<import("../contexts").SelectionProviderProps, "value" | "defaultValue" | "selection" | "onChange"> & import("../contexts").ListContextType & import("../../../types").ScrollProps & import("react").RefAttributes<HTMLElement>>;
tabIndex?: number | undefined;
collapse?: {
value?: (string | number)[] | undefined;
onChange?(value: (string | number)[]): void;
defaultValue?: (string | number)[] | undefined;
} | undefined;
className?: string | undefined;
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
} & import("../contexts").SelectionState & import("../contexts").ListContextType & import("../../../types").ScrollProps & import("react").RefAttributes<HTMLElement>>;

@@ -13,3 +13,6 @@ var __rest = (this && this.__rest) || function (s, e) {

import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { forwardRef, useMemo, useRef, useState } from 'react';
import cn from 'classnames';
import mergeRefs from 'merge-refs';
import { forwardRef, useMemo, useRef } from 'react';
import { useUncontrolledProp } from 'uncontrollable';
import { HiddenTabButton } from '../../../helperComponents';

@@ -22,7 +25,13 @@ import { extractItemIds, extractItemRefs, withCollapsedItems } from '../../../utils';

import styles from '../styles.module.css';
export const List = forwardRef((_a) => {
var { items: itemsProp, search, pinBottom, pinTop, footerActiveElementsRefs } = _a, props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs"]);
export const List = forwardRef((_a, ref) => {
var _b;
var { items: itemsProp, search, pinBottom, pinTop, footerActiveElementsRefs, onKeyDown, tabIndex = 0, className, collapse = {} } = _a, props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs", "onKeyDown", "tabIndex", "className", "collapse"]);
const hasSearch = useMemo(() => Boolean(search), [search]);
const memorizedItems = useMemo(() => addItemsIds((pinTop !== null && pinTop !== void 0 ? pinTop : []).concat(itemsProp).concat(pinBottom !== null && pinBottom !== void 0 ? pinBottom : [])), [itemsProp, pinBottom, pinTop]);
const [openCollapsedItems, setOpenCollapsedItems] = useState([]);
const [openCollapsedItems, setOpenCollapsedItems] = useUncontrolledProp(collapse.value, (_b = collapse.defaultValue) !== null && _b !== void 0 ? _b : [], collapse.onChange
? cb => {
var _a;
(_a = collapse.onChange) === null || _a === void 0 ? void 0 : _a.call(collapse, cb(collapse.value));
}
: undefined);
const { search: searchItem, footerRefs } = useItemsWithIds({

@@ -57,2 +66,12 @@ search: hasSearch,

const isActive = listRef.current === document.activeElement && activeFocusIndex === -1 && openNestedIndex === -1;
const mergedHandlerKeyDown = (e) => {
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(e);
handleListKeyDown === null || handleListKeyDown === void 0 ? void 0 : handleListKeyDown(e);
};
const handleOnFocus = (e) => {
if (e.relatedTarget === null ||
(e.relatedTarget && itemRefs.every(({ current }) => current !== e.relatedTarget))) {
resetActiveFocusIndex();
}
};
return (

@@ -73,9 +92,4 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment

openCollapsedItems,
toggleOpenCollapsedItems: id => setOpenCollapsedItems(items => items.includes(id) ? items.filter(item => item !== id) : items.concat([id])),
}, children: _jsxs("div", { className: styles.wrapper, "data-active": isActive || undefined, children: [_jsx(ListPrivate, Object.assign({}, props, slicedItems, { ref: listRef, onFocus: e => {
if (e.relatedTarget === null ||
(e.relatedTarget && itemRefs.every(({ current }) => current !== e.relatedTarget))) {
resetActiveFocusIndex();
}
}, onKeyDown: handleListKeyDown, tabIndex: 0, search: search, nested: false })), _jsx(HiddenTabButton, { ref: btnRef, listRef: listRef })] }) }) })));
toggleOpenCollapsedItems: id => setOpenCollapsedItems((items = []) => items.includes(id) ? items.filter(item => item !== id) : items === null || items === void 0 ? void 0 : items.concat([id])),
}, children: _jsxs("div", { className: cn(styles.wrapper, className), "data-active": isActive || undefined, children: [_jsx(ListPrivate, Object.assign({}, props, slicedItems, { ref: mergeRefs(ref, listRef), onFocus: handleOnFocus, onKeyDown: mergedHandlerKeyDown, tabIndex: tabIndex, search: search, nested: false })), _jsx(HiddenTabButton, { ref: btnRef, listRef: listRef, tabIndex: tabIndex })] }) }) })));
});

@@ -14,3 +14,11 @@ import { RefObject } from 'react';

noResults?: string | undefined;
} & Pick<import("../contexts").SelectionProviderProps, "value" | "defaultValue" | "selection" | "onChange"> & import("../contexts").ListContextType & import("../../../types").ScrollProps & {
tabIndex?: number | undefined;
collapse?: {
value?: (string | number)[] | undefined;
onChange?(value: (string | number)[]): void;
defaultValue?: (string | number)[] | undefined;
} | undefined;
className?: string | undefined;
onKeyDown?(e: import("react").KeyboardEvent<HTMLElement>): void;
} & import("../contexts").SelectionState & import("../contexts").ListContextType & import("../../../types").ScrollProps & {
nested?: boolean | undefined;

@@ -22,3 +30,3 @@ active?: boolean | undefined;

onKeyDown?(e: import("react").KeyboardEvent<HTMLElement>): void;
collapse?: "single" | "multiple" | undefined;
limitedScrollHeight?: boolean | undefined;
} & import("react").RefAttributes<HTMLElement>>;

@@ -12,3 +12,3 @@ var __rest = (this && this.__rest) || function (s, e) {

};
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import cn from 'classnames';

@@ -26,3 +26,3 @@ import { forwardRef, useMemo } from 'react';

var _b, _c;
var { items, pinTop, pinBottom, collapse = 'multiple', onKeyDown, onBlur, onFocus, tabIndex, active, scroll, nested, search, scrollRef, scrollContainerRef, footer, loading, noData = 'No data', noResults = 'No results', size = 's', marker } = _a, props = __rest(_a, ["items", "pinTop", "pinBottom", "collapse", "onKeyDown", "onBlur", "onFocus", "tabIndex", "active", "scroll", "nested", "search", "scrollRef", "scrollContainerRef", "footer", "loading", "noData", "noResults", "size", "marker"]);
var { items, pinTop, pinBottom, onKeyDown, onBlur, onFocus, tabIndex, active, scroll, nested, search, scrollRef, scrollContainerRef, footer, loading, noData = 'No data', noResults = 'No results', size = 's', marker, limitedScrollHeight, className } = _a, props = __rest(_a, ["items", "pinTop", "pinBottom", "onKeyDown", "onBlur", "onFocus", "tabIndex", "active", "scroll", "nested", "search", "scrollRef", "scrollContainerRef", "footer", "loading", "noData", "noResults", "size", "marker", "limitedScrollHeight", "className"]);
const itemsJSX = useRenderItems(items);

@@ -33,8 +33,8 @@ const itemsPinTopJSX = useRenderItems(pinTop !== null && pinTop !== void 0 ? pinTop : []);

const loadingJSX = useMemo(() => loading && (_jsx("div", { role: 'spinbutton', tabIndex: -1, className: styles.loader, "data-size": size, "data-no-items": hasNoItems || undefined, "data-test-id": 'list__loader', children: _jsx(Spinner, { size: size === 'l' ? 's' : 'xs' }) })), [hasNoItems, loading, size]);
const content = useMemo(() => (_jsxs(_Fragment, { children: [itemsJSX, loadingJSX, hasNoItems && !(search === null || search === void 0 ? void 0 : search.value) && !loading && (_jsx("div", { className: commonStyles.infoBlock, "data-test-id": 'list__no-data', children: noData })), hasNoItems && (search === null || search === void 0 ? void 0 : search.value) && !loading && (_jsx("div", { className: commonStyles.infoBlock, "data-test-id": 'list__no-results', children: noResults }))] })), [hasNoItems, itemsJSX, loading, loadingJSX, noData, noResults, search === null || search === void 0 ? void 0 : search.value]);
const listJSX = (_jsx("ul", Object.assign({ className: commonStyles.listContainer, ref: ref, onKeyDown: onKeyDown, tabIndex: tabIndex, onFocus: !nested ? onFocus : undefined, onBlur: onBlur, "data-active": active || undefined, role: 'menu' }, extractSupportProps(props), { children: _jsxs(ToggleGroup, { selectionMode: collapse, children: [(Number(pinTop === null || pinTop === void 0 ? void 0 : pinTop.length) > 0 || search) && (_jsxs(PinTopGroupItem, { children: [search && _jsx(SearchItem, { search: search }), Number(pinTop === null || pinTop === void 0 ? void 0 : pinTop.length) > 0 && itemsPinTopJSX] })), scroll ? (_jsxs(Scroll, { className: cn({
[commonStyles.scrollContainerS]: scroll && size === 's',
[commonStyles.scrollContainerM]: scroll && size === 'm',
[commonStyles.scrollContainerL]: scroll && size === 'l',
}), barHideStrategy: 'never', size: 's', ref: scrollContainerRef, children: [content, _jsx("div", { className: styles.scrollStub, ref: scrollRef })] })) : (_jsx(_Fragment, { children: content })), (Number(pinBottom === null || pinBottom === void 0 ? void 0 : pinBottom.length) > 0 || footer) && (_jsxs(PinBottomGroupItem, { children: [Number(pinBottom === null || pinBottom === void 0 ? void 0 : pinBottom.length) > 0 && itemsPinBottomJSX, footer && _jsx("div", { className: styles.footer, children: footer })] }))] }) })));
const content = useMemo(() => (_jsxs("div", { className: styles.content, children: [itemsJSX, loadingJSX, hasNoItems && !(search === null || search === void 0 ? void 0 : search.value) && !loading && (_jsx("div", { className: commonStyles.infoBlock, "data-test-id": 'list__no-data', children: noData })), hasNoItems && (search === null || search === void 0 ? void 0 : search.value) && !loading && (_jsx("div", { className: commonStyles.infoBlock, "data-test-id": 'list__no-results', children: noResults }))] })), [hasNoItems, itemsJSX, loading, loadingJSX, noData, noResults, search === null || search === void 0 ? void 0 : search.value]);
const listJSX = (_jsx("ul", Object.assign({ className: cn(commonStyles.listContainer, className), ref: ref, onKeyDown: onKeyDown, tabIndex: tabIndex, onFocus: !nested ? onFocus : undefined, onBlur: onBlur, "data-active": active || undefined, role: 'menu' }, extractSupportProps(props), { children: _jsxs(ToggleGroup, { selectionMode: 'multiple', children: [(Number(pinTop === null || pinTop === void 0 ? void 0 : pinTop.length) > 0 || search) && (_jsxs(PinTopGroupItem, { children: [search && _jsx(SearchItem, { search: search }), Number(pinTop === null || pinTop === void 0 ? void 0 : pinTop.length) > 0 && itemsPinTopJSX] })), scroll ? (_jsxs(Scroll, { className: cn({
[commonStyles.scrollContainerS]: scroll && limitedScrollHeight && size === 's',
[commonStyles.scrollContainerM]: scroll && limitedScrollHeight && size === 'm',
[commonStyles.scrollContainerL]: scroll && limitedScrollHeight && size === 'l',
}), barHideStrategy: 'leave', size: size === 's' ? 's' : 'm', ref: scrollContainerRef, children: [content, _jsx("div", { className: styles.scrollStub, ref: scrollRef })] })) : (_jsx(_Fragment, { children: content })), (Number(pinBottom === null || pinBottom === void 0 ? void 0 : pinBottom.length) > 0 || footer) && (_jsxs(PinBottomGroupItem, { children: [Number(pinBottom === null || pinBottom === void 0 ? void 0 : pinBottom.length) > 0 && itemsPinBottomJSX, footer && _jsx("div", { className: styles.footer, children: footer })] }))] }) })));
if (!nested) {

@@ -41,0 +41,0 @@ return (_jsx(ListContextProvider, { size: size, marker: marker, children: listJSX }));

@@ -6,3 +6,8 @@ import { FocusEvent, KeyboardEvent, ReactNode, RefObject } from 'react';

import { ItemProps } from '../Items';
import { ListContextType, SelectionProviderProps } from './contexts';
import { ListContextType, SelectionState } from './contexts';
type CollapseState = {
value?: (string | number)[];
onChange?(value: (string | number)[]): void;
defaultValue?: (string | number)[];
};
export type ListProps = WithSupportProps<{

@@ -15,5 +20,8 @@ /** Основные элементы списка */

pinBottom?: ItemProps[];
/** Кастомизируемый элемент, помещаемый внизу списка */
/**
* Кастомизируемый элемент в конце списка
* @type ReactElement
*/
footer?: ReactNode;
/** Список ссылок на костомные элементы, помещенные в специальную секцию внизу списка */
/** Список ссылок на кастомные элементы, помещенные в специальную секцию внизу списка */
footerActiveElementsRefs?: RefObject<HTMLElement>[];

@@ -28,3 +36,10 @@ /** Настройки поисковой строки */

noResults?: string;
} & Pick<SelectionProviderProps, 'value' | 'defaultValue' | 'onChange' | 'selection'> & ListContextType & ScrollProps>;
/** Tab Index */
tabIndex?: number;
/** Настройки раскрытия элементов */
collapse?: CollapseState;
/** CSS-класс */
className?: string;
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
} & SelectionState & ListContextType & ScrollProps>;
export type DroplistProps = {

@@ -35,3 +50,3 @@ /** Ссылка на элемент-триггер для дроплиста */

children?: ReactNode;
} & Pick<DropdownProps, 'trigger' | 'placement' | 'widthStrategy' | 'open' | 'onOpenChange'> & ListProps;
} & Pick<DropdownProps, 'trigger' | 'placement' | 'widthStrategy' | 'open' | 'onOpenChange'> & Omit<ListProps, 'tabIndex' | 'onKeyDown'>;
export type ListPrivateProps = ListProps & {

@@ -44,3 +59,4 @@ nested?: boolean;

onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
collapse?: 'single' | 'multiple';
limitedScrollHeight?: boolean;
};
export {};
import { RefObject } from 'react';
type HiddenTabButtonProps = {
listRef: RefObject<HTMLElement>;
tabIndex?: number;
};
export declare const HiddenTabButton: import("react").ForwardRefExoticComponent<HiddenTabButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
export {};
import { jsx as _jsx } from "react/jsx-runtime";
import { forwardRef, useCallback } from 'react';
import styles from './styles.module.css';
export const HiddenTabButton = forwardRef(({ listRef }, ref) => {
export const HiddenTabButton = forwardRef(({ listRef, tabIndex }, ref) => {
const handleFocus = useCallback((e) => {

@@ -16,3 +16,3 @@ var _a;

}, []);
return _jsx("button", { "aria-hidden": true, ref: ref, onKeyDown: handleKeyDown, onFocus: handleFocus, className: styles.hiddenBtn });
return (_jsx("button", { "aria-hidden": true, ref: ref, onKeyDown: handleKeyDown, onFocus: handleFocus, className: styles.hiddenBtn, tabIndex: tabIndex }));
});

@@ -9,3 +9,4 @@ import { isAccordionItemProps, isBaseItemProps, isGroupItemProps, isNextListItemProps, } from './components/Items';

var _a;
if ((isBaseItemProps(item) || isNextListItemProps(item) || isAccordionItemProps(item)) && !item.disabled) {
if (((isBaseItemProps(item) && !item.inactive) || isNextListItemProps(item) || isAccordionItemProps(item)) &&
!item.disabled) {
newItems = newItems.concat([item]);

@@ -59,3 +60,6 @@ ids = ids.concat([(_a = item.id) !== null && _a !== void 0 ? _a : '']);

return items
.filter(item => isAccordionItemProps(item) || isNextListItemProps(item) || isGroupItemProps(item) || !item.disabled)
.filter(item => isAccordionItemProps(item) ||
isNextListItemProps(item) ||
isGroupItemProps(item) ||
(isBaseItemProps(item) && !item.disabled && !item.inactive))
.reduce((prev, item) => {

@@ -62,0 +66,0 @@ var _a;

@@ -7,3 +7,3 @@ {

"title": "List",
"version": "0.1.3",
"version": "0.2.0",
"sideEffects": [

@@ -46,5 +46,6 @@ "*.css",

"classnames": "2.5.1",
"merge-refs": "1.2.2",
"uncontrollable": "8.0.4"
},
"gitHead": "0726752996aeaf01f58b892fef581c38c1528ea1"
"gitHead": "07c554af2d1a8128e587ba88d5d36e6da3c8f1ce"
}

@@ -31,6 +31,7 @@ # List

| placement | enum Placement: `"left"`, `"left-start"`, `"left-end"`, `"right"`, `"right-start"`, `"right-end"`, `"top"`, `"top-start"`, `"top-end"`, `"bottom"`, `"bottom-start"`, `"bottom-end"` | top | Положение поповера относительно своего триггера (children). |
| className | `string` | - | CSS-класс |
| pinTop | `ItemProps[]` | - | Элементы списка, закрепленные сверху |
| pinBottom | `ItemProps[]` | - | Элементы списка, закрепленные снизу |
| footer | `ReactNode` | - | Кастомизируемый элемент, помещаемый внизу списка |
| footerActiveElementsRefs | `RefObject<HTMLElement>[]` | - | Список ссылок на костомные элементы, помещенные в специальную секцию внизу списка |
| footer | `ReactElement` | - | Кастомизируемый элемент в конце списка |
| footerActiveElementsRefs | `RefObject<HTMLElement>[]` | - | Список ссылок на кастомные элементы, помещенные в специальную секцию внизу списка |
| search | `SearchState` | - | Настройки поисковой строки |

@@ -40,6 +41,4 @@ | loading | `boolean` | - | Флаг, отвещающий за состояние загрузки списка |

| noResults | `string` | - | Текст для состояния "Отсутсвие результата" при поиске |
| value | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Controlled состояние |
| defaultValue | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Начальное состояние |
| onChange | `((value: any) => void) \| ((value: any) => void)` | - | Controlled обработчик измения состояния |
| selection | "single" \| "multiple" | - | Режим выбора |
| collapse | `CollapseState` | {} | Настройки раскрытия элементов |
| selection | `SelectionSingleState \| SelectionMultipleState` | - | |
| size | "s" \| "m" \| "l" | - | Размер списка |

@@ -57,4 +56,4 @@ | marker | `boolean` | - | Отображать ли маркер у выбранного жлемента списка |

| pinBottom | `ItemProps[]` | - | Элементы списка, закрепленные снизу |
| footer | `ReactNode` | - | Кастомизируемый элемент, помещаемый внизу списка |
| footerActiveElementsRefs | `RefObject<HTMLElement>[]` | - | Список ссылок на костомные элементы, помещенные в специальную секцию внизу списка |
| footer | `ReactElement` | - | Кастомизируемый элемент в конце списка |
| footerActiveElementsRefs | `RefObject<HTMLElement>[]` | - | Список ссылок на кастомные элементы, помещенные в специальную секцию внизу списка |
| search | `SearchState` | - | Настройки поисковой строки |

@@ -64,6 +63,7 @@ | loading | `boolean` | - | Флаг, отвещающий за состояние загрузки списка |

| noResults | `string` | - | Текст для состояния "Отсутсвие результата" при поиске |
| value | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Controlled состояние |
| defaultValue | `SelectionSingleValueType \| SelectionSingleValueType[]` | - | Начальное состояние |
| onChange | `((value: any) => void) \| ((value: any) => void)` | - | Controlled обработчик измения состояния |
| selection | "single" \| "multiple" | - | Режим выбора |
| tabIndex | `number` | - | Tab Index |
| collapse | `CollapseState` | {} | Настройки раскрытия элементов |
| className | `string` | - | CSS-класс |
| onKeyDown | `(e: KeyboardEvent<HTMLElement>) => void` | - | |
| selection | `SelectionSingleState \| SelectionMultipleState` | - | |
| size | "s" \| "m" \| "l" | - | Размер списка |

@@ -70,0 +70,0 @@ | marker | `boolean` | - | Отображать ли маркер у выбранного жлемента списка |

@@ -20,34 +20,56 @@ import { FocusEvent, KeyboardEvent, MouseEvent, ReactNode, RefObject } from 'react';

export type BaseItemProps = WithSupportProps<{
/**
* Слот до основного контента
* @type ReactElement
*/
beforeContent?: ReactNode;
/**
* Слот после основного контента
* @type ReactElement
*/
afterContent?: ReactNode;
/** Основной контент айтема */
content: ItemContentProps;
/** Колбек обработки клика */
onClick?(e: MouseEvent<HTMLButtonElement>): void;
/** Колбек обработки нажатия клавиши */
onKeyDown?(e: KeyboardEvent<HTMLButtonElement>): void;
/** Колбек обработки фокуса */
onFocus?(e: FocusEvent<HTMLButtonElement>): void;
/** Колбек обработки блюра */
onBlur?(e: FocusEvent<HTMLButtonElement>): void;
/** Уникальный идентификатор */
id?: string | number;
/** Флаг неактивности элемента */
disabled?: boolean;
itemRef?: RefObject<HTMLButtonElement>;
}>;
className?: string;
export type SwitchProps = {
/**
* Флаг отображения отключения реакции на любое css состояние (hover/focus и тд)
* <br>
* Так же элемент пропадает из навигации с клавиатуры, и не может быть выбран (selection)
*/
inactive?: boolean;
/**
* Флаг отображения состояния выбранного элемента через switch
*/
switch?: boolean;
};
}>;
type BaseItemsWithoutNonGroupProps = Omit<BaseItemProps, 'switch' | 'inactive'>;
// eslint-disable-next-line no-use-before-define
export type ItemProps = (BaseItemProps & SwitchProps) | AccordionItemProps | NextListItemProps | GroupItemProps;
export type ItemProps = BaseItemProps | AccordionItemProps | NextListItemProps | GroupItemProps;
export type AccordionItemProps = BaseItemProps & {
export type AccordionItemProps = BaseItemsWithoutNonGroupProps & {
items: ItemProps[];
// TODO: add later
// mode?: 'single' | 'multiple';
type: 'collapse';
};
export type NextListItemProps = BaseItemProps & {
export type NextListItemProps = BaseItemsWithoutNonGroupProps & {
items: ItemProps[];

@@ -59,5 +81,5 @@ type: 'next-list';

export type GroupItemProps = SeparatorProps & {
export type GroupItemProps = Omit<SeparatorProps, 'size'> & {
items: ItemProps[];
id?: string | number;
};

@@ -8,4 +8,10 @@ import { FocusEvent, KeyboardEvent, ReactNode, RefObject } from 'react';

import { ItemProps } from '../Items';
import { ListContextType, SelectionProviderProps } from './contexts';
import { ListContextType, SelectionState } from './contexts';
type CollapseState = {
value?: (string | number)[];
onChange?(value: (string | number)[]): void;
defaultValue?: (string | number)[];
};
export type ListProps = WithSupportProps<

@@ -19,8 +25,9 @@ {

pinBottom?: ItemProps[];
/** Кастомизируемый элемент, помещаемый внизу списка */
/**
* Кастомизируемый элемент в конце списка
* @type ReactElement
*/
footer?: ReactNode;
/** Список ссылок на костомные элементы, помещенные в специальную секцию внизу списка */
/** Список ссылок на кастомные элементы, помещенные в специальную секцию внизу списка */
footerActiveElementsRefs?: RefObject<HTMLElement>[];
// TODO: add later
// collapse?: 'single' | 'multiple';
/** Настройки поисковой строки */

@@ -34,3 +41,10 @@ search?: SearchState;

noResults?: string;
} & Pick<SelectionProviderProps, 'value' | 'defaultValue' | 'onChange' | 'selection'> &
/** Tab Index */
tabIndex?: number;
/** Настройки раскрытия элементов */
collapse?: CollapseState;
/** CSS-класс */
className?: string;
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
} & SelectionState &
ListContextType &

@@ -46,3 +60,3 @@ ScrollProps

} & Pick<DropdownProps, 'trigger' | 'placement' | 'widthStrategy' | 'open' | 'onOpenChange'> &
ListProps;
Omit<ListProps, 'tabIndex' | 'onKeyDown'>;

@@ -56,4 +70,3 @@ export type ListPrivateProps = ListProps & {

onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
// TODO: remove later
collapse?: 'single' | 'multiple';
limitedScrollHeight?: boolean;
};

@@ -23,3 +23,6 @@ import { RefObject } from 'react';

items.forEach(item => {
if ((isBaseItemProps(item) || isNextListItemProps(item) || isAccordionItemProps(item)) && !item.disabled) {
if (
((isBaseItemProps(item) && !item.inactive) || isNextListItemProps(item) || isAccordionItemProps(item)) &&
!item.disabled
) {
newItems = newItems.concat([item]);

@@ -92,3 +95,9 @@ ids = ids.concat([item.id ?? '']);

return items
.filter(item => isAccordionItemProps(item) || isNextListItemProps(item) || isGroupItemProps(item) || !item.disabled)
.filter(
item =>
isAccordionItemProps(item) ||
isNextListItemProps(item) ||
isGroupItemProps(item) ||
(isBaseItemProps(item) && !item.disabled && !item.inactive),
)
.reduce(

@@ -95,0 +104,0 @@ (prev: Array<string | number>, item: ItemProps) => {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc