Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ jobs:
steps:
- uses: actions/checkout@v2
# Setup .npmrc file to publish to GitHub Packages
- uses: actions/setup-node@v2
- uses: actions/setup-node@v4
with:
node-version: '18.x'
registry-url: 'https://npm.pkg.github.com'
- run: |
echo "@internxt:registry=https://npm.pkg.github.com/" > .npmrc
echo "//npm.pkg.github.com/:_authToken=${{ secrets.PERSONAL_ACCESS_TOKEN }}" >> .npmrc
# Install all necessary dependencies
- run: yarn
env:
Expand Down
13 changes: 5 additions & 8 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,18 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x]
node-version: [20.x]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- run: echo "registry=https://registry.yarnpkg.com/" > .npmrc
- run: echo "@internxt:registry=https://npm.pkg.github.com" >> .npmrc
# You cannot read packages from other private repos with GITHUB_TOKEN
# You have to use a PAT instead https://github.com/actions/setup-node/issues/49
- run: echo //npm.pkg.github.com/:_authToken=${{ secrets.PERSONAL_ACCESS_TOKEN }} >> .npmrc
- run: echo "always-auth=true" >> .npmrc
- run: |
echo "@internxt:registry=https://npm.pkg.github.com/" > .npmrc
echo "//npm.pkg.github.com/:_authToken=${{ secrets.PERSONAL_ACCESS_TOKEN }}" >> .npmrc

