Skip to content

Shadcn multi-select component in sandbox reactive editor #1575

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

Merged
merged 8 commits into from
Apr 10, 2025
Merged

Conversation

myieye
Copy link
Contributor

@myieye myieye commented Apr 3, 2025

Resolves #1580

  • Popover for desktop
  • Drawer for mobile
  • Back button closes drawer (on desktop at least, I haven't tested mobile, which is where it should 😬)
    • I built this into the drawer component for mobile-sized screens
  • Always a visible "Dismiss" button
  • The "X" behaves differently on desktop and mobile
    • On mobile I think it's especially common for an X by the input to clear the input, so I went with that. But there's always a "Cancel" button
    • On Desktop the "X" closes the popup, BUT it dissappears when in "dirty" state. For 2 reasons: (1) so there aren't 2 close/dismiss buttons so close to each other and (2) I think it slightly helps communicate the fact that the popup is in a different state (it's non-dismissable: can only be closed via Cancel or Submit).
localhost_5137_sandbox.-.Google.Chrome.2025-04-07.16-44-50.mp4

Updates:

  • Badges/header in mobile drawer so the user has context:
    image

  • keyboard navigation

    • Ctrl + Space to toggle selection of an item
    • No tab navigation through checkboxes, because it's weird having 2 ways to navigate the command-item list
    • Ctrl + Enter submits the selection
  • A sort strategy can be specified for the selected values

  • Moved the submit button on desktop to inside the filter input, so there isn't a layout shift and it's nice and visible.
    image

  • Readonly mode
    image

Copy link

coderabbitai bot commented Apr 3, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

This pull request updates various aspects of the project. The dependency version for bits-ui is fixed to 1.3.16, and CSS grid layouts have been adjusted with new and removed classes. Several new Svelte components for field titles and multi-select functionality have been added while existing UI components (button, checkbox, command input, drawer) receive enhancements and additional properties. Furthermore, type safety improvements are made in the entry editor, and sandbox/view configurations are extended with new state variables and metadata. Lastly, the project dropdown behavior is modified to no longer auto-close upon selection.

Changes

File(s) Change Summary
frontend/viewer/package.json Updated bits-ui version from ^1.3.10 to fixed 1.3.16.
frontend/viewer/src/app.postcss Modified grid layout classes: removed .xs-form, added .lg-form, updated .editor-grid, and introduced new classes (.field-root, .field-title, .field-body).
frontend/viewer/src/lib/components/field-editors/… Added new Svelte components: field-title.svelte (for field titles with i18n and help icons) and multi-select.svelte (multi-select dropdown with filtering and badges).
frontend/viewer/src/lib/components/ui/… Enhanced UI components: in button.svelte added an 'xs-icon' variant; in checkbox.svelte changed class from size-4 to size-full; in command-input.svelte added a children prop; in drawer-content.svelte added a handle prop; in drawer.svelte integrated history and mobile back handling.
frontend/viewer/src/lib/entry-editor/… Improved type safety: updated FieldTitle.svelte imports and added type declarations in field-data.ts, replacing ambiguous types with stricter definitions (FieldId vs. FieldIds).
frontend/viewer/src/lib/sandbox/… and frontend/viewer/src/lib/views/view-data.ts Extended sandbox and view configurations: added type properties to view objects, introduced new state variables in Sandbox.svelte (including semantic domain management), and defined new view constants (FW_LITE_VIEW and FW_CLASSIC_VIEW).
frontend/viewer/src/project/ProjectDropdown.svelte Removed the automatic dropdown closure on selection by eliminating the open = false; line in the handler.

Poem

I'm a hoppy coder rabbit,
Skipping through grids and fields so bright,
New components and tweaks in every byte,
With carrots of code and a playful delight,
I nibble on changes from morning till night!


🪧 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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @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.

Copy link

github-actions bot commented Apr 3, 2025

UI unit Tests

12 tests   12 ✅  0s ⏱️
 4 suites   0 💤
 1 files     0 ❌

Results for commit 443e710.

♻️ This comment has been updated with latest results.

Copy link

github-actions bot commented Apr 3, 2025

C# Unit Tests

112 tests  ±0   112 ✅ ±0   6s ⏱️ ±0s
 17 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 443e710. ± Comparison against base commit fc1a798.

