Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(SelectableCards): new component thumbnail variant #1445

Merged
merged 10 commits into from
Dec 10, 2024
Merged

Conversation

marcinsawicki
Copy link
Contributor

@marcinsawicki marcinsawicki commented Nov 28, 2024

Resolves: #1347

Description

New SelectableCard component with dedicated components based on it:

  • ThumbnailSelectableCard
  • GallerySelectableCard
  • InteractiveSelectableCard with small grid system (GridWrapper, GridRow, GridCol)

Docs page, unit tests, stories representing each variant.

Storybook

https://feature-1347--613a8e945a5665003a05113b.chromatic.com/?path=/docs/components-selectablecard--docs

Checklist

Obligatory:

  • Self review (use this as your final check for proposed changes before requesting the review)
  • Add correct label
  • Assign pull request with the correct issue

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced style prop for the Avatar component, allowing custom styling.
    • Added new SelectableCard component with options for radio and checkbox selections.
    • Launched GallerySelectableCard and ThumbnailSelectableCard components for enhanced card selection interfaces.
    • Created InteractiveSelectableCard for interactive card functionality with customizable content.
    • Introduced GridWrapper, GridRow, and GridCol components for responsive layouts.
    • Added new useInteractive hook for enhanced click handling.
  • Documentation

    • Added comprehensive documentation for the SelectableCard and its variations in Storybook.
  • Tests

    • Implemented unit tests for all new components to ensure functionality and reliability.

Copy link
Contributor

coderabbitai bot commented Nov 28, 2024

Walkthrough

The pull request introduces several new components and enhancements to the SelectableCard feature set in the React components library. Key additions include the SelectableCard, ThumbnailSelectableCard, and GallerySelectableCard components, each designed to support selectable card interfaces with radio and checkbox functionality. The Avatar component is also updated to accept a custom style prop for enhanced styling flexibility. Documentation and tests for the new components have been added, ensuring comprehensive coverage and usability.

Changes

File Change Summary
packages/react-components/src/components/Avatar/Avatar.tsx Added optional style property to AvatarProps interface.
packages/react-components/src/components/SelectableCard/*.tsx Introduced SelectableCard, ThumbnailSelectableCard, GallerySelectableCard components.
packages/react-components/src/components/SelectableCard/*.mdx Added documentation for SelectableCard components in Storybook.
packages/react-components/src/components/SelectableCard/*.scss Created SCSS styles for SelectableCard and its variations.
packages/react-components/src/components/SelectableCard/*.spec.tsx Added unit tests for SelectableCard and its variations.
packages/react-components/src/hooks/*.ts Introduced useInteractive hook for handling interactive elements.
packages/react-components/src/index.ts Exported SelectableCard and its variations for public access.
packages/react-components/src/utils/types.ts Modified ComponentCoreProps interface to reorder properties.

Assessment against linked issues

Objective Addressed Explanation
New selectable card component
Documentation for selectable cards
Support for radio and checkbox selection types
Custom styling options for components

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 191c1de and 0882426.

📒 Files selected for processing (1)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.spec.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.spec.tsx

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@marcinsawicki marcinsawicki linked an issue Nov 28, 2024 that may be closed by this pull request
@marcinsawicki marcinsawicki added the feature New feature or request label Nov 28, 2024
@marcinsawicki marcinsawicki added this to the Cycle #10 milestone Nov 28, 2024
@marcinsawicki marcinsawicki self-assigned this Nov 28, 2024
@marcinsawicki marcinsawicki marked this pull request as ready for review December 6, 2024 13:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 22

🧹 Outside diff range and nitpick comments (27)
packages/react-components/src/components/SelectableCard/index.ts (1)

2-9: Consider restructuring grid components and fix import path.

  1. Grid components (GridWrapper, GridCol, GridRow) seem tightly coupled with InteractiveSelectableCard. Consider moving them to a separate module if they're meant to be reusable.
  2. Remove the trailing slash from the import path.
export {
  ThumbnailSelectableCard,
  GallerySelectableCard,
  InteractiveSelectableCard,
  GridWrapper,
  GridCol,
  GridRow,
-} from './components/';
+} from './components';
packages/react-components/src/components/Avatar/Avatar.tsx (1)

Line range hint 148-148: Consider documenting style precedence

The style merging logic ({ ...backgroundStyle, ...style }) means custom styles will override background styles. Consider documenting this behavior in the props interface.

   /**
    * Custom style for the avatar
+   * Note: Custom styles will override default background styles
    */
   style?: React.CSSProperties;