- name: Install dependencies
run: yarn
Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "@internxt/ui",
"version": "0.0.7",
"version": "0.0.8",
"description": "Library of Internxt components",
"repository": {
"type": "git",
"url": "git+https://github.com/internxt/InternxtUI.git"
"url": "git+https://github.com/internxt/ui.git"
},
"author": "",
"license": "MIT",
"homepage": "https://github.com/internxt/InternxtUI#readme",
"homepage": "https://github.com/internxt/ui#readme",
"type": "module",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
Expand All @@ -24,7 +24,6 @@
},
"devDependencies": {
"@chromatic-com/storybook": "^1.2.25",
"@internxt/css-config": "^1.0.2",
"@internxt/eslint-config-internxt": "^1.0.9",
"@internxt/prettier-config": "^1.0.2",
"@storybook/addon-essentials": "^8.0.4",
Expand Down Expand Up @@ -59,6 +58,8 @@
"prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.12",
"react": "^18.2.0",
"react-dnd": "14.0.5",
"react-dnd-html5-backend": "^14.0.0",
"sass": "^1.72.0",
"storybook": "^8.0.4",
"tailwindcss": "^3.4.1",
Expand Down Expand Up @@ -88,11 +89,10 @@
"storybook:build": "storybook build"
},
"dependencies": {
"@internxt/css-config": "^1.0.2",
"@phosphor-icons/react": "^2.1.5",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/themes": "^3.0.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1"
"@radix-ui/themes": "^3.0.0"
},
"lint-staged": {
"*.{ts,tsx}": [
Expand Down
13 changes: 12 additions & 1 deletion src/components/contextMenu/ContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface ContextMenuProps<T> {
menuItemsRef: React.MutableRefObject<HTMLDivElement | null>;
menu?: MenuItemsType<T>;
openedFromRightClick: boolean;
isOpen: boolean;
posX: number;
posY: number;
isContextMenuCutOff: boolean;
Expand Down Expand Up @@ -34,6 +35,9 @@ export interface ContextMenuProps<T> {
* - Indicates whether the context menu was opened via a right-click (`true`).
* Determines whether the menu's position is set based on the click location or a predefined position.
*
* @property {boolean} [isOpen]
* - To know is Menu is visible.
*
* @property {number} posX
* - X-coordinate for the menu's position, used if `openedFromRightClick` is `true`.
*
Expand All @@ -60,6 +64,7 @@ const ContextMenu = <T,>({
posX,
posY,
isContextMenuCutOff,
isOpen,
genericEnterKey,
handleMenuClose,
}: ContextMenuProps<T>): JSX.Element => {
Expand All @@ -78,7 +83,13 @@ const ContextMenu = <T,>({
}
ref={menuItemsRef}
>
<Menu item={item} genericEnterKey={genericEnterKey} handleMenuClose={handleMenuClose} menu={menu} />
<Menu
item={item}
isOpen={isOpen}
genericEnterKey={genericEnterKey}
handleMenuClose={handleMenuClose}
menu={menu}
/>
</div>
);
};
Expand Down
1 change: 1 addition & 0 deletions src/components/contextMenu/__test__/ContextMenu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('ContextMenu Component', () => {
posX: 100,
posY: 100,
isContextMenuCutOff: false,
isOpen: true,
genericEnterKey: vi.fn(),
handleMenuClose: vi.fn(),
};
Expand Down
23 changes: 20 additions & 3 deletions src/components/dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, ReactNode } from 'react';
import { useState, ReactNode, useEffect, useRef } from 'react';
import { Menu, MenuItemType } from '../';

export type DropdownProps<T> = {
Expand Down Expand Up @@ -56,6 +56,23 @@ const Dropdown = <T,>({
}: DropdownProps<T>): JSX.Element => {
const [isOpen, setIsOpen] = useState(false);
const direction = openDirection === 'left' ? 'origin-top-left' : 'origin-top-right';
const containerRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
setIsOpen(false);
}
};

document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('contextmenu', handleClickOutside);

return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('contextmenu', handleClickOutside);
};
}, []);

const group1: Array<MenuItemType<T>> = options
? options.map((option) => ({
Expand All @@ -78,7 +95,7 @@ const Dropdown = <T,>({
const closeMenu = () => setIsOpen(false);

return (
<div className="relative outline-none">
<div className="relative outline-none" ref={containerRef}>
<button
className={`cursor-pointer outline-none ${classButton}`}
onClick={toggleMenu}
Expand All @@ -96,7 +113,7 @@ const Dropdown = <T,>({
data-testid="menu-dropdown"
>
<div className={`absolute ${classMenuItems}`}>
<Menu item={item} handleMenuClose={closeMenu} menu={allItems} />
<Menu item={item} isOpen={isOpen} handleMenuClose={closeMenu} menu={allItems} />
</div>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from './modal';
export * from './popover';
export * from './radioButton';
export * from './slider';
export * from './skeletonLoader';
export * from './switch';
export * from './textArea';
export * from './tooltip';
4 changes: 3 additions & 1 deletion src/components/list/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ const List = <T extends { id: number }, F extends keyof T>({
};

const onOrderableColumnClicked = (field: HeaderProps<T, F>) => {
onCloseContextMenu();
if (!field.orderable || !onOrderByChanged) return;

const columnWasAlreadySelected = orderBy?.field === field.name;
Expand Down Expand Up @@ -307,6 +308,7 @@ const List = <T extends { id: number }, F extends keyof T>({
displayMenuDiv={displayMenuDiv}
isVerticalScrollbarVisible={isVerticalScrollbarVisible}
checkboxDataCy={checkboxDataCy}
onClose={onCloseContextMenu}
/>
) : null}

Expand All @@ -318,7 +320,7 @@ const List = <T extends { id: number }, F extends keyof T>({
<InfiniteScroll
handleNextPage={handleNextPage}
hasMoreItems={!!hasMoreItems}
loader={<></>}
loader={loader}
scrollableTarget="scrollableList"
>
{items.map((item, index) => (
Expand Down
4 changes: 3 additions & 1 deletion src/components/list/ListHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface ListHeaderProps<T, F> {
checkboxDataCy?: string;
onTopSelectionCheckboxClick: () => void;
onOrderableColumnClicked: (column: HeaderProps<T, F>) => void;
onClose?: () => void;
}

const ListHeader = <T, F extends keyof T>({
Expand All @@ -34,9 +35,10 @@ const ListHeader = <T, F extends keyof T>({
displayMenuDiv,
isVerticalScrollbarVisible,
checkboxDataCy,
onClose,
}: ListHeaderProps<T, F>) => {
return (
<div className="flex h-12 shrink-0 flex-row px-5">
<div onClick={onClose} onContextMenu={onClose} className="flex h-12 shrink-0 flex-row px-5">
{/* COLUMN */}
<div className="flex h-full min-w-full flex-row items-center border-b border-gray-10">
{/* SELECTION CHECKBOX */}
Expand Down
1 change: 1 addition & 0 deletions src/components/list/ListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ const ListItem = <T extends { id: number }>({
menu={menu}
menuItemsRef={menuItemsRef}
openedFromRightClick={openedFromRightClick}
isOpen={isOpen}
posX={posX}
posY={posY}
isContextMenuCutOff={isContextMenuCutOff}
Expand Down
34 changes: 23 additions & 11 deletions src/components/menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReactNode, useEffect, useState } from 'react';
import { isValidElement, ReactNode, useEffect, useState } from 'react';
import useHotkeys from '../../hooks/useHotKeys';

export type MenuItemType<T> =
Expand All @@ -23,6 +23,7 @@ export type MenuItemsType<T> = Array<MenuItemType<T>>;

export interface MenuProps<T> {
item?: T;
isOpen: boolean;
menu?: MenuItemsType<T>;
handleMenuClose: () => void;
genericEnterKey?: () => void;
Expand All @@ -37,6 +38,9 @@ export interface MenuProps<T> {
* @property {T} [item]
* - Optional item that may be used in menu actions (e.g., data passed for actions).
*
* @property {boolean} [isOpen]
* - To know is Menu is visible.
*
* @property {MenuItemsType<T>} [menu]
* - Optional array of menu items. Each item can define a separator, title, icon, action, etc.
*
Expand All @@ -58,7 +62,7 @@ export interface MenuProps<T> {
* It features a dynamic index for item selection, with keyboard and mouse-based navigation.
*/

const Menu = <T,>({ item, menu, genericEnterKey, handleMenuClose }: MenuProps<T>): JSX.Element => {
const Menu = <T,>({ item, menu, isOpen, genericEnterKey, handleMenuClose }: MenuProps<T>): JSX.Element => {
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
const [enterPressed, setEnterPressed] = useState<boolean>(false);
const handleMouseEnter = (index: number) => {
Expand All @@ -70,6 +74,7 @@ const Menu = <T,>({ item, menu, genericEnterKey, handleMenuClose }: MenuProps<T>

const handleArrowDown = () => {
menu &&
isOpen &&
setSelectedIndex((prevIndex) => {
const getNextEnabledIndex = (startIndex: number): number => {
let newIndex = startIndex;
Expand All @@ -91,6 +96,7 @@ const Menu = <T,>({ item, menu, genericEnterKey, handleMenuClose }: MenuProps<T>

const handleArrowUp = () => {
menu &&
isOpen &&
setSelectedIndex((prevIndex) => {
const getPreviousEnabledIndex = (startIndex: number): number => {
let newIndex = startIndex;
Expand All @@ -111,14 +117,20 @@ const Menu = <T,>({ item, menu, genericEnterKey, handleMenuClose }: MenuProps<T>
};

const handleEnterKey = () => {
setSelectedIndex((prevIndex) => {
if (prevIndex !== null) {
const menuItem = menu ? menu[prevIndex] : undefined;
if (item && menuItem && 'action' in menuItem && menuItem.action) menuItem.action(item);
} else if (genericEnterKey) genericEnterKey();
setEnterPressed(true);
return null;
});
menu &&
isOpen &&
setSelectedIndex((prevIndex) => {
if (prevIndex !== null) {
const menuItem = menu ? menu[prevIndex] : undefined;
if (item && menuItem && 'action' in menuItem && menuItem.action) menuItem.action(item);
if (item && menuItem && 'node' in menuItem && menuItem.node && isValidElement(menuItem.node)) {
const onClick = menuItem.node.props.onClick;
onClick && onClick();
}
} else if (genericEnterKey) genericEnterKey();
setEnterPressed(true);
return null;
});
};

useEffect(() => {
Expand All @@ -134,7 +146,7 @@ const Menu = <T,>({ item, menu, genericEnterKey, handleMenuClose }: MenuProps<T>
arrowup: handleArrowUp,
enter: handleEnterKey,
},
[],
[isOpen],
);

return (
Expand Down
1 change: 1 addition & 0 deletions src/components/menu/__test__/Menu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe('Menu Component', () => {
const defaultProps: MenuProps<{ id: number; name: string }> = {
item: { id: 1, name: 'Sample Item' },
menu: menuItems,
isOpen: true,
handleMenuClose,
genericEnterKey,
};
Expand Down
6 changes: 6 additions & 0 deletions src/hooks/useHotKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ type KeyMap = { [key: string]: () => void };

const useHotkeys = (keyMap: KeyMap, dependencies: unknown[] = []) => {
const handleKeyDown = (event: KeyboardEvent) => {
const isInputElement = ['input', 'textarea'].includes(document.activeElement?.tagName.toLowerCase() ?? '');

if (isInputElement && event.key.toLowerCase() !== 'escape') {
return;
}

const keyCombination = `${event.ctrlKey ? 'ctrl+' : ''}${event.metaKey ? 'meta+' : ''}${event.key.toLowerCase()}`;
if (keyMap[keyCombination]) {
event.preventDefault();
Expand Down
1 change: 1 addition & 0 deletions src/stories/components/breadcrumbs/breadcrumbs.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const menuBreadcrumbs = (props: BreadcrumbsMenuProps): JSX.Element => {
<Menu
item={{ id: 1, name: 'Sample Item' }}
handleMenuClose={handleClick}
isOpen={false}
menu={[
{ name: 'Title', isTitle: () => true },
{ separator: true },
Expand Down
1 change: 1 addition & 0 deletions src/stories/components/contextMenu/ContextMenu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const ContextMenuWithNotifications = () => {
isContextMenuCutOff={false}
genericEnterKey={() => {}}
handleMenuClose={() => {}}
isOpen={true}
/>
</div>
</div>
Expand Down
13 changes: 11 additions & 2 deletions src/stories/components/dropdown/Dropdown.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,18 @@ const defaultArgs: DropdownProps<unknown> = {
{ text: 'Option 1', onClick: () => alert('Option 1 selected') },
{ text: 'Option 2', onClick: () => alert('Option 2 selected') },
],
menuItems: ['Item 1', 'Item 2'],
menuItems: [
<div onClick={() => alert('Item 1 selected')}>{'Item 1'}</div>,
<div onClick={() => alert('Item 2 selected')}>{'Item 2'}</div>,
],
dropdownActionsContext: [
{ name: 'Action 1', action: () => alert('Launched action 1') },
{
name: 'Action 1',
action: () => {
console.log('llamada');
alert('Launched action 1');
},
},
{ name: 'Action 2', action: () => alert('Launched action 2') },
{ separator: true },
{ name: 'Action 3', action: () => alert('Launched action 3') },
Expand Down
Loading
Loading