♻️ This comment has been updated with latest results.

@myieye myieye marked this pull request as ready for review April 8, 2025 15:32
Copy link

@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: 1

🧹 Nitpick comments (6)
frontend/viewer/src/lib/entry-editor/field-data.ts (1)

33-36: Note the deprecation of FieldIds type

The FieldIds type is now properly marked as deprecated with a clear migration path to use the more type-safe FieldId type. Consider updating usages of FieldIds across the codebase to use FieldId in future PRs.

frontend/viewer/src/lib/components/field-editors/field-title.svelte (1)

30-37: Consider simplifying the word splitting logic

The current implementation for handling the last word separation is a bit complex and introduces extra processing. Consider using CSS to handle the spacing between the label and help icon instead.

- // kind of crazy, but I don't think Svelte's white-space handling let's us use &nbsp; between the label and help icon
- const { lastWord, otherWords } = $derived.by(() => {
-   const words = label.split(' ');
-   return {
-     lastWord: words.pop(),
-     otherWords: words.join(' '),
-   };
- })

And then update the template section to:

<span class="name" {title}>
  {label}
  {#if helpId}
    <span class="inline-block ml-1"><FieldHelpIcon {helpId} /></span>
  {/if}
</span>
frontend/viewer/src/lib/views/view-data.ts (1)

42-49: Consider using a getter for alternateView consistency

While FW_CLASSIC_VIEW works as implemented, for consistency with FW_LITE_VIEW, consider using a getter for the alternateView property here as well.

-  alternateView: FW_LITE_VIEW,
+  get alternateView() { return FW_LITE_VIEW; }
frontend/viewer/src/lib/components/field-editors/multi-select.svelte (2)

144-149: Handle cases where no item is highlighted.
The helper function getHighlightedValue() can return undefined if no item is highlighted. While this is handled gracefully in your tryToggleHighlightedValue() method, consider logging or providing a user hint if toggling fails, particularly for accessibility or discoverability.


237-239: Fix typographical error in comment.
“deault” should be “default.”

- // prevents deault command item selection
+ // prevents default command item selection
frontend/viewer/src/lib/sandbox/Sandbox.svelte (1)

89-91: Verify randomness approach for shuffling lists.
Using return Math.random() - 0.5 inside a custom comparison function can produce random ordering, though it lacks uniformity and stability. If you need a more robust shuffle, consider implementing Fisher–Yates or a similar algorithm.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f0fd8f7 and 9fceeea.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • frontend/viewer/package.json (1 hunks)
  • frontend/viewer/src/app.postcss (1 hunks)
  • frontend/viewer/src/lib/components/field-editors/field-title.svelte (1 hunks)
  • frontend/viewer/src/lib/components/field-editors/multi-select.svelte (1 hunks)
  • frontend/viewer/src/lib/components/ui/button/button.svelte (1 hunks)
  • frontend/viewer/src/lib/components/ui/checkbox/checkbox.svelte (1 hunks)
  • frontend/viewer/src/lib/components/ui/command/command-input.svelte (2 hunks)
  • frontend/viewer/src/lib/components/ui/drawer/drawer-content.svelte (2 hunks)
  • frontend/viewer/src/lib/components/ui/drawer/drawer.svelte (2 hunks)
  • frontend/viewer/src/lib/entry-editor/FieldTitle.svelte (1 hunks)
  • frontend/viewer/src/lib/entry-editor/field-data.ts (1 hunks)
  • frontend/viewer/src/lib/sandbox/OptionSandbox.svelte (2 hunks)
  • frontend/viewer/src/lib/sandbox/Sandbox.svelte (7 hunks)
  • frontend/viewer/src/lib/views/view-data.ts (2 hunks)
  • frontend/viewer/src/project/ProjectDropdown.svelte (0 hunks)
💤 Files with no reviewable changes (1)
  • frontend/viewer/src/project/ProjectDropdown.svelte
🧰 Additional context used
🧬 Code Definitions (1)
frontend/viewer/src/lib/views/view-data.ts (1)
frontend/viewer/src/lib/entry-editor/field-data.ts (1)
  • FieldIds (36-36)
🔇 Additional comments (27)
frontend/viewer/src/lib/components/ui/checkbox/checkbox.svelte (1)

26-26: Improved icon container sizing

Changing from fixed size to size-full ensures the icon properly fills and centers within its container, creating better visual alignment.

frontend/viewer/src/lib/components/ui/command/command-input.svelte (1)

10-10: Enhanced component flexibility with children support

The addition of the children property and its conditional rendering allows the command input component to display additional content, which is essential for the multi-select implementation. The optional chaining ensures graceful handling when no children are provided.

Also applies to: 26-26

frontend/viewer/src/lib/components/ui/button/button.svelte (1)

24-24: Added compact icon button variant

The new xs-icon size variant provides a smaller button option (8x8 units vs 10x10 for regular icon buttons), which is useful for compact UI elements like clear buttons in the multi-select component. This addition enhances the component library's flexibility while maintaining consistent sizing patterns.

frontend/viewer/package.json (1)

51-51: Fixed bits-ui dependency version for stability

Locking the bits-ui version to exactly 1.3.16 (removing the caret from ^1.3.10) ensures consistent behavior across all environments. This prevents automatic updates to newer minor versions that might introduce breaking changes, particularly important for the UI components being modified in this PR.

frontend/viewer/src/lib/components/ui/drawer/drawer-content.svelte (2)

10-10: Good enhancement - adds flexibility to the drawer component

Adding an optional handle prop with a default value of true maintains backward compatibility while allowing customization when needed.

Also applies to: 15-15


29-31: Proper implementation of conditional handle rendering

The conditional rendering of the handle element aligns well with the PR objectives, supporting the responsive design approach for the multi-select component.

frontend/viewer/src/lib/entry-editor/FieldTitle.svelte (1)

5-5: Good type safety improvement

The import of the FieldId type and the type casting in the reactive statement enhances type safety by ensuring id is treated as a valid field identifier when accessing fieldData.

Also applies to: 11-11

frontend/viewer/src/lib/sandbox/OptionSandbox.svelte (1)

33-34: Proper view type differentiation

Adding type properties to distinguish between 'fw-classic' and 'fw-lite' views supports the responsive design approach mentioned in the PR objectives, enabling different component behaviors for desktop and mobile users.

Also applies to: 44-45

frontend/viewer/src/lib/entry-editor/field-data.ts (1)

30-31: Excellent type safety improvement

Creating a FieldId type from the keys of privateFieldData and using it to type the fieldData export ensures that only valid field identifiers can be used at compile time.

frontend/viewer/src/lib/components/field-editors/field-title.svelte (3)

1-5: Clean imports with well-defined dependencies

The imports are appropriately organized, importing only what's needed for the component's functionality: translation utilities, help icon component, and view service.


6-14: Props with proper type definitions

The component defines clear prop types using TypeScript, ensuring type safety for the component inputs. The use of $props() follows Svelte 5 conventions.


40-52: Clear and semantic markup structure

The component uses a clean and semantic structure with appropriate class names. The inline-flex approach ensures proper alignment of the text and icon.

frontend/viewer/src/lib/views/view-data.ts (3)

33-40: Good use of constants and getter for circular reference prevention

The FW_LITE_VIEW constant is well-defined with the new type property, and the use of a getter for alternateView cleverly prevents circular reference issues that would occur with direct assignments.


55-65: Good reuse of view constants in the views array

Using the defined constants in the views array and spreading FW_LITE_VIEW into custom views is a clean approach that ensures consistency across view definitions.


91-96: Type safety improvement with union type

Adding the type property to the ViewDefinition interface with a specific union type improves type safety and makes view type distinctions explicit in the codebase.

frontend/viewer/src/lib/components/ui/drawer/drawer.svelte (4)

2-4: Mobile detection integration

Good addition of the mobile detection hook and necessary imports to enhance drawer behavior across different devices.


14-20: Reactive drawer state handling

The use of the watch function to react to open state changes is a clean implementation. This ensures proper handling of browser history when the drawer opens or closes.


22-36: Browser history management for mobile UX

The implementation intelligently handles browser history only on mobile devices, ensuring back button behavior works as expected without affecting desktop navigation.


38-52: Proper cleanup with AbortController and lifecycle hooks

The use of AbortController with onMount and onDestroy ensures proper cleanup of event listeners and history state, preventing memory leaks and unexpected navigation behavior.

frontend/viewer/src/app.postcss (3)

94-96: New lg-form grid template introduction

The new .lg-form class provides a wider layout option with more space for field labels, enhancing the responsive design system.


98-103: Enhanced responsive grid layout

The updated .editor-grid class now applies different grid layouts at different breakpoints, which aligns with the PR's focus on responsive design for both desktop and mobile users.


105-121: Well-structured field component CSS classes

The new field-related classes (.field-root, .field-title, .field-body) create a consistent and responsive layout system for field components. The use of subgrid ensures proper alignment across nested grids.

frontend/viewer/src/lib/components/field-editors/multi-select.svelte (3)

18-34: Confirm $bindable() usage for Svelte compatibility.
It appears that these lines rely on non-standard Svelte reactivity helpers, particularly $bindable(). Please verify that integrating this feature works correctly in your build environment and does not conflict with standard Svelte patterns or any bundling plugins.


254-286: Mobile and desktop integration looks solid.
Your Drawer-based approach for mobile users, alongside the Popover for desktop, is a well-structured solution. The distinct flows for “Dismiss” vs. “Submit” align with the design goals, and the toggled states (dirty, open) appear to be managed cleanly.


102-122:

✅ Verification successful

Consider avoiding in-place array manipulation for reactive updates.
Calling pendingValues.splice(index, 1); mutates the array in-place. While it may work with $state or certain store libraries, in many reactive contexts, splicing can sometimes lead to stale reactivity. To ensure consistent behavior, consider creating a new array instead:

- if (index !== -1) pendingValues.splice(index, 1);
+ if (index !== -1) {
+   pendingValues = [
+     ...pendingValues.slice(0, index),
+     ...pendingValues.slice(index + 1)
+   ];
+ }

Action: Update reactive removal in multi-select.svelte to avoid in-place mutation

In the toggleSelected function, while adding values a new array is created to trigger reactivity, the removal branch uses in-place mutation with splice, which may not update the UI as expected. Instead, reassign a new array after removal to ensure consistent reactive updates. For example, replace:

- if (index !== -1) pendingValues.splice(index, 1);
+ if (index !== -1) {
+   pendingValues = [
+     ...pendingValues.slice(0, index),
+     ...pendingValues.slice(index + 1)
+   ];
+ }

This change aligns with Svelte’s reactive principles and avoids potential stale reactivity issues.

frontend/viewer/src/lib/sandbox/Sandbox.svelte (2)

155-156: Confirm two-way binding approach for values.
Your usage of bind:values={() => selectedDomains, (newValues) => selectedDomains = newValues} is unconventional compared to standard Svelte’s bind:values={variable}. Verify that this syntax works as expected with your chosen reactivity library or store.


2-32: Imports and layout adjustments look good.
These additions integrate new UI components (e.g., FieldTitle, ThemePicker, Tabs, Switch) and reorganize the layout. No major issues are apparent, and the approach seems consistent with the rest of the codebase.

Also applies to: 25-31, 102-104, 115-117, 127-139, 142-142, 146-149, 152-166, 202-202, 206-206, 282-289

@hahn-kev
Copy link
Collaborator

hahn-kev commented Apr 9, 2025

Looks great! One issue I noticed in dark mode there's a bg-popover on the command which in dark mode looks like this on the mobile view:
image

notice the grey background only under the command area. I think it would look fine without that background:
image

or if there was no rounding on the corners:
image

but right now it feels cramped with the background having rounded corners.

Otherwise it looks great! Nice work

Copy link
Collaborator

@hahn-kev hahn-kev left a comment

Choose a reason for hiding this comment

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

Looks good, I'd like to fix the weird background in dark mode before this is merged but I'm happy to approve it.

@myieye
Copy link
Contributor Author

myieye commented Apr 9, 2025

Regarding the weird background color:
It turns out that only for green and rose in dark mode --popover != --background.
That sort of seems like an oversight, so I opted to solve the problem by changing --popover in those 2 themes.

@myieye myieye merged commit 80a3be6 into develop Apr 10, 2025
23 checks passed
@myieye myieye deleted the shadcn-editor branch April 10, 2025 07:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Shadcn Multi-select
2 participants