packages/react-components/src/hooks/types.ts (1)

20-28: Consider strengthening the MouseEvent type safety

The MouseEvent type could be more specific by using a union type of allowed HTML elements instead of the generic HTMLElement.

-  onClick: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
+  onClick: (e: React.MouseEvent<HTMLDivElement | HTMLSpanElement, MouseEvent>) => void;
packages/react-components/src/hooks/useInteractive.ts (1)

5-5: Expand NON_INTERACTIVE_TAGS list

Consider adding other common interactive elements: 'label', 'summary', 'details'.

-const NON_INTERACTIVE_TAGS = ['input', 'button', 'select', 'textarea', 'a'];
+const NON_INTERACTIVE_TAGS = ['input', 'button', 'select', 'textarea', 'a', 'label', 'summary', 'details'];
packages/react-components/src/components/SelectableCard/SelectableCard.module.scss (1)

64-67: Use spacing variables for heights

Replace fixed height values with design system spacing variables.

-    height: 21px;
+    height: var(--spacing-6);

     &__checkbox {
-      height: 16px;
+      height: var(--spacing-5);
     }
packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveGrid/InteractiveGrid.tsx (1)

15-15: Use classnames library for safer class composition

Current className concatenation could lead to extra spaces and potential undefined classes.

+import classNames from 'classnames';
// ...
-<div className={`${styles[gridWrapperBaseClass]} ${className || ''}`}>
+<div className={classNames(styles?.[gridWrapperBaseClass], className)}>
packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/types.ts (1)

3-4: Document the purpose of empty interface extension

Consider adding JSDoc comments explaining why this interface exists if it doesn't add new properties.

packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/ThumbnailSelectableCard.module.scss (1)

8-10: Consider using CSS variable for icon height

Replace the fixed height with a CSS variable for better maintainability and flexibility.

  &__icon {
    display: flex;
-   height: 21px;
+   height: var(--thumbnail-icon-height, 21px);
  }
packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/types.ts (1)

5-18: LGTM! Consider enhancing prop descriptions.

The interface is well-structured and properly extends the core props. Consider adding examples in the JSDoc comments for better developer experience.

   /**
    * The label of the card
+   * @example "Gallery Item 1"
    */
   label?: string;
packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveGrid/InteractiveGrid.module.scss (1)

5-21: LGTM! Consider adding responsive behavior.

The grid implementation is clean and follows best practices. Consider adding media queries for responsive gap adjustments on smaller screens.

 .#{$grid-wrapper-base-class} {
   display: flex;
   flex-wrap: wrap;
   gap: var(--spacing-5) var(--spacing-16);
+  @media (max-width: 768px) {
+    gap: var(--spacing-4) var(--spacing-8);
+  }
 }
packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/types.ts (1)

5-23: Consider creating shared types for common props.

The interface is well-defined, but there's prop overlap with GallerySelectableCard. Consider extracting common props (label, icon, customElement) into a shared interface.

