diff --git a/apps/fluent-tester/src/TestComponents/TabList/TabListTest.tsx b/apps/fluent-tester/src/TestComponents/TabList/TabListTest.tsx index 15af21c48e..794fe2cb41 100644 --- a/apps/fluent-tester/src/TestComponents/TabList/TabListTest.tsx +++ b/apps/fluent-tester/src/TestComponents/TabList/TabListTest.tsx @@ -1,15 +1,21 @@ import React from 'react'; -import { View } from 'react-native'; +import { TextInput, View } from 'react-native'; import { ButtonV1 as Button } from '@fluentui-react-native/button'; +import { CheckboxV1 as Checkbox } from '@fluentui-react-native/checkbox'; import { Divider } from '@fluentui-react-native/divider'; -import { Menu, MenuTrigger, MenuList, MenuItem, MenuPopover } from '@fluentui-react-native/menu'; +import { useFluentTheme } from '@fluentui-react-native/framework'; +import { RadioGroupV1 as RadioGroup, Radio } from '@fluentui-react-native/radio-group'; import { TabList, Tab } from '@fluentui-react-native/tablist'; +import type { TabListProps, TabProps, TabTokens } from '@fluentui-react-native/tablist'; import { TextV1 as Text } from '@fluentui-react-native/text'; +import type { Theme } from '@fluentui-react-native/theme-types'; +import { themedStyleSheet } from '@fluentui-react-native/themed-stylesheet'; import { TabListE2ETest } from './TabListE2ETest'; import { TABLIST_TESTPAGE } from '../../../../E2E/src/index.consts'; import { svgProps, fontProps } from '../Common/iconExamples'; +import { commonTestStyles } from '../Common/styles'; import { stackStyle } from '../Common/styles'; import type { PlatformStatus, TestSection } from '../Test'; import { Test } from '../Test'; @@ -17,6 +23,7 @@ import { Test } from '../Test'; const SubHeader = Text.customize({ variant: 'subheaderStandard' }); const Header = Text.customize({ variant: 'headerStandard' }); const Line = Divider.customize({ paddingVertical: 4 }); +const LineV = Divider.customize({ paddingHorizontal: 8 }); const PaddedTabList = TabList.customize({ paddingVertical: 4, }); @@ -50,162 +57,287 @@ const TabListDefaultTest: React.FunctionComponent = () => { ); }; -const TabListDisabledTest: React.FunctionComponent = () => { +const getStyles = themedStyleSheet((theme: Theme) => { + return { + horizontalStack: { display: 'flex', flexDirection: 'row' }, + flexOne: { flex: 1 }, + textInput: { ...commonTestStyles.textBox, backgroundColor: theme.colors.neutralBackground2 }, + }; +}); + +interface SubmitInputProps { + onSubmit: (value: string) => void; + initialValue?: string; + label?: string; +} +const SubmitInput: React.FunctionComponent = ({ label, onSubmit, initialValue }) => { + const styles = getStyles(useFluentTheme()); + const [value, setValue] = React.useState(initialValue ?? ''); + + const handleSubmit = React.useCallback(() => onSubmit(value), [onSubmit, value]); + return ( - - - - Tab 1 - - Tab 2 - - Tab 3 - - Tab 4 - - - Tab 1 - Tab 2 - Tab 3 - + + {label && {label}} + + + + ); }; -const TabListVariantsTest: React.FunctionComponent = () => { - return ( - -
Size Variants
- - Small - - Small Tab 1 - Small Tab 2 - Small Tab 3 - - Medium (default) - - Medium Tab 1 - Medium Tab 2 - Medium Tab 3 - - Large - - Large Tab 1 - Large Tab 2 - Large Tab 3 - -
Appearance
- - Transparent Appearance - - Tab 1 - Tab 2 - Tab 3 - - Subtle Appearance - - Tab 1 - Tab 2 - Tab 3 - -
Vertical Orientation
- - - Tab 1 - Tab 2 - Tab 3 - -
+const tabStates = [ + 'small', + 'medium', + 'large', + 'vertical', + 'hovered', + 'disabled', + 'selected', + 'focused', + 'pressed', + 'transparent', + 'subtle', + 'hasIcon', +]; + +const CustomizableTabList: React.FunctionComponent = () => { + const styles = getStyles(useFluentTheme()); + const [tablistProps, setTablistProps] = React.useState({ + defaultSelectedKey: 'A', + appearance: 'transparent', + size: 'medium', + vertical: false, + disabled: false, + }); + const [tabPropsDict, setTabPropsDict] = React.useState<{ [tabKey: string]: TabProps }>({ + A: { tabKey: 'A', children: 'Tab A' }, + B: { tabKey: 'B', children: 'Tab B' }, + C: { tabKey: 'C', children: 'Tab C' }, + D: { tabKey: 'D', children: 'Tab D' }, + }); + const [tabsToRender, setTabsToRender] = React.useState({ + A: true, + B: true, + C: true, + D: true, + }); + const [showTabControls, setShowTabControls] = React.useState({ + A: false, + B: false, + C: false, + D: false, + }); + const [tabTokensDict, setTabTokensDict] = React.useState<{ [tabKey: string]: TabTokens }>({ A: {}, B: {}, C: {}, D: {} }); + + const setTabProp = React.useCallback( + (tabKey: string, propName: T, value: TabProps[T]) => { + setTabPropsDict((prev) => { + const newProps = prev[tabKey]; + if (value === '') { + delete newProps[propName]; + } else { + newProps[propName] = value; + } + return { ...prev, [tabKey]: newProps }; + }); + }, + [setTabPropsDict], ); -}; + const setTabToken = React.useCallback( + (tabKey: string, tokenName: T, value: TabTokens[T]) => { + setTabTokensDict((prev) => { + const newTokens = prev[tabKey]; + if (value === '') { + delete newTokens[tokenName]; + for (const state of tabStates) { + if (newTokens[state][tokenName]) delete newTokens[state][tokenName]; + } + } else { + newTokens[tokenName] = value; + for (const state of tabStates) { + if (newTokens[state]) { + newTokens[state][tokenName] = value; + } else { + newTokens[state] = { [tokenName]: value }; + } + } + } + return { ...prev, [tabKey]: newTokens }; + }); + }, + [setTabTokensDict], + ); + const setTabTokenIfCastableToInt = React.useCallback( + (tabKey: string, tokenName: T, value: string) => { + const casted = parseInt(value); + if (casted) { + setTabToken(tabKey, tokenName, casted as any); + } else if (value === '') { + setTabToken(tabKey, tokenName, value as any); + } else { + console.warn(value, 'could not be casted to an integer.'); + } + }, + [setTabToken], + ); + + const customizedTabs = { + A: Tab.customize(tabTokensDict['A']), + B: Tab.customize(tabTokensDict['B']), + C: Tab.customize(tabTokensDict['C']), + D: Tab.customize(tabTokensDict['D']), + }; -const TabListIconTest: React.FunctionComponent = () => { return ( - - - Font Icon - - - - SVG Icon - - - - - ); -}; - -const TabListViewTest: React.FunctionComponent = () => { - const [key, setKey] = React.useState('a'); - const views = React.useMemo( - () => ({ - a: ( - - This is View 1 - Here is some text. - - ), - b: ( - - This is View 2 - - - ), - c: ( +
TabList Props
+ + setTablistProps((prev) => ({ ...prev, appearance: appearance as any }))} + > + + + + + setTablistProps((prev) => ({ ...prev, size: size as any }))}> + + + + + - This is View 3 - - - - - - - Item 1 - Item 2 - Item 3 - - - + setTablistProps((prev) => ({ ...prev, disabled: checked }))} + /> + setTablistProps((prev) => ({ ...prev, vertical: checked }))} + /> - ), - }), - [], - ); - return ( - - - View 1 - View 2 - View 3 + + +
Tab Props / Tokens / Settings
+ {Object.entries(tabPropsDict).map(([tabKey, props], i) => { + const controls = ( + <> + + + setTabProp(tabKey, 'children', val)} + /> + setTabProp(tabKey, 'disabled', checked)} /> + setTabsToRender((prev) => ({ ...prev, [tabKey]: checked }))} + /> + { + switch (key) { + case 'none': + setTabProp(tabKey, 'icon', undefined); + break; + case 'font': + setTabProp(tabKey, 'icon', { fontSource: fontProps }); + break; + case 'svg': + setTabProp(tabKey, 'icon', { svgSource: svgProps }); + break; + default: + } + }} + > + + + + + + + + setTabToken(tabKey, 'backgroundColor', v)} /> + setTabToken(tabKey, 'borderColor', v)} /> + setTabTokenIfCastableToInt(tabKey, 'borderWidth', v)} /> + setTabTokenIfCastableToInt(tabKey, 'borderRadius', v)} /> + setTabToken(tabKey, 'borderStyle', v as any)} /> + + + + setTabToken(tabKey, 'color', v)} /> + setTabTokenIfCastableToInt(tabKey, 'contentMarginStart', v)} /> + setTabTokenIfCastableToInt(tabKey, 'contentMarginEnd', v)} /> + setTabToken(tabKey, 'flexDirection', v as any)} /> + setTabToken(tabKey, 'indicatorColor', v)} /> + + + + setTabTokenIfCastableToInt(tabKey, 'indicatorMargin', v)} /> + setTabToken(tabKey, 'indicatorOrientation', v as any)} /> + setTabTokenIfCastableToInt(tabKey, 'indicatorRadius', v)} /> + setTabTokenIfCastableToInt(tabKey, 'indicatorThickness', v)} /> + setTabToken(tabKey, 'iconColor', v)} /> + + + + setTabTokenIfCastableToInt(tabKey, 'iconSize', v)} /> + setTabTokenIfCastableToInt(tabKey, 'stackMarginHorizontal', v)} + /> + setTabTokenIfCastableToInt(tabKey, 'stackMarginVertical', v)} + /> + + + + ); + return ( + <> + + Tab {tabKey} + + + + {showTabControls[tabKey] && controls} + + ); + })} +
+ + + {Object.keys(tabPropsDict) + .filter((tabKey) => tabsToRender[tabKey]) + .map((tabKey, i) => { + const CustomizedTab = customizedTabs[tabKey]; + return ; + })} - {views[key]}
); }; const sections: TestSection[] = [ { - name: 'TabList Controlled vs Uncontrolled', + name: 'Controlled vs Uncontrolled', component: TabListDefaultTest, testID: TABLIST_TESTPAGE, }, { - name: 'Disabled', - component: TabListDisabledTest, - }, - { - name: 'Variants', - component: TabListVariantsTest, - }, - { - name: 'Icon', - component: TabListIconTest, - }, - { - name: 'Rendering Content Separately', - component: TabListViewTest, + name: 'Customized TabList', + component: CustomizableTabList, }, ]; diff --git a/packages/experimental/TabList/src/TabList/useTabList.ts b/packages/experimental/TabList/src/TabList/useTabList.ts index 8e072c9027..3c61631655 100644 --- a/packages/experimental/TabList/src/TabList/useTabList.ts +++ b/packages/experimental/TabList/src/TabList/useTabList.ts @@ -44,9 +44,9 @@ export const useTabList = (props: TabListProps): TabListInfo => { const addTabKey = React.useCallback( (tabKey: string) => { - if (__DEV__ && tabKeys.includes(tabKey)) { - console.warn(`Tab Key "${tabKey}" already exists in the TabList. Duplicate keys are not supported.`); - } + // if (__DEV__ && tabKeys.includes(tabKey)) { + // console.warn(`Tab Key "${tabKey}" already exists in the TabList. Duplicate keys are not supported.`); + // } setTabKeys((keys) => [...keys, tabKey]); }, [tabKeys, setTabKeys],