-
Notifications
You must be signed in to change notification settings - Fork 46
design improvements for rule creation tabs #3308
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,75 +1,63 @@ | ||
| import React, { useState, createContext, useContext, isValidElement, ReactNode, useId } from 'react'; | ||
| import React, { isValidElement, ReactNode } from 'react'; | ||
| import * as RadixTabs from '@radix-ui/react-tabs'; | ||
| import cn from '@ably/ui/core/utils/cn'; | ||
|
|
||
| type TabsContextType = { | ||
| activeTab: string; | ||
| tabsId: string; | ||
| }; | ||
|
|
||
| const TabsContext = createContext<TabsContextType | undefined>(undefined); | ||
|
|
||
| interface TabProps { | ||
| value: string; | ||
| label: string; | ||
| children: ReactNode; | ||
| } | ||
|
|
||
| export const Tab: React.FC<TabProps> = ({ value, children }) => { | ||
| const context = useContext(TabsContext); | ||
| if (!context) { | ||
| return null; | ||
| } | ||
| return context.activeTab === value ? ( | ||
| <div role="tabpanel" id={`${context.tabsId}-panel-${value}`} aria-labelledby={`${context.tabsId}-tab-${value}`}> | ||
| {children} | ||
| </div> | ||
| ) : null; | ||
| export const Tab: React.FC<TabProps> = () => { | ||
| // Tab is only used declaratively — Tabs reads its props and renders RadixTabs.Content. | ||
| // When used outside of Tabs, render nothing. | ||
| return null; | ||
| }; | ||
|
|
||
| interface TabsProps { | ||
| children: ReactNode; | ||
| } | ||
|
|
||
| export const Tabs: React.FC<TabsProps> = ({ children }) => { | ||
| const tabsId = useId(); | ||
|
|
||
| const tabs: { value: string; label: string }[] = []; | ||
| const contentByValue: Record<string, ReactNode> = {}; | ||
|
|
||
| React.Children.forEach(children, (child) => { | ||
| if (isValidElement<TabProps>(child) && child.props.value) { | ||
| tabs.push({ value: child.props.value, label: child.props.label ?? child.props.value }); | ||
| contentByValue[child.props.value] = child.props.children; | ||
| } | ||
| }); | ||
|
|
||
| const [activeTab, setActiveTab] = useState(tabs[0]?.value ?? ''); | ||
|
|
||
| return ( | ||
| <TabsContext.Provider value={{ activeTab, tabsId }}> | ||
| <div className="my-5 border border-neutral-300 dark:border-neutral-800 rounded-lg overflow-hidden"> | ||
| <div | ||
| className="flex gap-1 border-b border-neutral-300 dark:border-neutral-800 bg-neutral-100 dark:bg-neutral-1100 px-2 pt-2" | ||
| role="tablist" | ||
| > | ||
| {tabs.map(({ value, label }) => ( | ||
| <button | ||
| key={value} | ||
| id={`${tabsId}-tab-${value}`} | ||
| role="tab" | ||
| aria-selected={activeTab === value} | ||
| aria-controls={`${tabsId}-panel-${value}`} | ||
| onClick={() => setActiveTab(value)} | ||
| className={cn( | ||
| 'px-4 py-2 text-sm font-medium rounded-t-md transition-colors cursor-pointer', | ||
| activeTab === value | ||
| ? 'bg-white dark:bg-neutral-1300 text-neutral-1300 dark:text-neutral-000 border border-neutral-300 dark:border-neutral-800 border-b-white dark:border-b-neutral-1300 -mb-px' | ||
| : 'text-neutral-700 dark:text-neutral-500 hover:text-neutral-1000 dark:hover:text-neutral-300', | ||
| )} | ||
| > | ||
| {label} | ||
| </button> | ||
| ))} | ||
| </div> | ||
| <div className="p-5">{children}</div> | ||
| </div> | ||
| </TabsContext.Provider> | ||
| <RadixTabs.Root | ||
| defaultValue={tabs[0]?.value} | ||
| className="my-5 border border-neutral-300 dark:border-neutral-800 rounded-lg overflow-hidden" | ||
| > | ||
| <RadixTabs.List className="flex gap-1 border-b border-neutral-300 dark:border-neutral-800 bg-neutral-100 dark:bg-neutral-1100 px-2 pt-2"> | ||
| {tabs.map(({ value, label }) => ( | ||
| <RadixTabs.Trigger | ||
| key={value} | ||
| value={value} | ||
| style={{ outline: 'none', borderTopLeftRadius: '6px', borderTopRightRadius: '6px' }} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the review, yeah this was feedback from design: the focus ring looks bad here so we decided to hide it. You can still navigate between the tabs using arrows keys and the focused tab is clearly visible so I don't think this is a real problem for accessibility. |
||
| className={cn( | ||
| 'px-4 py-2 ui-text-label3 transition-colors cursor-pointer', | ||
| 'border border-transparent', | ||
| 'text-neutral-700 dark:text-neutral-500 hover:text-neutral-1000 dark:hover:text-neutral-300', | ||
| 'data-[state=active]:bg-white data-[state=active]:dark:bg-neutral-1300 data-[state=active]:text-neutral-1300 data-[state=active]:dark:text-neutral-000', | ||
| 'data-[state=active]:border-neutral-300 data-[state=active]:dark:border-neutral-800', | ||
| 'data-[state=active]:border-b-white data-[state=active]:dark:border-b-neutral-1300 data-[state=active]:-mb-px', | ||
| )} | ||
| > | ||
| {label} | ||
| </RadixTabs.Trigger> | ||
| ))} | ||
| </RadixTabs.List> | ||
| {tabs.map(({ value }) => ( | ||
| <RadixTabs.Content key={value} value={value} className="px-5 pt-5 pb-2.5"> | ||
| {contentByValue[value]} | ||
| </RadixTabs.Content> | ||
| ))} | ||
| </RadixTabs.Root> | ||
| ); | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this will render nothing now, and we should probably keep that behaviour when using them independently. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah you're right, updated!