+interface ICommonCardProps {
+  label: string;
+  icon?: React.ReactNode;
+  customElement?: React.ReactNode;
+}
+
-export interface IThumbnailSelectableCardProps extends ISelectableCardCoreProps {
+export interface IThumbnailSelectableCardProps extends ISelectableCardCoreProps, ICommonCardProps {
-  label: string;
   description?: string;
-  icon?: React.ReactNode;
-  customElement?: React.ReactNode;
 }
packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.tsx (1)

13-17: Add prop type validation for children

Consider adding a more specific type for children to prevent invalid content.

export const InteractiveSelectableCard: FC<IInteractiveSelectableCardProps> = ({
  children,
  contentClassName,
  ...props
-}) => {
+}: {
+  children: ReactNode;
+  contentClassName?: string;
+}) => {
packages/react-components/src/components/SelectableCard/types.ts (1)

28-28: Add default kind type

Consider specifying a default kind in the type to match component's default behavior.

-  kind?: 'thumbnail' | 'gallery' | 'interactive';
+  kind?: 'thumbnail' | 'gallery' | 'interactive' | 'default';
packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.stories.tsx (2)

3-7: Consider using absolute imports

Replace relative imports (../../../../stories/components/StoryDescriptor) with absolute imports for better maintainability.


18-38: Enhance story coverage

Consider adding examples for:

  • Disabled state
  • Error state
  • Selected state
  • Different sizes
packages/react-components/src/components/SelectableCard/SelectableCard.stories.css (1)

23-24: Avoid fixed dimensions

Consider using CSS custom properties or relative units for width/height to improve component flexibility.

packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/ThumbnailSelectableCard.tsx (1)

14-21: Add prop validation for required props

Consider adding prop-types or TypeScript required markers for essential props like label.

packages/react-components/src/components/SelectableCard/SelectableCard.stories.tsx (1)

30-58: Enhance story maintainability

  1. Extract selection logic into a custom hook
  2. Move inline styles to CSS module
  3. Use array mapping for cards instead of repetition
export const Default = () => {
  const [selectedIndex, setSelectedIndex] = React.useState<number>(1);
+  const cards = ['Default selectable card 1', 'Default selectable card 2', 'Default selectable card 3'];

  return (
-    <div style={{ display: 'flex', gap: 8 }}>
+    <div className={styles.cardContainer}>
+      {cards.map((text, index) => (
+        <SelectableCard
+          key={index}
+          selectionType="radio"
+          isSelected={selectedIndex === index + 1}
+          onClick={() => setSelectedIndex(index + 1)}
+        >
+          {text}
+        </SelectableCard>
+      ))}
    </div>
  );
};
packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/ThumbnailSelectableCard.stories.tsx (1)

18-50: Reduce duplication and enhance story coverage

  1. Create a shared story template for both radio and checkbox examples
  2. Add interaction tests using play functions
const Template = (selectionType: 'radio' | 'checkbox') => (
  <>
    <StoryDescriptor title="With Label">
      <Cards selectionType={selectionType} />
    </StoryDescriptor>
    {/* ... other variants ... */}
  </>
);

export const RadioTypeExamples = () => Template('radio');
export const CheckboxTypeExamples = () => Template('checkbox');
packages/react-components/src/components/SelectableCard/SelectableCard.tsx (1)

39-48: Simplify keyboard event handling

Multiple spacebar key checks can be simplified.

-    if (
-      e.key === 'Enter' ||
-      e.key === ' ' ||
-      e.key === 'Spacebar' ||
-      e.key === 'Space'
-    ) {
+    if (e.key === 'Enter' || e.key === ' ') {
       e.preventDefault();
       onClick?.();
     }
packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.spec.tsx (1)

19-69: Add test coverage for edge cases

Consider adding tests for:

  1. Error handling scenarios
  2. Accessibility attributes
  3. Keyboard interactions

Example test cases to add:

it('should handle keyboard interactions', () => {
  const onClick = vi.fn();
  const { getByRole } = renderComponent({
    ...DEFAULT_PROPS,
    onClick,
  });
  
  const button = getByRole('button');
  fireEvent.keyDown(button, { key: 'Enter' });
  expect(onClick).toHaveBeenCalled();
});

it('should have proper aria attributes', () => {
  const { getByRole } = renderComponent({
    ...DEFAULT_PROPS,
    label: 'Test Label',
  });
  
  const button = getByRole('button');
  expect(button).toHaveAttribute('aria-selected', 'false');
  expect(button).toHaveAttribute('aria-label', 'Test Label');
});
packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.components.tsx (2)

21-45: Refactor getComponent for better maintainability

Extract custom element to a separate component and avoid inline styles. Consider using styled-components or CSS modules.

+ const CustomElement = ({ icon: Icon, index }: { icon: IconComponent, index: number }) => (
+   <StyledCustomElement>
+     <Icon size="small" />
+     <div>{`Custom element ${index + 1}`}</div>
+   </StyledCustomElement>
+ );

76-91: Optimize selection logic using Set

Current array operations can be inefficient for large datasets.

- const [selectedIndex, setSelectedIndex] = React.useState<number[]>([]);
+ const [selectedIndices, setSelectedIndices] = React.useState<Set<number>>(new Set());

  const handleCardClick = (index: number) => {
-   if (selectedIndex.includes(index)) {
-     setSelectedIndex(selectedIndex.filter((selected) => selected !== index));
+   setSelectedIndices(prev => {
+     const next = new Set(prev);
+     if (next.has(index)) {
+       next.delete(index);
+     } else {
+       next.add(index);
+     }
+     return next;
+   });
-   } else {
-     setSelectedIndex([...selectedIndex, index]);
-   }
  };
packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/ThumbnailSelectableCard.components.tsx (1)

1-104: Consider component composition pattern

Create a base SelectableCardGroup component that both ThumbnailSelectableCard and GallerySelectableCard can extend.

This would:

  • Reduce code duplication
  • Improve maintainability
  • Make it easier to add new card variants
packages/react-components/src/components/SelectableCard/SelectableCard.spec.tsx (1)

88-98: Enhance keyboard interaction test coverage

Consider adding tests for space key press and tab+shift navigation for complete keyboard accessibility coverage.

 it('should allow to focus by tab press and call onClick on keyboard interaction', () => {
   const onClick = vi.fn();
   renderComponent({
     ...DEFAULT_PROPS,
     onClick,
   });

   userEvent.tab();
   userEvent.keyboard('{enter}');
+  userEvent.keyboard(' ');
   expect(onClick).toHaveBeenCalledTimes(1);
+  
+  // Test backward navigation
+  userEvent.tab({ shift: true });
+  expect(document.activeElement).not.toBe(getByRole('button'));
 });
packages/react-components/src/components/SelectableCard/SelectableCard.mdx (1)

127-127: Fix grammar in InteractiveGrid description

Correct subject-verb agreement.

-The grid components consists of:
+The grid components consist of:
🧰 Tools
🪛 LanguageTool

[grammar] ~127-~127: The verb form ‘consists’ does not seem to match the subject ‘components’.
Context: ...he UI assumptions. The grid components consists of: - GridWrapper - necessary to use ...

(SUBJECT_VERB_AGREEMENT_PLURAL)

packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.stories.tsx (1)

15-15: Consider moving styles to a component-specific file

The story-specific styles should be colocated with the stories rather than using the parent's CSS file.

-import '../../SelectableCard.stories.css';
+import './InteractiveSelectableCard.stories.css';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6e42a71 and 562c31f.

📒 Files selected for processing (36)
  • packages/react-components/src/components/Avatar/Avatar.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/SelectableCard.mdx (1 hunks)
  • packages/react-components/src/components/SelectableCard/SelectableCard.module.scss (1 hunks)
  • packages/react-components/src/components/SelectableCard/SelectableCard.spec.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/SelectableCard.stories.css (1 hunks)
  • packages/react-components/src/components/SelectableCard/SelectableCard.stories.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/SelectableCard.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.components.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.module.scss (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.spec.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.stories.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/types.ts (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveGrid/InteractiveGrid.module.scss (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveGrid/InteractiveGrid.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveGrid/index.ts (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.components.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.module.scss (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.spec.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.stories.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/types.ts (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/ThumbnailSelectableCard.components.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/ThumbnailSelectableCard.module.scss (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/ThumbnailSelectableCard.spec.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/ThumbnailSelectableCard.stories.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/ThumbnailSelectableCard.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/types.ts (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/index.ts (1 hunks)
  • packages/react-components/src/components/SelectableCard/index.ts (1 hunks)
  • packages/react-components/src/components/SelectableCard/types.ts (1 hunks)
  • packages/react-components/src/hooks/index.ts (1 hunks)
  • packages/react-components/src/hooks/types.ts (2 hunks)
  • packages/react-components/src/hooks/useInteractive.ts (1 hunks)
  • packages/react-components/src/index.ts (1 hunks)
  • packages/react-components/src/utils/types.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveGrid/index.ts
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.module.scss
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.module.scss
🧰 Additional context used
🪛 LanguageTool
packages/react-components/src/components/SelectableCard/SelectableCard.mdx

[grammar] ~127-~127: The verb form ‘consists’ does not seem to match the subject ‘components’.
Context: ...he UI assumptions. The grid components consists of: - GridWrapper - necessary to use ...

(SUBJECT_VERB_AGREEMENT_PLURAL)

🔇 Additional comments (11)
packages/react-components/src/components/SelectableCard/index.ts (1)

1-1: LGTM!

Clean and straightforward export of the base component.

packages/react-components/src/utils/types.ts (1)

7-17: LGTM! Props ordering follows React conventions

The addition of children prop and repositioning of style prop follows React's conventional prop ordering, with children coming first.

packages/react-components/src/components/Avatar/Avatar.tsx (1)

56-59: LGTM! Style prop addition is well documented

The style prop addition follows TypeScript best practices and includes clear documentation.

packages/react-components/src/index.ts (1)

45-45: LGTM! Export follows established patterns

The new SelectableCard export is correctly placed in alphabetical order.

packages/react-components/src/hooks/index.ts (1)

4-4: LGTM!

The export follows the established pattern and maintains consistency with other hook exports.

packages/react-components/src/components/SelectableCard/SelectableCard.module.scss (3)

26-32: LGTM! Consistent spacing and positioning

The thumbnail variant follows design system conventions with proper spacing variables.


55-59: LGTM! Well-handled selected state

Good use of design tokens and proper handling of both normal and hover states.


13-13: Consider container width constraints

Using max-content might cause overflow issues in narrow containers. Consider using a more flexible width strategy.

packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveGrid/InteractiveGrid.tsx (1)

1-37: Document props and verify test coverage

Consider adding:

  1. JSDoc comments for each component
  2. Prop type documentation
  3. Unit tests for className composition and children rendering
packages/react-components/src/components/SelectableCard/components/index.ts (1)

1-8: LGTM! Clean and well-organized exports

The exports are logically grouped and follow a consistent pattern.

packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.tsx (1)

20-23: Review accessibility role usage

The role="presentation" might strip semantic meaning from interactive elements. Consider using role="group" if the div contains interactive elements.

✅ Verification successful

role="presentation" is appropriate here

The role="presentation" is correctly used in this case because:

  • The div is just a styling wrapper
  • The parent SelectableCard component already handles all interactive behavior with proper role="button" and keyboard interactions
  • No semantic meaning is being stripped as the wrapper div contains no interactive elements of its own
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for interactive elements within this component
ast-grep --pattern 'role="presentation"' -A 5

Length of output: 47


Script:

#!/bin/bash
# Let's check the full component implementation to understand the context
ast-grep --pattern 'InteractiveSelectableCard = $$$'

# Also check for any button, input, or other interactive elements
rg -A 10 "InteractiveSelectableCard" packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.tsx

Length of output: 688


Script:

#!/bin/bash
# Let's check what's inside the children prop and the full component content
rg -l "InteractiveSelectableCard" --type tsx
cat packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.tsx

Length of output: 846


Script:

#!/bin/bash
# Let's check the parent SelectableCard component and its implementation
cat packages/react-components/src/components/SelectableCard/SelectableCard.tsx

# Also check where InteractiveSelectableCard is being used
rg -B 2 -A 2 "InteractiveSelectableCard" --type ts --type tsx

Length of output: 1927

packages/react-components/src/index.ts Show resolved Hide resolved
packages/react-components/src/hooks/useInteractive.ts Outdated Show resolved Hide resolved
Comment on lines +20 to +69
describe('<ThumbnailSelectableCard> component', () => {
it('should allow for custom class for card wrapper and content', () => {
const { container, getByRole } = renderComponent({
...DEFAULT_PROPS,
className: 'my-class',
contentClassName: 'my-content-class',
});

expect(container.firstChild).toHaveClass('my-class');
expect(getByRole('presentation')).toHaveClass('my-content-class');
});

it('should render with label', () => {
const { getByText } = renderComponent(DEFAULT_PROPS);

expect(getByText('Label')).toBeInTheDocument();
});

it('should render with description if provided', () => {
const { getByText } = renderComponent({
...DEFAULT_PROPS,
description: 'Description',
});

expect(getByText('Description')).toBeInTheDocument();
});

it('should render with icon if provided', () => {
const { getByRole } = renderComponent({
...DEFAULT_PROPS,
icon: <div role="img" />,
});

expect(getByRole('img')).toBeInTheDocument();
});

it('should not render label and other elements if custom element is provided', () => {
const { getByText, queryByRole, queryByText } = renderComponent({
...DEFAULT_PROPS,
description: 'Description',
icon: <div role="img" />,
customElement: <div>Custom element</div>,
});

expect(queryByRole('img')).not.toBeInTheDocument();
expect(queryByText('Label')).not.toBeInTheDocument();
expect(queryByText('Description')).not.toBeInTheDocument();
expect(getByText('Custom element')).toBeInTheDocument();
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance test coverage for critical functionality

While basic rendering is well covered, the test suite should include:

  • Selection behavior (radio/checkbox modes)
  • Click handler verification
  • Edge cases with null/undefined props
  • Accessibility checks (ARIA attributes, keyboard navigation)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
packages/react-components/src/components/SelectableCard/SelectableCard.mdx (2)

71-71: Fix typo in prop name

customElemnt should be customElement

🧰 Tools
🪛 LanguageTool

[typographical] ~71-~71: The word “otherwise” is an adverb that can’t be used like a conjunction, and therefore needs to be separated from the sentence.
Context: ... provide an icon or customElemnt or label, otherwise the card will be empty. <Canvas of={Ga...

(THUS_SENTENCE)


135-135: Fix grammar in grid components description

Change "The grid components consists of" to "The grid components consist of"

🧰 Tools
🪛 LanguageTool

[grammar] ~135-~135: The verb form ‘consists’ does not seem to match the subject ‘components’.
Context: ...he UI assumptions. The grid components consists of: - GridWrapper - necessary to use ...

(SUBJECT_VERB_AGREEMENT_PLURAL)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 97fd961 and 283e170.

📒 Files selected for processing (1)
  • packages/react-components/src/components/SelectableCard/SelectableCard.mdx (1 hunks)
🧰 Additional context used
🪛 LanguageTool
packages/react-components/src/components/SelectableCard/SelectableCard.mdx

[typographical] ~71-~71: The word “otherwise” is an adverb that can’t be used like a conjunction, and therefore needs to be separated from the sentence.
Context: ... provide an icon or customElemnt or label, otherwise the card will be empty. <Canvas of={Ga...

(THUS_SENTENCE)


[grammar] ~135-~135: The verb form ‘consists’ does not seem to match the subject ‘components’.
Context: ...he UI assumptions. The grid components consists of: - GridWrapper - necessary to use ...

(SUBJECT_VERB_AGREEMENT_PLURAL)

🔇 Additional comments (2)
packages/react-components/src/components/SelectableCard/SelectableCard.mdx (2)

60-61: Fix arrow function syntax in onClick handlers

The arrow function syntax is incorrect in multiple examples.

-  onClick={() = {}}
+  onClick={() => {}}

Also applies to: 86-87, 107-108


1-144: Well-structured documentation with comprehensive examples

The documentation provides clear explanations and examples for all SelectableCard variants. The inclusion of implementation examples and API documentation makes it very useful for developers.

🧰 Tools
🪛 LanguageTool

[typographical] ~71-~71: The word “otherwise” is an adverb that can’t be used like a conjunction, and therefore needs to be separated from the sentence.
Context: ... provide an icon or customElemnt or label, otherwise the card will be empty. <Canvas of={Ga...

(THUS_SENTENCE)


[grammar] ~135-~135: The verb form ‘consists’ does not seem to match the subject ‘components’.
Context: ...he UI assumptions. The grid components consists of: - GridWrapper - necessary to use ...

(SUBJECT_VERB_AGREEMENT_PLURAL)

Copy link

@vladko-uxds vladko-uxds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

@JoannaSikora JoannaSikora left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍🏼

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (5)
packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/types.ts (3)

6-10: Enhance BaseProps documentation

The label property documentation could be more descriptive. Consider adding details about its purpose and usage context.

 /**
- * The label of the card
+ * The text label displayed in the gallery card. This label helps identify
+ * the card's content for users.
  */

17-20: Fix incorrect documentation for never prop

The documentation for customElement is misleading since this prop can never be used in the WithIcon type.

-  /**
-   * The custom element of the card
-   */
+  /** @deprecated This prop cannot be used with WithIcon type */

24-27: Fix incorrect documentation for never prop

The documentation for icon is misleading since this prop can never be used in the WithCustomElement type.

-  /**
-   * The icon of the card
-   */
+  /** @deprecated This prop cannot be used with WithCustomElement type */
packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/ThumbnailSelectableCard.components.tsx (1)

59-69: Make card count configurable

The number of cards is currently hardcoded to 4. Consider adding a prop to make the card count configurable for greater flexibility.

Also applies to: 93-103

packages/react-components/src/components/SelectableCard/SelectableCard.mdx (1)

135-135: Fix grammatical error

Change "The grid components consists of:" to "The grid components consist of:" to match the plural subject.

Apply this diff to correct the grammar:

- The grid components consists of:
+ The grid components consist of:
🧰 Tools
🪛 LanguageTool

[grammar] ~135-~135: The verb form ‘consists’ does not seem to match the subject ‘components’.
Context: ...he UI assumptions. The grid components consists of: - GridWrapper - necessary to use ...

(SUBJECT_VERB_AGREEMENT_PLURAL)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 283e170 and 191c1de.

📒 Files selected for processing (14)
  • packages/react-components/src/components/SelectableCard/SelectableCard.mdx (1 hunks)
  • packages/react-components/src/components/SelectableCard/SelectableCard.module.scss (1 hunks)
  • packages/react-components/src/components/SelectableCard/SelectableCard.stories.css (1 hunks)
  • packages/react-components/src/components/SelectableCard/SelectableCard.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.components.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.module.scss (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.spec.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.stories.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/types.ts (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveGrid/InteractiveGrid.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.stories.tsx (1 hunks)
  • packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/ThumbnailSelectableCard.components.tsx (1 hunks)
  • packages/react-components/src/hooks/useInteractive.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • packages/react-components/src/hooks/useInteractive.ts
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.spec.tsx
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.tsx
  • packages/react-components/src/components/SelectableCard/SelectableCard.tsx
  • packages/react-components/src/components/SelectableCard/SelectableCard.stories.css
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.module.scss
  • packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.stories.tsx
  • packages/react-components/src/components/SelectableCard/SelectableCard.module.scss
  • packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveGrid/InteractiveGrid.tsx
🧰 Additional context used
🪛 LanguageTool
packages/react-components/src/components/SelectableCard/SelectableCard.mdx

[grammar] ~135-~135: The verb form ‘consists’ does not seem to match the subject ‘components’.
Context: ...he UI assumptions. The grid components consists of: - GridWrapper - necessary to use ...

(SUBJECT_VERB_AGREEMENT_PLURAL)

🔇 Additional comments (4)
packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/types.ts (1)

34-35: LGTM! Well-structured type definitions

Excellent use of TypeScript's discriminated union pattern to ensure mutually exclusive props. The type structure effectively prevents invalid prop combinations while maintaining type safety.

packages/react-components/src/components/SelectableCard/components/ThumbnailSelectableCard/ThumbnailSelectableCard.components.tsx (1)

52-70: Refactor shared logic into a custom hook

The RadioCards and CheckboxCards components share similar state management and rendering logic. Extracting this into a custom hook or component will improve code reuse and maintainability.

Also applies to: 78-104

packages/react-components/src/components/SelectableCard/components/GallerySelectableCard/GallerySelectableCard.components.tsx (1)

67-76: Make card count configurable

The card count is hardcoded to 4. Introducing a prop to configure the number of cards can enhance the component's reusability.

Also applies to: 98-107

packages/react-components/src/components/SelectableCard/components/InteractiveSelectableCard/InteractiveSelectableCard.stories.tsx (1)

24-84: Reduce code duplication in story examples

The RadioTypeExamples and CheckboxTypeExamples stories have repetitive code. Extracting shared content into reusable components will improve maintainability.

Also applies to: 86-148

@marcinsawicki marcinsawicki merged commit 56b00a7 into main Dec 10, 2024
6 checks passed
@marcinsawicki marcinsawicki deleted the feature/1347 branch December 10, 2024 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
Development

Successfully merging this pull request may close these issues.

[Selectable cards] - new component
3 participants