init project
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled

This commit is contained in:
how2ice
2025-12-12 14:26:25 +09:00
commit 005cf56baf
43188 changed files with 1079531 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
import debounce from '../utils/debounce';
import { ownerWindow, unstable_useEnhancedEffect as useEnhancedEffect } from '../utils';
const styles = {
width: 99,
height: 99,
position: 'absolute',
top: -9999,
overflow: 'scroll',
};
/**
* @ignore - internal component.
* The component originates from https://github.com/STORIS/react-scrollbar-size.
* It has been moved into the core in order to minimize the bundle size.
*/
export default function ScrollbarSize(props) {
const { onChange, ...other } = props;
const scrollbarHeight = React.useRef();
const nodeRef = React.useRef(null);
const setMeasurements = () => {
scrollbarHeight.current = nodeRef.current.offsetHeight - nodeRef.current.clientHeight;
};
useEnhancedEffect(() => {
const handleResize = debounce(() => {
const prevHeight = scrollbarHeight.current;
setMeasurements();
if (prevHeight !== scrollbarHeight.current) {
onChange(scrollbarHeight.current);
}
});
const containerWindow = ownerWindow(nodeRef.current);
containerWindow.addEventListener('resize', handleResize);
return () => {
handleResize.clear();
containerWindow.removeEventListener('resize', handleResize);
};
}, [onChange]);
React.useEffect(() => {
setMeasurements();
onChange(scrollbarHeight.current);
}, [onChange]);
return <div style={styles} {...other} ref={nodeRef} />;
}
ScrollbarSize.propTypes = {
onChange: PropTypes.func.isRequired,
};

View File

@@ -0,0 +1,49 @@
import { expect } from 'chai';
import { spy, stub } from 'sinon';
import { createRenderer } from '@mui/internal-test-utils';
import ScrollbarSize from './ScrollbarSize';
describe('<ScrollbarSize />', () => {
const { clock, render } = createRenderer({ clock: 'fake' });
describe('mount', () => {
it('should call on initial load', () => {
const onChange = spy();
render(<ScrollbarSize onChange={onChange} />);
expect(onChange.called).to.equal(true);
});
});
describe('prop: onChange', () => {
it('should call on first resize event', async () => {
const onChange = spy();
const { container } = render(<ScrollbarSize onChange={onChange} />);
stub(container.firstChild, 'offsetHeight').get(() => 20);
stub(container.firstChild, 'clientHeight').get(() => 0);
onChange.resetHistory();
window.dispatchEvent(new window.Event('resize', {}));
clock.tick(166);
expect(onChange.callCount).to.equal(1);
expect(onChange.args[0][0]).to.equal(20);
});
it('should not call if height has not changed from previous resize', async () => {
const onChange = spy();
const { container } = render(<ScrollbarSize onChange={onChange} />);
stub(container.firstChild, 'offsetHeight').get(() => 20);
stub(container.firstChild, 'clientHeight').get(() => 0);
onChange.resetHistory();
window.dispatchEvent(new window.Event('resize', {}));
clock.tick(166);
window.dispatchEvent(new window.Event('resize', {}));
clock.tick(166);
expect(onChange.callCount).to.equal(1);
expect(onChange.args[0][0]).to.equal(20);
});
});
});

View File

