Skip to content

Conversation

@noahchoii
Copy link
Contributor

@noahchoii noahchoii commented Nov 20, 2025

Description of Changes

  • Discussions regarding the current component name are ongoing.
  • It is based on the Popover.
  • Since it is only used in the bottom area, the various props of the Positioner are meaningless. However, style overrides may be necessary, so the Positioner component is provided as-is, but it only has the attributes of a div element, excluding custom props like side and align.
  • When the ActionBar opens, focus moves to the focusable element inside it. However, considering it manipulates multiple items, there may be cases where you don't want to immediately shift focus. Since Base UI beta.6 provides this functionality under the name initialFocus, it should be implemented when the version is upgraded.

Checklist

Before submitting the PR, please make sure you have checked all of the following items.

  • The PR title follows the Conventional Commits convention. (e.g., feat, fix, docs, style, refactor, test, chore)
  • I have added tests for my changes.
  • I have updated the Storybook or relevant documentation.
  • I have added a changeset for this change. (e.g., for any changes that affect users, such as component prop changes or new features).
  • I have performed a self-code review.
  • I have followed the project's coding conventions and component patterns.

@changeset-bot
Copy link

changeset-bot bot commented Nov 20, 2025

🦋 Changeset detected

Latest commit: 5d425b9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@vapor-ui/core Minor
figma-plugin Patch
website Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Nov 20, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
vapor-ui Ready Ready Preview Comment Nov 25, 2025 1:47am

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @noahchoii, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new ActionBar component, designed to provide a fixed, bottom-aligned container for primary actions within the UI. Built on the Popover component, it offers a streamlined API tailored for its specific positioning requirements. The addition is accompanied by thorough documentation, interactive Storybook examples, and a clear path for future improvements in focus handling.

Highlights

  • New ActionBar Component: Introduces a new ActionBar component to the @vapor-ui/core library, designed for displaying key actions fixed at the bottom of the screen.
  • Popover-based Implementation: The ActionBar is built upon the existing Popover component from @base-ui-components/react, leveraging its core functionality for overlay management.
  • Simplified Positioning: The Positioner component within ActionBar is adapted to only support bottom-fixed positioning, simplifying its API by excluding irrelevant props like side and align.
  • Comprehensive Documentation & Examples: Includes new documentation pages, detailed prop tables, and Storybook examples demonstrating default, action-rich, and controlled usage of the ActionBar.
  • Future Focus Management: Notes a planned enhancement for focus management using the initialFocus API from Base UI beta.6 once the library version is upgraded.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

새로운 ActionBar 컴포넌트 추가에 대한 PR을 리뷰했습니다. 전반적으로 컴포넌트 구조와 기능 구현이 잘 이루어졌습니다. 문서와 예제 코드도 충실하게 작성되었습니다. 몇 가지 개선점을 제안합니다. 예제 코드의 잠재적인 버그 수정, 스타일 가이드 준수를 위한 displayName 설정 및 상수 타입 강화, 그리고 문서의 명확성 개선에 대한 의견을 남겼습니다. 이 피드백들이 컴포넌트의 완성도를 높이는 데 도움이 되기를 바랍니다.

