nextui icon indicating copy to clipboard operation
nextui copied to clipboard

[BUG] - ButtonGroup styles affects nested children

Open devlzcode opened this issue 2 years ago • 8 comments

NextUI Version

2.2.9

Describe the bug

Buttons nested deeply in a ButtonGroup are still styled as a ButtonGroup.

e.g.

<ButtonGroup>
  <Button onPress={onOpen}>Open Modal<Button>
  <Modal isOpen={isOpen} onOpenChange={onOpenChange}>
    <ModalContent>
      <ModalFooter>
        {/* These buttons will be styled as if they were in a ButtonGroup */}
        <Button>Close</Button>
        <Button>Submit</Button>
      </ModalFooter>
    </ModalContent>
  </Modal>
</ButtonGroup>

Your Example Website or App

No response

Steps to Reproduce the Bug or Issue

  1. Create a button group
  2. Create a modal and place the trigger button and the modal component within the button group
  3. Create buttons in the modal

Expected behavior

I expect that the buttons are styled normally, e.g. not connected as if it were a button group.

Screenshots or Videos

image image

Operating System Version

macOS

Browser

Chrome

devlzcode avatar Dec 18 '23 00:12 devlzcode

I do not think this is a bug. You can do like this: image

thought7878 avatar Jan 06 '24 07:01 thought7878

I do not think this is a bug.

You can do like this:

image

I should update the example to be more comprehensive then. I have two modals, A & B. Both files look like this:

export const Trigger = () => (
  <Button>Open Modal</Button>
  <Modal>...</Modal>
)

And used like so:

<ButtonGroup>
  <TriggerA/>
  <TriggerB/>
</ButtonGroup>

I hope that clears it up.

devlzcode avatar Jan 06 '24 07:01 devlzcode

Having the same issue. Would appreciate a workaround

badmagick329 avatar Apr 05 '24 00:04 badmagick329

Issue is still there on heroui, would like a fix 🙏 I think only direct children buttons of buttongroup should be impacted by buttongroup

AntoineArt avatar Jan 30 '25 15:01 AntoineArt

@AntoineArt can you provide your code as well? Also it shouldn't Modal in ButtonGroup.

wingkwong avatar Jan 31 '25 17:01 wingkwong

@AntoineArt can you provide your code as well? Also it shouldn't Modal in ButtonGroup.

I don't use this library anymore (moved to using headless UI libraries like ariakit & rac) but if the modal is rendered inside of portal as a child of the body then the styles shouldn't affect it. So you should check for that.

devlzcode avatar Jan 31 '25 23:01 devlzcode

@AntoineArt can you provide your code as well? Also it shouldn't Modal in ButtonGroup.

Hi, here is a minimal working version :

'use client';

import {
  Button,
  ButtonGroup,
  Input,
  Modal,
  ModalBody,
  ModalContent,
  ModalFooter,
  ModalHeader,
  Table,
  TableBody,
  TableCell,
  TableColumn,
  TableHeader,
  TableRow,
} from '@heroui/react';

import React from 'react';

export const Content = () => {
  return (
    <div>
      <EmployeesTableDemo />
    </div>
  );
};

// Simplified interfaces
interface IUserProfile {
  id: string;
  first_name: string;
  last_name: string;
  email: string;
  phone_number?: string;
  is_active: boolean;
}

interface EmployeeDetailsModalProps {
  data?: IUserProfile;
  tooltipText?: string;
  button: React.ReactNode;
  modalTitle: string;
}

// Sample data
const MOCK_USERS: IUserProfile[] = [
  {
    id: '1',
    first_name: 'John',
    last_name: 'Doe',
    email: '[email protected]',
    phone_number: '1234567890',
    is_active: true,
  },
  {
    id: '2',
    first_name: 'Jane',
    last_name: 'Smith',
    email: '[email protected]',
    is_active: true,
  },
];

const COLUMNS = [
  { name: 'Name', uid: 'name' },
  { name: 'Email', uid: 'email' },
  { name: 'Phone', uid: 'phone_number' },
  { name: 'Actions', uid: 'actions' },
];

function EmployeeDetailsModal({ data, tooltipText, button, modalTitle }: EmployeeDetailsModalProps) {
  const [isOpen, setIsOpen] = React.useState(false);

  return (
    <>
      {React.cloneElement(button as React.ReactElement, {
        onPress: () => setIsOpen(true),
      })}
      <Modal
        isOpen={isOpen}
        onOpenChange={setIsOpen}
        size='2xl'
      >
        <ModalContent>
          {(onClose) => (
            <>
              <ModalHeader>{modalTitle}</ModalHeader>
              <ModalBody>
                <Input
                  label='First Name'
                  value={data?.first_name}
                  variant='bordered'
                />
                <Input
                  label='Last Name'
                  value={data?.last_name}
                  variant='bordered'
                />
                <Input
                  label='Email'
                  value={data?.email}
                  variant='bordered'
                />
                <Input
                  label='Phone'
                  value={data?.phone_number}
                  variant='bordered'
                />
              </ModalBody>
              <ModalFooter>
                <Button
                  color='danger'
                  variant='light'
                  onPress={onClose}
                >
                  Cancel
                </Button>
                <Button
                  color='primary'
                  onPress={onClose}
                >
                  Save Changes
                </Button>
              </ModalFooter>
            </>
          )}
        </ModalContent>
      </Modal>
    </>
  );
}

export default function EmployeesTableDemo() {
  const [users] = React.useState<IUserProfile[]>(MOCK_USERS);

  const handleDelete = (id: string) => {
    console.log('Delete user:', id);
  };

  const RenderCell = ({ user, columnKey }: { user: IUserProfile; columnKey: string }) => {
    switch (columnKey) {
      case 'name':
        return `${user.first_name} ${user.last_name}`;
      case 'actions':
        return (
          <ButtonGroup
            variant='bordered'
            size='sm'
          >
            <EmployeeDetailsModal
              data={user}
              tooltipText='Edit user details'
              modalTitle={`Edit ${user.first_name} ${user.last_name}`}
              button={
                <Button
                // size='sm'
                // variant='bordered'
                >
                  Edit
                </Button>
              }
            />
            <Button
              color='danger'
              onPress={() => handleDelete(user.id)}
            >
              Delete
            </Button>
          </ButtonGroup>
        );
      default:
        return user[columnKey as keyof IUserProfile] || <i>Not provided</i>;
    }
  };

  return (
    <div className='p-4'>
      <div className='mb-4'>
        <EmployeeDetailsModal
          tooltipText='Add new employee'
          modalTitle='Add New Employee'
          button={<Button color='primary'>Add Employee</Button>}
        />
      </div>

      <Table aria-label='Employees table'>
        <TableHeader>
          {COLUMNS.map((column) => (
            <TableColumn key={column.uid}>{column.name}</TableColumn>
          ))}
        </TableHeader>
        <TableBody>
          {users.map((user) => (
            <TableRow key={user.id}>
              {COLUMNS.map((column) => (
                <TableCell key={`${user.id}-${column.uid}`}>
                  <RenderCell
                    user={user}
                    columnKey={column.uid}
                  />
                </TableCell>
              ))}
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </div>
  );
}

You can see that if modal's trigger button is in a buttongroup (edit button in table), children buttons are also styled as if in a buttongroup.

It is probably due to my button being a prop, but I need it in order to reuse my modal at several places.

Image

Image

AntoineArt avatar Feb 04 '25 10:02 AntoineArt

👀

Cierra-Runis avatar Oct 10 '25 12:10 Cierra-Runis