@@ -0,0 +1,308 @@
import * as React from 'react';
import { SxProps } from '@mui/system';
import { OverridableStringUnion } from '@mui/types';
import { CreateSlotsAndSlotProps, SlotProps } from '../utils/types';
import { Theme } from '../styles';
import TabScrollButton, { TabScrollButtonProps } from '../TabScrollButton';
import { OverridableComponent, OverrideProps } from '../OverridableComponent';
import { TabsClasses } from './tabsClasses';
import SvgIcon from '../SvgIcon';
export interface TabsPropsIndicatorColorOverrides {}
export interface TabsRootSlotPropsOverrides {}
export interface TabsScrollerSlotPropsOverrides {}
export interface TabsListSlotPropsOverrides {}
export interface TabsScrollbarSlotPropsOverrides {}
export interface TabsIndicatorSlotPropsOverrides {}
export interface TabsScrollButtonsSlotPropsOverrides {}
export interface TabsStartScrollButtonIconSlotPropsOverrides {}
export interface TabsEndScrollButtonIconSlotPropsOverrides {}
export interface TabsSlots {
/**
* The component used for the popper.
* @default div
*/
root: React.ElementType;
/**
* The component used for the scroller.
* @default div
*/
scroller: React.ElementType;
/**
* The component used for the flex container.
* @default div
*/
list: React.ElementType;
/**
* The component used for the scroller.
* @default ScrollbarSize
*/
scrollbar: React.ElementType;
/**
* The component used for the tab indicator.
* @default span
*/
indicator: React.ElementType;
/**
* The component used for the scroll button.
* @default TabScrollButton
*/
scrollButtons: React.ElementType;
/**
* The component used for the start scroll button icon.
* @default KeyboardArrowLeft
*/
startScrollButtonIcon: React.ElementType;
/**
* The component used for the end scroll button icon.
* @default KeyboardArrowRight
*/
endScrollButtonIcon: React.ElementType;
}
export type TabsSlotsAndSlotProps = CreateSlotsAndSlotProps<
TabsSlots,
{
/**
* Props forwarded to the root slot.
* By default, the available props are based on the div element.
*/
root: SlotProps<'div', TabsRootSlotPropsOverrides, TabsOwnerState>;
/**
* Props forwarded to the scroller slot.
* By default, the available props are based on the div element.
*/
scroller: SlotProps<'div', TabsScrollerSlotPropsOverrides, TabsOwnerState>;
/**
* Props forwarded to the list slot.
* By default, the available props are based on the div element.
*/
list: SlotProps<'div', TabsListSlotPropsOverrides, TabsOwnerState>;
/**
* Props forwarded to the scrollbar slot.
* By default, the available props are based on the div element.
*/
scrollbar: SlotProps<
'div',
{ onChange?: (scrollbarWidth: undefined | number) => void } & TabsScrollbarSlotPropsOverrides,
TabsOwnerState
>;
/**
* Props forwarded to the indicator slot.
* By default, the available props are based on the span element.
*/
indicator: SlotProps<'span', TabsIndicatorSlotPropsOverrides, TabsOwnerState>;
/**
* Props forwarded to the scrollButton slot.
* By default, the available props are based on the [TabScrollButton](https://mui.com/material-ui/api/tab-scroll-button/#props) component.
*/
scrollButtons: SlotProps<
typeof TabScrollButton,
TabsScrollButtonsSlotPropsOverrides,
TabsOwnerState
>;
/**
* Props forwarded to the startScrollButtonIcon slot.
* By default, the available props are based on the [SvgIcon](https://mui.com/material-ui/api/svg-icon/#props) component.
*/
startScrollButtonIcon: SlotProps<
typeof SvgIcon,
TabsStartScrollButtonIconSlotPropsOverrides,
TabsOwnerState
>;
/**
* Props forwarded to the endScrollButtonIcon slot.
* By default, the available props are based on the [SvgIcon](https://mui.com/material-ui/api/svg-icon/#props) component.
*/
endScrollButtonIcon: SlotProps<
typeof SvgIcon,
TabsEndScrollButtonIconSlotPropsOverrides,
TabsOwnerState
>;
}
> & {
slots?: {
/**
* @deprecated Use `slots.startScrollButtonIcon` instead.
*/
StartScrollButtonIcon?: React.ElementType;
/**
* @deprecated Use `slots.endScrollButtonIcon` instead.
*/
EndScrollButtonIcon?: React.ElementType;
};
};
export interface TabsOwnerState extends Omit<TabsProps, 'slots' | 'slotProps'> {
vertical: boolean;
fixed: boolean;
hideScrollbar: boolean;
scrollableX: boolean;
scrollableY: boolean;
centered: boolean;
scrollButtonsHideMobile: boolean;
}
export interface TabsOwnProps extends TabsSlotsAndSlotProps {
/**
* Callback fired when the component mounts.
* This is useful when you want to trigger an action programmatically.
* It supports two actions: `updateIndicator()` and `updateScrollButtons()`
*
* @param {object} actions This object contains all possible actions
* that can be triggered programmatically.
*/
action?: React.Ref<TabsActions>;
/**
* If `true`, the scroll buttons aren't forced hidden on mobile.
* By default the scroll buttons are hidden on mobile and takes precedence over `scrollButtons`.
* @default false
*/
allowScrollButtonsMobile?: boolean;
/**
* The label for the Tabs as a string.
*/
'aria-label'?: string;
/**
* An id or list of ids separated by a space that label the Tabs.
*/
'aria-labelledby'?: string;
/**
* If `true`, the tabs are centered.
* This prop is intended for large views.
* @default false
*/
centered?: boolean;
/**
* The content of the component.
*/
children?: React.ReactNode;
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<TabsClasses>;
/**
* Determines the color of the indicator.
* @default 'primary'
*/
indicatorColor?: OverridableStringUnion<
'secondary' | 'primary',
TabsPropsIndicatorColorOverrides
>;
/**
* Callback fired when the value changes.
*
* @param {React.SyntheticEvent} event The event source of the callback. **Warning**: This is a generic event not a change event.
* @param {any} value We default to the index of the child (number)
*/
onChange?: (event: React.SyntheticEvent, value: any) => void;
/**
* The component orientation (layout flow direction).
* @default 'horizontal'
*/
orientation?: 'horizontal' | 'vertical';
/**
* The component used to render the scroll buttons.
* @deprecated use the `slots.scrollButtons` prop instead. This prop will be removed in a future major release. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details.
* @default TabScrollButton
*/
ScrollButtonComponent?: React.ElementType;
/**
* Determine behavior of scroll buttons when tabs are set to scroll:
*
* - `auto` will only present them when not all the items are visible.
* - `true` will always present them.
* - `false` will never present them.
*
* By default the scroll buttons are hidden on mobile.
* This behavior can be disabled with `allowScrollButtonsMobile`.
* @default 'auto'
*/
scrollButtons?: 'auto' | true | false;
/**
* If `true` the selected tab changes on focus. Otherwise it only
* changes on activation.
*/
selectionFollowsFocus?: boolean;
/**
* Props applied to the tab indicator element.
* @deprecated use the `slotProps.indicator` prop instead. This prop will be removed in a future major release. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details.
* @default {}
*/
TabIndicatorProps?: React.HTMLAttributes<HTMLDivElement> & {
sx?: SxProps<Theme>;
};
/**
* Props applied to the [`TabScrollButton`](https://mui.com/material-ui/api/tab-scroll-button/) element.
* @deprecated use the `slotProps.scrollButtons` prop instead. This prop will be removed in a future major release. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details.
* @default {}
*/
TabScrollButtonProps?: Partial<TabScrollButtonProps>;
/**
* Determines the color of the `Tab`.
* @default 'primary'
*/
textColor?: 'secondary' | 'primary' | 'inherit';
/**
* The value of the currently selected `Tab`.
* If you don't want any selected `Tab`, you can set this prop to `false`.
*/
value?: any;
/**
* Determines additional display behavior of the tabs:
*
* - `scrollable` will invoke scrolling properties and allow for horizontally
* scrolling (or swiping) of the tab bar.
* - `fullWidth` will make the tabs grow to use all the available space,
* which should be used for small views, like on mobile.
* - `standard` will render the default state.
* @default 'standard'
*/
variant?: 'standard' | 'scrollable' | 'fullWidth';
/**
* If `true`, the scrollbar is visible. It can be useful when displaying
* a long vertical list of tabs.
* @default false
*/
visibleScrollbar?: boolean;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps<Theme>;
}
export interface TabsTypeMap<
AdditionalProps = {},
RootComponent extends React.ElementType = 'div',
> {
props: AdditionalProps & TabsOwnProps;
defaultComponent: RootComponent;
}
/**
*
* Demos:
*
* - [Tabs](https://mui.com/material-ui/react-tabs/)
*
* API:
*
* - [Tabs API](https://mui.com/material-ui/api/tabs/)
*/
declare const Tabs: OverridableComponent<TabsTypeMap>;
export interface TabsActions {
updateIndicator(): void;
updateScrollButtons(): void;
}
export type TabsProps<
RootComponent extends React.ElementType = TabsTypeMap['defaultComponent'],
AdditionalProps = {},
> = OverrideProps<TabsTypeMap<AdditionalProps, RootComponent>, RootComponent> & {
component?: React.ElementType;
};
export default Tabs;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,126 @@
import * as React from 'react';
import { expectType } from '@mui/types';
import { createTheme } from '@mui/material/styles';
import Tabs from '@mui/material/Tabs';
import SvgIcon from '@mui/material/SvgIcon';
function testOnChange() {
function handleTabsChange(event: React.SyntheticEvent, tabsValue: unknown) {}
<Tabs onChange={handleTabsChange} />;
function handleElementChange(event: React.ChangeEvent) {}
<Tabs
// @ts-expect-error internally it's either FocusEvent or ClickEvent
onChange={handleElementChange}
/>;
}
function TabTest() {
return <Tabs TabIndicatorProps={{ style: { backgroundColor: 'green' } }} />;
}
function TabIndicatorSxTest() {
return <Tabs TabIndicatorProps={{ sx: {} }} />;
}
function SampleIcon() {
return (
<SvgIcon>
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
</SvgIcon>
);
}
// Test for slots and slotProps
<Tabs
value={0}
variant="scrollable"
scrollButtons
textColor="secondary"
slots={{
StartScrollButtonIcon: SampleIcon,
EndScrollButtonIcon: SampleIcon,
}}
slotProps={{
endScrollButtonIcon: (ownerState) => ({
'data-testid': 'test-label-scrollButtonEnd',
fontSize: ownerState.textColor === 'secondary' ? 'large' : 'small',
}),
startScrollButtonIcon: (ownerState) => ({
'data-testid': 'test-label-scrollButtonStart',
fontSize: ownerState.textColor === 'secondary' ? 'large' : 'small',
}),
}}
/>;
// Test for ref type
<Tabs
ref={(elem) => {
expectType<HTMLDivElement | null, typeof elem>(elem);
}}
/>;
<Tabs
slots={{
root: 'div',
scroller: 'div',
list: 'div',
scrollbar: 'div',
indicator: 'div',
scrollButtons: 'div',
startScrollButtonIcon: 'div',
endScrollButtonIcon: 'div',
}}
/>;
const CustomComponent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
(props, ref) => <div ref={ref} {...props} />,
);
<Tabs
slots={{
root: CustomComponent,
scroller: CustomComponent,
list: CustomComponent,
scrollbar: CustomComponent,
indicator: CustomComponent,
scrollButtons: CustomComponent,
startScrollButtonIcon: CustomComponent,
endScrollButtonIcon: CustomComponent,
}}
/>;
<Tabs
slotProps={{
root: {
className: 'flex',
},
scroller: {
className: 'flex',
},
list: {
className: 'flex',
},
scrollbar: {
className: 'flex',
},
indicator: {
className: 'flex',
sx: {
color: 'primary.main',
},
style: { backgroundColor: 'green' },
},
scrollButtons: {
className: 'flex',
disableRipple: true,
},
startScrollButtonIcon: {
className: 'flex',
fontSize: 'large',
},
endScrollButtonIcon: {
className: 'flex',
fontSize: 'large',
},
}}
/>;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
export { default } from './Tabs';
export * from './Tabs';
export { default as tabsClasses } from './tabsClasses';
export * from './tabsClasses';