Comment on lines 30 to 36
const handleItemChange = (itemId: string, isChecked: boolean) => {
setSelectedItems((prev) => ({ ...prev, [itemId]: isChecked }));

const newSelectedCount = isChecked ? selectedCount + 1 : selectedCount - 1;

setOpen(newSelectedCount > 0);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

high

handleItemChange 함수에서 newSelectedCount를 계산할 때 이전 렌더링의 selectedCount 값을 사용하고 있어, 여러 상태 업데이트가 동시에 발생할 때 오래된 상태(stale state)를 참조하는 버그가 발생할 수 있습니다. setSelectedItems는 비동기적으로 동작하기 때문입니다.

안전하게 상태를 업데이트하려면 setSelectedItems의 함수형 업데이트를 사용하여 이전 상태를 기반으로 새 상태를 계산하고, 그 안에서 setOpen을 호출하는 것이 좋습니다.

Suggested change
const handleItemChange = (itemId: string, isChecked: boolean) => {
setSelectedItems((prev) => ({ ...prev, [itemId]: isChecked }));
const newSelectedCount = isChecked ? selectedCount + 1 : selectedCount - 1;
setOpen(newSelectedCount > 0);
};
const handleItemChange = (itemId: string, isChecked: boolean) => {
setSelectedItems((prev) => {
const newSelectedItems = { ...prev, [itemId]: isChecked };
const newSelectedCount = Object.values(newSelectedItems).filter(Boolean).length;
setOpen(newSelectedCount > 0);
return newSelectedItems;
});
};

Comment on lines 105 to 111
"prop": "render",
"type": [
"React.ReactElement",
"(props: PopupProps, state: PopupState) => React.ReactElement"
],
"default": "undefined",
"description": "팝업 요소를 렌더링하는 함수 또는 컴포넌트입니다."
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

positionerPrimitivePropsrender prop에 대한 설명이 "팝업 요소를 렌더링하는 함수 또는 컴포넌트입니다."로 되어 있어 popupPrimitiveProps의 설명과 동일하여 혼동을 줄 수 있습니다. 이 prop은 positioner를 렌더링하므로, 설명을 "Positioner 요소를 렌더링하는 함수 또는 컴포넌트입니다."와 같이 수정하는 것이 명확하겠습니다.

Comment on lines +16 to +20
const options = [
{ id: 'item1', label: 'First Item', defaultChecked: false },
{ id: 'item2', label: 'Second Item', defaultChecked: false },
{ id: 'item3', label: 'Third Item', defaultChecked: false },
];
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

스타일 가이드 4.6. 상수 (Constants)에 따라, options 배열과 같은 상수는 as const를 사용하여 불변성을 보장하고 타입 추론을 강화하는 것이 좋습니다.1

Suggested change
const options = [
{ id: 'item1', label: 'First Item', defaultChecked: false },
{ id: 'item2', label: 'Second Item', defaultChecked: false },
{ id: 'item3', label: 'Third Item', defaultChecked: false },
];
const options = [
{ id: 'item1', label: 'First Item', defaultChecked: false },
{ id: 'item2', label: 'Second Item', defaultChecked: false },
{ id: 'item3', label: 'Third Item', defaultChecked: false },
] as const;

Style Guide References

Footnotes

  1. 상수는 as const를 사용하여 불변성 및 타입 추론을 강화하는 것이 권장됩니다.


import * as styles from './action-bar.css';

export const ActionBarRoot = (props: ActionBarRoot.Props) => <Popover.Root {...props} />;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

스타일 가이드 6.14.1. Export 방식의 예시(490라인)에 따라, 디버깅 편의성을 위해 컴파운드 컴포넌트의 각 부분에 displayName을 설정하는 것이 좋습니다.1 ActionBarRoot 컴포넌트에도 displayName을 추가하고, 형식은 Parent.Child (예: ActionBar.Root)로 지정하는 것이 일관성에 좋습니다.

Suggested change
export const ActionBarRoot = (props: ActionBarRoot.Props) => <Popover.Root {...props} />;
export const ActionBarRoot = (props: ActionBarRoot.Props) => <Popover.Root {...props} />;
ActionBarRoot.displayName = 'ActionBar.Root';

Style Guide References

Footnotes

  1. 컴파운드 컴포넌트의 각 부분에 displayName을 설정하는 것은 디버깅 시 React DevTools에서 컴포넌트를 쉽게 식별할 수 있도록 도와주므로 권장됩니다. Parent.Child 형식을 사용하면 계층 구조를 명확히 할 수 있습니다.

return <Popover.Trigger ref={ref} {...componentProps} />;
},
);
ActionBarTrigger.displayName = 'ActionBarTrigger';
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

스타일 가이드 6.14.1. Export 방식의 예시(490, 497라인)를 보면 displayNameParent.Child 형식(예: Dialog.Trigger)으로 지정하고 있습니다.1 이렇게 하면 React DevTools에서 컴포넌트 계층 구조를 더 쉽게 파악할 수 있습니다. 다른 하위 컴포넌트들의 displayName도 이 형식에 맞게 일관성 있게 수정하는 것을 권장합니다.

