Repository navigation
fix(ui): pill navbar stacking, gradient fade, and layout - #440
Conversation
Portal pill navbar + gradient haze to document.body via createPortal to escape ion-app's contain:layout. Override --ion-tab-bar-background to transparent in both light and dark palettes to eliminate the dark bar behind the pill. Add radial gradient haze overlay and mask-image content fade. Keep IonTabBar as invisible layout spacer for Ionic's flex system.
WalkthroughAdds a persistent bottom IonTabBar with animated tab icons (Wallet, Apps, Settings), a PillNavbarOverlay portal for pill-style navigation, updates PillNavbar to support Settings, moves content styling to class-based shells, and reorganizes CSS/ionic tokens for tab-aware layout, z-index, and reduced-motion handling. Changes
Sequence DiagramsequenceDiagram
actor User
participant App
participant PillNavbarOverlay
participant AnimatedTabIcon
participant IonTabBar
User->>PillNavbarOverlay: click tab button (wallet/apps/settings)
PillNavbarOverlay->>App: onWalletClick/onAppsClick/onSettingsClick
App->>App: triggerTabAnim(tabName) / set animatingTab
App->>AnimatedTabIcon: pass animatingTab prop
AnimatedTabIcon->>AnimatedTabIcon: run icon animation
AnimatedTabIcon-->>User: icon animates
App->>IonTabBar: set active tab
IonTabBar->>App: apply/remove .tab-hidden on non-active pages
IonTabBar-->>User: active tab content becomes visible
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying wallet-bitcoin with
|
| Latest commit: |
78c44f6
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://6ac22164.wallet-bitcoin.pages.dev |
| Branch Preview URL: | https://wt-pill-fixes-20260313.wallet-bitcoin.pages.dev |
Deploying wallet-mutinynet with
|
| Latest commit: |
78c44f6
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://c58fc285.arkade-wallet.pages.dev |
| Branch Preview URL: | https://wt-pill-fixes-20260313.arkade-wallet.pages.dev |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/index.css (1)
245-264:⚠️ Potential issue | 🟠 MajorThe five-tab bar overflows on narrow phones.
Line 251 caps the bar at
100vw - 24px, but the new five-button layout has a hard minimum of384px(5 × 72buttons +4 × 4gaps +8container padding). On a 320px device that only leaves296px, so the outer tabs will be pushed off-screen instead of compressing.📏 Suggested responsive fix
.pill-navbar { - width: fit-content; + width: min(460px, calc(100vw - 24px)); max-width: calc(100vw - 24px); display: flex; align-items: center; gap: 4px; @@ .pill-nav-btn { + flex: 1 1 0; - min-width: 72px; + min-width: 0; min-height: 44px; padding: 6px 12px; @@ +@media (max-width: 360px) { + .pill-nav-btn { + padding-inline: 8px; + } +}Also applies to: 274-283
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.css` around lines 245 - 264, The pill bar overflows on narrow viewports because child tabs have a hard minimum width but the container uses fit-content; update .pill-navbar and its tab children so the container still respects viewport space and tabs can shrink: keep the container max-width as calc(100vw - 24px) (or use max-width: min(100vw - 24px, 100%)) but remove fit-content sizing (use width: 100% or auto) and ensure each tab element (e.g., the tab buttons—selectors like .pill-navbar > button or .pill-button / .pill-tab) uses flexible sizing such as display:flex; flex: 1 1 0; and min-width: 0 so five tabs can compress instead of overflowing; optionally add overflow-x:auto if you prefer scroll on extreme narrow screens.
🧹 Nitpick comments (2)
src/components/PillNavbar.tsx (1)
19-26: Consider simplifying nested ternary with a lookup object.The three-level nested ternary for ref selection is functional but could be more readable.
♻️ Optional: Use a lookup map
+ const refMap: Record<string, React.RefObject<HTMLDivElement | null>> = { + wallet: walletRef, + apps: appsRef, + settings: settingsRef, + } + useEffect(() => { - const ref = - activeTab === 'wallet' - ? walletRef - : activeTab === 'apps' - ? appsRef - : activeTab === 'settings' - ? settingsRef - : null + const ref = refMap[activeTab] ?? null if (!ref?.current) return🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/PillNavbar.tsx` around lines 19 - 26, Replace the nested ternary used to set ref in PillNavbar (the ref variable computed from activeTab) with a simple lookup map keyed by activeTab (e.g., const refMap = { wallet: walletRef, apps: appsRef, settings: settingsRef }; const ref = refMap[activeTab] ?? null) so readability is improved and you avoid the three-level nested ternary; update any references to ref accordingly (walletRef, appsRef, settingsRef, activeTab).src/App.tsx (1)
58-79: Animation logic is duplicated with PillNavbar.Both
AnimatedTabIconhere and theuseEffectinPillNavbar.tsxuse the same class toggle + reflow + animationend pattern. Consider extracting to a shared hook if more instances emerge.♻️ Optional: Shared hook for animation triggering
// hooks/useAnimationTrigger.ts import { useRef, useEffect } from 'react' export function useAnimationTrigger(trigger: boolean, animClass: string) { const ref = useRef<HTMLDivElement>(null) useEffect(() => { if (!trigger || !ref.current) return const el = ref.current el.classList.remove(animClass) void el.offsetWidth el.classList.add(animClass) const handleEnd = () => el.classList.remove(animClass) el.addEventListener('animationend', handleEnd) return () => el.removeEventListener('animationend', handleEnd) }, [trigger, animClass]) return ref }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/App.tsx` around lines 58 - 79, AnimatedTabIcon duplicates the animation toggle/reflow/animationend logic used in PillNavbar; extract that logic into a shared hook (e.g., useAnimationTrigger) and use it from both components: create a hook that returns a ref and accepts (trigger: boolean, animClass: string), move the effect currently inside AnimatedTabIcon (and the similar effect in PillNavbar) into that hook, then replace the local useRef/useEffect in AnimatedTabIcon and PillNavbar with the hook and pass animClass (currently animClass = 'tab-anim-pop') and the animating/trigger prop so both components reuse the same animation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/index.css`:
- Around line 245-264: The pill bar overflows on narrow viewports because child
tabs have a hard minimum width but the container uses fit-content; update
.pill-navbar and its tab children so the container still respects viewport space
and tabs can shrink: keep the container max-width as calc(100vw - 24px) (or use
max-width: min(100vw - 24px, 100%)) but remove fit-content sizing (use width:
100% or auto) and ensure each tab element (e.g., the tab buttons—selectors like
.pill-navbar > button or .pill-button / .pill-tab) uses flexible sizing such as
display:flex; flex: 1 1 0; and min-width: 0 so five tabs can compress instead of
overflowing; optionally add overflow-x:auto if you prefer scroll on extreme
narrow screens.
---
Nitpick comments:
In `@src/App.tsx`:
- Around line 58-79: AnimatedTabIcon duplicates the animation
toggle/reflow/animationend logic used in PillNavbar; extract that logic into a
shared hook (e.g., useAnimationTrigger) and use it from both components: create
a hook that returns a ref and accepts (trigger: boolean, animClass: string),
move the effect currently inside AnimatedTabIcon (and the similar effect in
PillNavbar) into that hook, then replace the local useRef/useEffect in
AnimatedTabIcon and PillNavbar with the hook and pass animClass (currently
animClass = 'tab-anim-pop') and the animating/trigger prop so both components
reuse the same animation behavior.
In `@src/components/PillNavbar.tsx`:
- Around line 19-26: Replace the nested ternary used to set ref in PillNavbar
(the ref variable computed from activeTab) with a simple lookup map keyed by
activeTab (e.g., const refMap = { wallet: walletRef, apps: appsRef, settings:
settingsRef }; const ref = refMap[activeTab] ?? null) so readability is improved
and you avoid the three-level nested ternary; update any references to ref
accordingly (walletRef, appsRef, settingsRef, activeTab).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f2747891-0a28-4ad5-8cee-6c5f0f43da15
📒 Files selected for processing (9)
src/App.tsxsrc/components/ButtonsOnBottom.tsxsrc/components/Content.tsxsrc/components/PillNavbar.tsxsrc/components/PillNavbarOverlay.tsxsrc/icons/Settings.tsxsrc/index.csssrc/ionic.csssrc/screens/Settings/Delegates.tsx
ghost
left a comment
There was a problem hiding this comment.
🔍 Arkana Review — wallet#440
Summary: Fixes pill navbar stacking/z-index issues by rendering the navbar via createPortal to document.body, escaping Ionic's contain: size layout style. Adds gradient haze overlay, dark mode fixes, and tab switch animations.
✅ What looks good
-
Portal approach is the correct fix for escaping Ionic's CSS containment —
createPortaltodocument.bodyis the standard React pattern for elements that need to break out of layout containment contexts. -
Dark mode fix — overriding
--ion-tab-bar-background: transparentin both:rootand.ion-palette-darkeliminates the shadow DOM dark bar. Good that it targets the right specificity levels. -
Author acknowledges tech debt — the spacing model with
--pill-navbar-spacer,--pill-navbar-clearance, etc. is explicitly called out as hacky with a follow-up PR planned. Appreciated.
🟡 Observations
-
Last tx history item partially hidden — acknowledged in the PR. Not blocking but worth tracking.
-
isolation: isolateon the gradient haze creates a new stacking context. Make sure this doesn't interfere with modals/dialogs that also need to render above the navbar.
🔒 Security
No security concerns. Pure UI/CSS changes — no wallet logic, key handling, or transaction code is touched.
Verdict: Good UI fix, well-scoped. LGTM.
🔍 Review —
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/App.tsx (1)
249-280: Consider simplifying IonTabBar content since it's purely a layout spacer.Per the PR objectives, the
IonTabBaris kept invisible to maintain Ionic's flex layout. However, the buttons currently render full content (icons, text,FlexCol,Focusable,AnimatedTabIcon) that will never be visible. This adds unnecessary DOM nodes and component instances.♻️ Suggested simplification
<IonTabBar slot='bottom'> - <IonTabButton tab={Tabs.Wallet} onClick={handleWallet} selected={tab === Tabs.Wallet}> - <Focusable> - <FlexCol centered gap='6px' padding='5px'> - <AnimatedTabIcon animating={animatingTab === 'wallet'}> - <WalletIcon /> - </AnimatedTabIcon> - Wallet - </FlexCol> - </Focusable> - </IonTabButton> - <IonTabButton tab={Tabs.Apps} onClick={handleApps} selected={tab === Tabs.Apps}> - <Focusable> - <FlexCol centered gap='6px' padding='5px'> - <AnimatedTabIcon animating={animatingTab === 'apps'}> - <AppsIcon /> - </AnimatedTabIcon> - Apps - </FlexCol> - </Focusable> - </IonTabButton> - <IonTabButton tab={Tabs.Settings} onClick={handleSettings} selected={tab === Tabs.Settings}> - <Focusable> - <FlexCol centered gap='6px' padding='5px'> - <AnimatedTabIcon animating={animatingTab === 'settings'}> - <SettingsIcon /> - </AnimatedTabIcon> - Settings - </FlexCol> - </Focusable> - </IonTabButton> + <IonTabButton tab={Tabs.Wallet} aria-hidden='true' /> + <IonTabButton tab={Tabs.Apps} aria-hidden='true' /> + <IonTabButton tab={Tabs.Settings} aria-hidden='true' /> </IonTabBar>This removes redundant components and marks the spacer buttons as
aria-hiddensincePillNavbarOverlayhandles the actual navigation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/App.tsx` around lines 249 - 280, IonTabBar is being used only as an invisible layout spacer but currently instantiates full tab content (IonTabButton, Focusable, FlexCol, AnimatedTabIcon, icons, labels) which is unnecessary; change the IonTabBar children to lightweight spacer buttons by replacing the full content inside IonTabButton (the ones using tab={Tabs.Wallet}, tab={Tabs.Apps}, tab={Tabs.Settings}) with minimal placeholders (e.g., empty or visually-hidden elements), add aria-hidden on those IonTabButton instances, and keep their onClick handlers (handleWallet, handleApps, handleSettings) and selected prop behavior intact so PillNavbarOverlay retains navigation responsibility while reducing DOM/component overhead.src/ionic.css (1)
233-256: Consider extracting hardcoded pixel offsets into CSS variables.The mask gradient uses several hardcoded pixel values (
56px,12px,18px) that create tight coupling with specific measurements. Per the PR objectives, the spacing model is noted as "hacky and brittle" with a follow-up planned. When that refactor happens, consider extracting these into named tokens for maintainability.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/ionic.css` around lines 233 - 256, The hardcoded pixel offsets in the mask gradient inside the selector ion-app.has-pill-navbar ion-content::part(scroll) (56px, 12px, 18px) make spacing brittle; replace those literal values with new CSS variables (for example --pill-navbar-gradient-top, --pill-navbar-gradient-mid, --pill-navbar-gradient-bottom or similar semantic names) and update the gradient calculations to use calc(100% - var(--pill-navbar-clearance) - var(--pill-navbar-gradient-top)) / calc(100% - var(--pill-navbar-clearance) - var(--pill-navbar-gradient-mid)) / calc(100% - var(--pill-navbar-gradient-bottom)) so the spacing tokens are configurable and self-documented (add default values for the new variables near other CSS tokens).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/App.tsx`:
- Around line 61-80: AnimatedTabIcon currently always triggers the pop animation
which ignores users' reduced-motion preference; import and call useReducedMotion
inside AnimatedTabIcon (or accept a prefersReduced boolean prop) and bail out of
the effect when reduced motion is requested — e.g., compute const prefersReduced
= useReducedMotion(); update the effect to if (!animating || prefersReduced ||
!ref.current) return; ensure prefersReduced is added to the effect dependencies
so animClass is never added for users who prefer reduced motion.
In `@src/ionic.css`:
- Around line 215-218: The invisible tab buttons (selector ion-tab-bar
ion-tab-button) are still reachable by keyboard/screen-readers; update the CSS
rule to also include visibility: hidden to remove them from focus/layout, and
ensure the component that renders these buttons (PillNavbar) sets tabindex="-1"
on those hidden ion-tab-button elements so they are excluded from keyboard
navigation.
---
Nitpick comments:
In `@src/App.tsx`:
- Around line 249-280: IonTabBar is being used only as an invisible layout
spacer but currently instantiates full tab content (IonTabButton, Focusable,
FlexCol, AnimatedTabIcon, icons, labels) which is unnecessary; change the
IonTabBar children to lightweight spacer buttons by replacing the full content
inside IonTabButton (the ones using tab={Tabs.Wallet}, tab={Tabs.Apps},
tab={Tabs.Settings}) with minimal placeholders (e.g., empty or visually-hidden
elements), add aria-hidden on those IonTabButton instances, and keep their
onClick handlers (handleWallet, handleApps, handleSettings) and selected prop
behavior intact so PillNavbarOverlay retains navigation responsibility while
reducing DOM/component overhead.
In `@src/ionic.css`:
- Around line 233-256: The hardcoded pixel offsets in the mask gradient inside
the selector ion-app.has-pill-navbar ion-content::part(scroll) (56px, 12px,
18px) make spacing brittle; replace those literal values with new CSS variables
(for example --pill-navbar-gradient-top, --pill-navbar-gradient-mid,
--pill-navbar-gradient-bottom or similar semantic names) and update the gradient
calculations to use calc(100% - var(--pill-navbar-clearance) -
var(--pill-navbar-gradient-top)) / calc(100% - var(--pill-navbar-clearance) -
var(--pill-navbar-gradient-mid)) / calc(100% -
var(--pill-navbar-gradient-bottom)) so the spacing tokens are configurable and
self-documented (add default values for the new variables near other CSS
tokens).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6259e534-04f6-490d-8fef-7454cefb9d4b
📒 Files selected for processing (2)
src/App.tsxsrc/ionic.css
| function AnimatedTabIcon({ children, animating }: { children: React.ReactNode; animating: boolean }) { | ||
| const ref = useRef<HTMLDivElement>(null) | ||
|
|
||
| useEffect(() => { | ||
| if (!animating || !ref.current) return | ||
| const el = ref.current | ||
| el.classList.remove(animClass) | ||
| void el.offsetWidth // Force reflow so removing + re-adding the class triggers the animation | ||
| el.classList.add(animClass) | ||
| const handleEnd = () => el.classList.remove(animClass) | ||
| el.addEventListener('animationend', handleEnd) | ||
| return () => el.removeEventListener('animationend', handleEnd) | ||
| }, [animating]) | ||
|
|
||
| return ( | ||
| <div ref={ref} className='tab-icon-animated'> | ||
| {children} | ||
| </div> | ||
| ) | ||
| } |
There was a problem hiding this comment.
Tab icon animation should respect reduced motion preference.
AnimatedTabIcon triggers a pop animation but doesn't check useReducedMotion, while page transitions already do (line 189-190). Users who prefer reduced motion won't see page slide animations but will still see tab icon pops, which is inconsistent.
🛡️ Proposed fix to respect reduced motion
-function AnimatedTabIcon({ children, animating }: { children: React.ReactNode; animating: boolean }) {
+function AnimatedTabIcon({ children, animating, reducedMotion }: { children: React.ReactNode; animating: boolean; reducedMotion: boolean }) {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
- if (!animating || !ref.current) return
+ if (!animating || !ref.current || reducedMotion) return
const el = ref.currentThen pass prefersReduced to each AnimatedTabIcon instance:
-<AnimatedTabIcon animating={animatingTab === 'wallet'}>
+<AnimatedTabIcon animating={animatingTab === 'wallet'} reducedMotion={prefersReduced}>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/App.tsx` around lines 61 - 80, AnimatedTabIcon currently always triggers
the pop animation which ignores users' reduced-motion preference; import and
call useReducedMotion inside AnimatedTabIcon (or accept a prefersReduced boolean
prop) and bail out of the effect when reduced motion is requested — e.g.,
compute const prefersReduced = useReducedMotion(); update the effect to if
(!animating || prefersReduced || !ref.current) return; ensure prefersReduced is
added to the effect dependencies so animClass is never added for users who
prefer reduced motion.
| ion-tab-bar ion-tab-button { | ||
| opacity: 0; | ||
| --background: transparent; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if tabindex is set on IonTabButton elements or if there's other focus prevention
rg -n 'IonTabButton' --type=tsx -A5 | head -60Repository: arkade-os/wallet
Length of output: 87
🏁 Script executed:
#!/bin/bash
# Explore repository structure to understand file types
ls -la
git ls-files | head -20
echo "---"
# Check what file types exist
find . -type f -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" | grep -v node_modules | head -20Repository: arkade-os/wallet
Length of output: 3247
🏁 Script executed:
#!/bin/bash
# Look at the ionic.css file around lines 215-218
cat -n src/ionic.css | sed -n '210,225p'Repository: arkade-os/wallet
Length of output: 436
🏁 Script executed:
#!/bin/bash
# Search for IonTabButton in various file types
rg -n 'IonTabButton' -t js -t jsx -t ts 2>/dev/null | head -40Repository: arkade-os/wallet
Length of output: 42
🏁 Script executed:
#!/bin/bash
# Check for any tabindex or focus-related attributes in component files
rg -n 'ion-tab-button|tabindex|aria-hidden|visibility.*hidden' src/ | grep -v node_modules | head -50Repository: arkade-os/wallet
Length of output: 718
🏁 Script executed:
#!/bin/bash
# Look at PillNavbarOverlay to understand the context
cat -n src/components/PillNavbarOverlay.tsxRepository: arkade-os/wallet
Length of output: 954
🏁 Script executed:
#!/bin/bash
# Check ionic.css for pointer-events on ion-tab-bar
cat -n src/ionic.css | sed -n '200,230p'Repository: arkade-os/wallet
Length of output: 907
🏁 Script executed:
#!/bin/bash
# Search for ion-tab-bar styling to understand the full context
rg -n 'ion-tab-bar' src/ionic.css -B3 -A3Repository: arkade-os/wallet
Length of output: 959
Add keyboard focus prevention to hidden tab buttons.
The opacity: 0 hides the buttons visually, but they remain in the DOM and accessibility tree. While pointer-events: none on the parent prevents mouse interaction, it does not prevent keyboard focus. Users navigating with Tab or screen reader users will encounter these invisible buttons, creating a confusing experience.
Add visibility: hidden (to remove from layout while preventing focus) or set tabindex="-1" (to exclude from keyboard navigation) on ion-tab-button to prevent unintended focus since PillNavbar handles the actual interaction.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/ionic.css` around lines 215 - 218, The invisible tab buttons (selector
ion-tab-bar ion-tab-button) are still reachable by keyboard/screen-readers;
update the CSS rule to also include visibility: hidden to remove them from
focus/layout, and ensure the component that renders these buttons (PillNavbar)
sets tabindex="-1" on those hidden ion-tab-button elements so they are excluded
from keyboard navigation.
…e-os#440) Portal pill navbar + gradient haze to document.body via createPortal to escape ion-app's contain:layout. Override --ion-tab-bar-background to transparent in both light and dark palettes to eliminate the dark bar behind the pill. Add radial gradient haze overlay and mask-image content fade. Keep IonTabBar as invisible layout spacer for Ionic's flex system. Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com> Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>
Summary
createPortaltodocument.body, escapingion-app'scontain: size layout stylethat was trapping z-index and fixed positioning--ion-tab-bar-background: transparentin both:rootand.ion-palette-darkto eliminate Ionic's shadow DOM dark bar behind the pillpill-navbar-haze) withisolation: isolatestacking context, plusmask-imagefade onion-content::part(scroll)for content fade-out effectIonTabBarkept in DOM (transparent,position: absolute) so Ionic's flex layout auto-accounts for bottom spacingAnimatedTabIconwith CSS pop animation on tab switchKnown issues / follow-up work
Files changed
src/App.tsx— PortalPillNavbarOverlay,AnimatedTabIcon,IonTabBaras spacer,has-pill-navbarclasssrc/components/PillNavbarOverlay.tsx— New: portals pill + gradient todocument.bodysrc/components/PillNavbar.tsx— Settings tab added, icon animationssrc/index.css—.pill-navbar-layer,.pill-navbar-haze(radial gradient), dark mode overridessrc/ionic.css—--ion-tab-bar-background: transparent,mask-imagefade, clearance tokenssrc/components/ButtonsOnBottom.tsx—.buttons-on-bottomclass for CSS targetingsrc/components/Content.tsx—.content-shellclasssrc/screens/Settings/Delegates.tsx— Per-page pill clearance paddingsrc/icons/Settings.tsx— Default export fixTest plan
Summary by CodeRabbit
New Features
Style
Refactor