View File

@@ -0,0 +1,4 @@
export { default } from './Tabs';
export { default as tabsClasses } from './tabsClasses';
export * from './tabsClasses';

View File

@@ -0,0 +1,60 @@
import generateUtilityClasses from '@mui/utils/generateUtilityClasses';
import generateUtilityClass from '@mui/utils/generateUtilityClass';
export interface TabsClasses {
/** Styles applied to the root element. */
root: string;
/** Styles applied to the root element if `orientation="vertical"`. */
vertical: string;
/** Styles applied to the flex container element. */
/** @deprecated use `list` instead. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */
flexContainer: string;
/** Styles applied to the flex container element if `orientation="vertical"`. */
/** @deprecated use a combination of `list` and `vertical` instead. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details. */
flexContainerVertical: string;
/** Styles applied to the list element. */
list: string;
/** Styles applied to the flex container element if `centered={true}` & `!variant="scrollable"`. */
centered: string;
/** Styles applied to the tablist element. */
scroller: string;
/** Styles applied to the tablist element if `!variant="scrollable"`. */
fixed: string;
/** Styles applied to the tablist element if `variant="scrollable"` and `orientation="horizontal"`. */
scrollableX: string;
/** Styles applied to the tablist element if `variant="scrollable"` and `orientation="vertical"`. */
scrollableY: string;
/** Styles applied to the tablist element if `variant="scrollable"` and `visibleScrollbar={false}`. */
hideScrollbar: string;
/** Styles applied to the ScrollButtonComponent component. */
scrollButtons: string;
/** Styles applied to the ScrollButtonComponent component if `allowScrollButtonsMobile={true}`. */
scrollButtonsHideMobile: string;
/** Styles applied to the TabIndicator component. */
indicator: string;
}
export type TabsClassKey = keyof TabsClasses;
export function getTabsUtilityClass(slot: string): string {
return generateUtilityClass('MuiTabs', slot);
}
const tabsClasses: TabsClasses = generateUtilityClasses('MuiTabs', [
'root',
'vertical',
'list',
'flexContainer',
'flexContainerVertical',
'centered',
'scroller',
'fixed',
'scrollableX',
'scrollableY',
'hideScrollbar',
'scrollButtons',
'scrollButtonsHideMobile',
'indicator',
]);
export default tabsClasses;