Suggested change
ActionBarTrigger.displayName = 'ActionBarTrigger';
ActionBarTrigger.displayName = 'ActionBar.Trigger';

Style Guide References

Footnotes

  1. 컴파운드 컴포넌트의 displayNameParent.Child 형식으로 지정하여 React DevTools에서 컴포넌트의 소속과 역할을 명확히 나타내는 것이 권장됩니다.

@vapor-ui
Copy link
Collaborator

vapor-ui commented Nov 20, 2025

All tests passed!

Tests Passed Failed Duration Report
128 128 0 1m 54s Open report ↗︎

Click here if you need to update snapshots.

@noahchoii noahchoii changed the title feat: add new ActionBar component feat: add new FloatingBar component Nov 21, 2025
Comment on lines +115 to +137
export namespace FloatingBarTrigger {
export type Props = VComponentProps<typeof Popover.Trigger>;
}

export namespace FloatingBarClose {
export type Props = VComponentProps<typeof Popover.Close>;
}

export namespace FloatingBarPortalPrimitive {
export type Props = VComponentProps<typeof Popover.Portal>;
}

export namespace FloatingBarPositionerPrimitive {
export type Props = VComponentProps<'div'>;
}

export namespace FloatingBarPopupPrimitive {
export type Props = VComponentProps<typeof Popover.Popup>;
}

export namespace FloatingBarPopup {
export type Props = FloatingBarPopupPrimitive.Props;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

[Suggestion] I recommend replacing the namespace usage with standard named type exports.

  • Standardization: Modern TypeScript development strongly prefers ES Modules over namespaces. As per the TypeScript Handbook and eslint-plugin-typescript, namespaces are considered legacy for code organization.
  • Bundle Size: Namespaces compile to IIFEs or objects in JavaScript, which can hurt tree-shaking and add unnecessary runtime overhead compared to zero-cost type aliases or interfaces.

Proposed Change: Instead of FloatingBarRoot.Props, consider using a flat naming convention like FloatingBarRootProps.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the insightful review regarding standardization and bundle size optimization!

  1. Regarding Standardization & DX I fully acknowledge that namespace is often considered legacy in modern TypeScript development. However, this design choice was intentional to improve Cohesion and Developer Experience (DX).

If we rely solely on named exports (e.g., FloatingBarTriggerProps), consumers must rely on naming conventions to associate the props with FloatingBar.Trigger. By utilizing Declaration Merging, FloatingBar.Trigger acts as both the runtime component and the namespace for its types. This allows consumers to access types via a predictable, scoped path like FloatingBar.Trigger.Props, creating a explicit structural link rather than just a nominal one.

  1. Regarding Bundle Size You are correct that namespaces containing values compile to IIFEs. However, the namespaces used here are strictly Type-only namespaces. I have verified that these are completely erased during compilation and do not generate any runtime code (IIFEs) or objects. Therefore, there is zero runtime overhead and no negative impact on tree-shaking.

Given that this approach incurs no performance cost while significantly enhancing the semantic connection between the component and its API, would you be open to keeping this structure?

Copy link
Contributor

Choose a reason for hiding this comment

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

I've verified with tsup that type-only namespaces indeed have no side effects, confirming your explanation.

While this isn't a common pattern in most design systems (e.g., Radix UI, Ark UI use flat exports), I can see the DX benefits of the Component.SubComponent.Props pattern for API cohesion.

If your team finds this pattern intuitive, it's worth keeping. However, I'd recommend:

  • Adding a brief comment or documentation explaining this architectural choice
  • This helps onboard new contributors who might expect the more common ComponentProps pattern

paddingInline: vars.size.space['200'],

borderRadius: vars.size.borderRadius[300],
border: `1px solid ${vars.color.border.normal}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

It must be unified using 1px or 0.0625rem in border later.

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.

4 participants