From 807f635a930f5ef1a87dfc8c8bde77a8c97b8730 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Thu, 2 Feb 2023 08:14:49 +0100 Subject: [PATCH] [docs] Add hooks API pages for MUI Base (#35828) --- docs/data/base/components/badge/badge.md | 1 + docs/data/base/components/button/button.md | 1 + .../components/form-control/form-control.md | 1 + docs/data/base/components/input/input.md | 1 + docs/data/base/components/menu/menu.md | 1 + docs/data/base/components/select/select.md | 1 + docs/data/base/components/slider/slider.md | 1 + .../data/base/components/snackbar/snackbar.md | 1 + docs/data/base/components/switch/switch.md | 1 + docs/data/base/components/tabs/tabs.md | 1 + docs/data/base/pages.ts | 2 +- docs/data/base/pagesApi.js | 15 + docs/pages/base/api/use-autocomplete.js | 23 + docs/pages/base/api/use-autocomplete.json | 237 ++++++++ docs/pages/base/api/use-badge.js | 19 + docs/pages/base/api/use-badge.json | 12 + docs/pages/base/api/use-button.js | 19 + docs/pages/base/api/use-button.json | 66 +++ .../api/use-form-control-unstyled-context.js | 23 + .../use-form-control-unstyled-context.json | 7 + docs/pages/base/api/use-input.js | 19 + docs/pages/base/api/use-input.json | 40 ++ docs/pages/base/api/use-menu-item.js | 23 + docs/pages/base/api/use-menu-item.json | 12 + docs/pages/base/api/use-menu.js | 19 + docs/pages/base/api/use-menu.json | 12 + docs/pages/base/api/use-select.js | 19 + docs/pages/base/api/use-select.json | 7 + docs/pages/base/api/use-slider.js | 19 + docs/pages/base/api/use-slider.json | 35 ++ docs/pages/base/api/use-snackbar.js | 23 + docs/pages/base/api/use-snackbar.json | 36 ++ docs/pages/base/api/use-switch.js | 19 + docs/pages/base/api/use-switch.json | 35 ++ docs/pages/base/api/use-tab-panel.js | 23 + docs/pages/base/api/use-tab-panel.json | 7 + docs/pages/base/api/use-tab.js | 19 + docs/pages/base/api/use-tab.json | 24 + docs/pages/base/api/use-tabs-list.js | 23 + docs/pages/base/api/use-tabs-list.json | 14 + docs/pages/base/api/use-tabs.js | 19 + docs/pages/base/api/use-tabs.json | 43 ++ docs/src/modules/components/ApiPage.js | 112 +--- docs/src/modules/components/HookApiPage.js | 173 ++++++ .../src/modules/components/PropertiesTable.js | 127 ++++ docs/src/modules/utils/helpers.ts | 2 +- .../use-autocomplete/use-autocomplete.json | 42 ++ .../api-docs/use-badge/use-badge.json | 1 + .../api-docs/use-button/use-button.json | 15 + .../use-form-control-unstyled-context.json | 1 + .../api-docs/use-input/use-input.json | 10 + .../api-docs/use-menu-item/use-menu-item.json | 1 + .../api-docs/use-menu/use-menu.json | 1 + .../api-docs/use-select/use-select.json | 1 + .../api-docs/use-slider/use-slider.json | 1 + .../api-docs/use-snackbar/use-snackbar.json | 11 + .../api-docs/use-switch/use-switch.json | 12 + .../api-docs/use-tab-panel/use-tab-panel.json | 1 + .../api-docs/use-tab/use-tab.json | 8 + .../api-docs/use-tabs-list/use-tabs-list.json | 5 + .../api-docs/use-tabs/use-tabs.json | 12 + docs/translations/translations.json | 4 + .../ApiBuilders/ComponentApiBuilder.ts | 7 +- .../ApiBuilders/HookApiBuilder.ts | 543 ++++++++++++++++++ packages/api-docs-builder/buildApi.ts | 38 +- packages/api-docs-builder/buildApiUtils.ts | 76 +++ .../utils/defaultParamsHandler.ts | 147 +++++ packages/api-docs-builder/utils/findHooks.ts | 32 ++ packages/markdown/parseMarkdown.js | 16 + packages/markdown/parseMarkdown.test.js | 4 + .../AutocompleteUnstyled/useAutocomplete.d.ts | 14 +- .../mui-base/src/BadgeUnstyled/useBadge.ts | 11 +- .../mui-base/src/ButtonUnstyled/useButton.ts | 19 +- .../src/ButtonUnstyled/useButton.types.ts | 31 + .../useFormControlUnstyledContext.ts | 11 +- .../mui-base/src/InputUnstyled/useInput.ts | 11 +- .../ListboxUnstyled/useControllableReducer.ts | 3 + .../src/ListboxUnstyled/useListbox.ts | 3 + .../src/MenuItemUnstyled/useMenuItem.ts | 15 +- packages/mui-base/src/MenuUnstyled/useMenu.ts | 11 +- .../mui-base/src/SelectUnstyled/useSelect.ts | 10 + .../mui-base/src/SliderUnstyled/useSlider.ts | 11 +- .../src/SnackbarUnstyled/useSnackbar.ts | 6 +- .../mui-base/src/SwitchUnstyled/useSwitch.ts | 6 +- .../src/TabPanelUnstyled/useTabPanel.ts | 15 +- packages/mui-base/src/TabUnstyled/useTab.ts | 15 +- .../src/TabsListUnstyled/useTabsList.ts | 15 +- packages/mui-base/src/TabsUnstyled/useTabs.ts | 15 +- packages/mui-base/src/utils/useSlotProps.ts | 1 + 89 files changed, 2407 insertions(+), 142 deletions(-) create mode 100644 docs/pages/base/api/use-autocomplete.js create mode 100644 docs/pages/base/api/use-autocomplete.json create mode 100644 docs/pages/base/api/use-badge.js create mode 100644 docs/pages/base/api/use-badge.json create mode 100644 docs/pages/base/api/use-button.js create mode 100644 docs/pages/base/api/use-button.json create mode 100644 docs/pages/base/api/use-form-control-unstyled-context.js create mode 100644 docs/pages/base/api/use-form-control-unstyled-context.json create mode 100644 docs/pages/base/api/use-input.js create mode 100644 docs/pages/base/api/use-input.json create mode 100644 docs/pages/base/api/use-menu-item.js create mode 100644 docs/pages/base/api/use-menu-item.json create mode 100644 docs/pages/base/api/use-menu.js create mode 100644 docs/pages/base/api/use-menu.json create mode 100644 docs/pages/base/api/use-select.js create mode 100644 docs/pages/base/api/use-select.json create mode 100644 docs/pages/base/api/use-slider.js create mode 100644 docs/pages/base/api/use-slider.json create mode 100644 docs/pages/base/api/use-snackbar.js create mode 100644 docs/pages/base/api/use-snackbar.json create mode 100644 docs/pages/base/api/use-switch.js create mode 100644 docs/pages/base/api/use-switch.json create mode 100644 docs/pages/base/api/use-tab-panel.js create mode 100644 docs/pages/base/api/use-tab-panel.json create mode 100644 docs/pages/base/api/use-tab.js create mode 100644 docs/pages/base/api/use-tab.json create mode 100644 docs/pages/base/api/use-tabs-list.js create mode 100644 docs/pages/base/api/use-tabs-list.json create mode 100644 docs/pages/base/api/use-tabs.js create mode 100644 docs/pages/base/api/use-tabs.json create mode 100644 docs/src/modules/components/HookApiPage.js create mode 100644 docs/src/modules/components/PropertiesTable.js create mode 100644 docs/translations/api-docs/use-autocomplete/use-autocomplete.json create mode 100644 docs/translations/api-docs/use-badge/use-badge.json create mode 100644 docs/translations/api-docs/use-button/use-button.json create mode 100644 docs/translations/api-docs/use-form-control-unstyled-context/use-form-control-unstyled-context.json create mode 100644 docs/translations/api-docs/use-input/use-input.json create mode 100644 docs/translations/api-docs/use-menu-item/use-menu-item.json create mode 100644 docs/translations/api-docs/use-menu/use-menu.json create mode 100644 docs/translations/api-docs/use-select/use-select.json create mode 100644 docs/translations/api-docs/use-slider/use-slider.json create mode 100644 docs/translations/api-docs/use-snackbar/use-snackbar.json create mode 100644 docs/translations/api-docs/use-switch/use-switch.json create mode 100644 docs/translations/api-docs/use-tab-panel/use-tab-panel.json create mode 100644 docs/translations/api-docs/use-tab/use-tab.json create mode 100644 docs/translations/api-docs/use-tabs-list/use-tabs-list.json create mode 100644 docs/translations/api-docs/use-tabs/use-tabs.json create mode 100644 packages/api-docs-builder/ApiBuilders/HookApiBuilder.ts create mode 100644 packages/api-docs-builder/utils/defaultParamsHandler.ts create mode 100644 packages/api-docs-builder/utils/findHooks.ts diff --git a/docs/data/base/components/badge/badge.md b/docs/data/base/components/badge/badge.md index 26295265b29580..c9ada698253385 100644 --- a/docs/data/base/components/badge/badge.md +++ b/docs/data/base/components/badge/badge.md @@ -2,6 +2,7 @@ product: base title: Unstyled React Badge component and hook components: BadgeUnstyled +hooks: useBadge githubLabel: 'component: badge' --- diff --git a/docs/data/base/components/button/button.md b/docs/data/base/components/button/button.md index 4d74420b2217d2..166fb8f365c160 100644 --- a/docs/data/base/components/button/button.md +++ b/docs/data/base/components/button/button.md @@ -2,6 +2,7 @@ product: base title: Unstyled React Button component and hook components: ButtonUnstyled +hooks: useButton githubLabel: 'component: button' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/button/ --- diff --git a/docs/data/base/components/form-control/form-control.md b/docs/data/base/components/form-control/form-control.md index 2801efc2411a99..3a5a9f9fd8a8b3 100644 --- a/docs/data/base/components/form-control/form-control.md +++ b/docs/data/base/components/form-control/form-control.md @@ -2,6 +2,7 @@ product: base title: Unstyled React Form Control component and hook components: FormControlUnstyled +hooks: useFormControlUnstyledContext githubLabel: 'component: FormControl' --- diff --git a/docs/data/base/components/input/input.md b/docs/data/base/components/input/input.md index 041a65ad0abc52..851dc5a8647ccb 100644 --- a/docs/data/base/components/input/input.md +++ b/docs/data/base/components/input/input.md @@ -2,6 +2,7 @@ product: base title: Unstyled React Input component and hook components: InputUnstyled +hooks: useInput githubLabel: 'component: input' --- diff --git a/docs/data/base/components/menu/menu.md b/docs/data/base/components/menu/menu.md index d0e917ca2c1cb4..1f0f1ff9ca5f06 100644 --- a/docs/data/base/components/menu/menu.md +++ b/docs/data/base/components/menu/menu.md @@ -2,6 +2,7 @@ product: base title: Unstyled React Menu components and hooks components: MenuUnstyled, MenuItemUnstyled +hooks: useMenu, useMenuItem githubLabel: 'component: menu' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/menubutton/ --- diff --git a/docs/data/base/components/select/select.md b/docs/data/base/components/select/select.md index d9563373b6c91f..5187597e8cfce0 100644 --- a/docs/data/base/components/select/select.md +++ b/docs/data/base/components/select/select.md @@ -2,6 +2,7 @@ product: base title: Unstyled React Select components and hook components: SelectUnstyled, MultiSelectUnstyled, OptionUnstyled, OptionGroupUnstyled +hooks: useSelect githubLabel: 'component: select' waiAria: https://www.w3.org/WAI/ARIA/apg/example-index/combobox/combobox-select-only.html --- diff --git a/docs/data/base/components/slider/slider.md b/docs/data/base/components/slider/slider.md index ca32ed42c0303a..2185e2db5236b1 100644 --- a/docs/data/base/components/slider/slider.md +++ b/docs/data/base/components/slider/slider.md @@ -2,6 +2,7 @@ product: base title: Unstyled React Slider component and hook components: SliderUnstyled +hooks: useSlider githubLabel: 'component: slider' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/slidertwothumb/ --- diff --git a/docs/data/base/components/snackbar/snackbar.md b/docs/data/base/components/snackbar/snackbar.md index c9d4aeefd351f1..22b4566a7ccb15 100644 --- a/docs/data/base/components/snackbar/snackbar.md +++ b/docs/data/base/components/snackbar/snackbar.md @@ -2,6 +2,7 @@ product: base title: Unstyled React Snackbar component and hook components: SnackbarUnstyled +hooks: useSnackbar githubLabel: 'component: snackbar' --- diff --git a/docs/data/base/components/switch/switch.md b/docs/data/base/components/switch/switch.md index 302264f756d2b5..f4ad5fb7555d75 100644 --- a/docs/data/base/components/switch/switch.md +++ b/docs/data/base/components/switch/switch.md @@ -2,6 +2,7 @@ product: base title: Unstyled React Switch component and hook components: SwitchUnstyled +hooks: useSwitch githubLabel: 'component: switch' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/switch/ --- diff --git a/docs/data/base/components/tabs/tabs.md b/docs/data/base/components/tabs/tabs.md index a92d229e379a79..c9ed200116e60b 100644 --- a/docs/data/base/components/tabs/tabs.md +++ b/docs/data/base/components/tabs/tabs.md @@ -2,6 +2,7 @@ product: base title: Unstyled React Tabs components components: TabsUnstyled, TabUnstyled, TabPanelUnstyled, TabsListUnstyled +hooks: useTab, useTabPanel, useTabs, useTabsList githubLabel: 'component: tabs' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/ --- diff --git a/docs/data/base/pages.ts b/docs/data/base/pages.ts index 9234b464b69437..27a02d7fc4ef28 100644 --- a/docs/data/base/pages.ts +++ b/docs/data/base/pages.ts @@ -73,7 +73,7 @@ const pages = [ ], }, { - title: 'Component API', + title: 'API', pathname: '/base/api', icon: 'CodeIcon', children: pagesApi, diff --git a/docs/data/base/pagesApi.js b/docs/data/base/pagesApi.js index e1dc27af074cb0..246c790c78b01a 100644 --- a/docs/data/base/pagesApi.js +++ b/docs/data/base/pagesApi.js @@ -24,4 +24,19 @@ module.exports = [ { pathname: '/base/api/tabs-unstyled' }, { pathname: '/base/api/tab-unstyled' }, { pathname: '/base/api/textarea-autosize' }, + { pathname: '/base/api/use-autocomplete' }, + { pathname: '/base/api/use-badge' }, + { pathname: '/base/api/use-button' }, + { pathname: '/base/api/use-form-control-unstyled-context' }, + { pathname: '/base/api/use-input' }, + { pathname: '/base/api/use-menu' }, + { pathname: '/base/api/use-menu-item' }, + { pathname: '/base/api/use-select' }, + { pathname: '/base/api/use-slider' }, + { pathname: '/base/api/use-snackbar' }, + { pathname: '/base/api/use-switch' }, + { pathname: '/base/api/use-tab' }, + { pathname: '/base/api/use-tab-panel' }, + { pathname: '/base/api/use-tabs' }, + { pathname: '/base/api/use-tabs-list' }, ]; diff --git a/docs/pages/base/api/use-autocomplete.js b/docs/pages/base/api/use-autocomplete.js new file mode 100644 index 00000000000000..1aff68548ec09e --- /dev/null +++ b/docs/pages/base/api/use-autocomplete.js @@ -0,0 +1,23 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-autocomplete.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docs/translations/api-docs/use-autocomplete', + false, + /use-autocomplete.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-autocomplete.json b/docs/pages/base/api/use-autocomplete.json new file mode 100644 index 00000000000000..452059b19ce211 --- /dev/null +++ b/docs/pages/base/api/use-autocomplete.json @@ -0,0 +1,237 @@ +{ + "parameters": { + "options": { + "type": { "name": "ReadonlyArray", "description": "Array of options." }, + "required": true + }, + "autoComplete": { + "type": { + "name": "boolean", + "description": "If `true`, the portion of the selected suggestion that has not been typed by the user,\nknown as the completion string, appears inline after the input cursor in the textbox.\nThe inline completion string is visually highlighted and has a selected state." + }, + "default": "false" + }, + "autoHighlight": { + "type": { + "name": "boolean", + "description": "If `true`, the first option is automatically highlighted." + }, + "default": "false" + }, + "autoSelect": { + "type": { + "name": "boolean", + "description": "If `true`, the selected option becomes the value of the input\nwhen the Autocomplete loses focus unless the user chooses\na different option or changes the character string in the input." + }, + "default": "false" + }, + "blurOnSelect": { + "type": { + "name": "'touch' | 'mouse' | true | false", + "description": "Control if the input should be blurred when an option is selected:\n\n- `false` the input is not blurred.\n- `true` the input is always blurred.\n- `touch` the input is blurred after a touch event.\n- `mouse` the input is blurred after a mouse event." + }, + "default": "false" + }, + "clearOnBlur": { + "type": { + "name": "boolean", + "description": "If `true`, the input's text is cleared on blur if no value is selected.\n\nSet to `true` if you want to help the user enter a new value.\nSet to `false` if you want to help the user resume their search." + }, + "default": "!props.freeSolo" + }, + "clearOnEscape": { + "type": { + "name": "boolean", + "description": "If `true`, clear all values when the user presses escape and the popup is closed." + }, + "default": "false" + }, + "componentName": { + "type": { + "name": "string", + "description": "The component name that is using this hook. Used for warnings." + } + }, + "defaultValue": { + "type": { + "name": "AutocompleteValue", + "description": "The default value. Use when the component is not controlled." + }, + "default": "props.multiple ? [] : null" + }, + "disableClearable": { + "type": { + "name": "DisableClearable", + "description": "If `true`, the input can't be cleared." + }, + "default": "false" + }, + "disableCloseOnSelect": { + "type": { + "name": "boolean", + "description": "If `true`, the popup won't close when a value is selected." + }, + "default": "false" + }, + "disabled": { + "type": { "name": "boolean", "description": "If `true`, the component is disabled." }, + "default": "false" + }, + "disabledItemsFocusable": { + "type": { + "name": "boolean", + "description": "If `true`, will allow focus on disabled items." + }, + "default": "false" + }, + "disableListWrap": { + "type": { + "name": "boolean", + "description": "If `true`, the list box in the popup will not wrap focus." + }, + "default": "false" + }, + "filterOptions": { + "type": { + "name": "(options: T[], state: FilterOptionsState) => T[]", + "description": "A function that determines the filtered options to be rendered on search." + } + }, + "filterSelectedOptions": { + "type": { + "name": "boolean", + "description": "If `true`, hide the selected options from the list box." + }, + "default": "false" + }, + "freeSolo": { + "type": { + "name": "FreeSolo", + "description": "If `true`, the Autocomplete is free solo, meaning that the user input is not bound to provided options." + }, + "default": "false" + }, + "getOptionDisabled": { + "type": { + "name": "(option: T) => boolean", + "description": "Used to determine the disabled state for a given option." + } + }, + "getOptionLabel": { + "type": { + "name": "(option: T | AutocompleteFreeSoloValueMapping) => string", + "description": "Used to determine the string value for a given option.\nIt's used to fill the input (and the list box options if `renderOption` is not provided).\n\nIf used in free solo mode, it must accept both the type of the options and a string." + }, + "default": "(option) => option.label ?? option" + }, + "groupBy": { + "type": { + "name": "(option: T) => string", + "description": "If provided, the options will be grouped under the returned string.\nThe groupBy value is also used as the text for group headings when `renderGroup` is not provided." + } + }, + "handleHomeEndKeys": { + "type": { + "name": "boolean", + "description": "If `true`, the component handles the \"Home\" and \"End\" keys when the popup is open.\nIt should move focus to the first option and last option, respectively." + }, + "default": "!props.freeSolo" + }, + "id": { + "type": { + "name": "string", + "description": "This prop is used to help implement the accessibility logic.\nIf you don't provide an id it will fall back to a randomly generated one." + } + }, + "includeInputInList": { + "type": { + "name": "boolean", + "description": "If `true`, the highlight can move to the input." + }, + "default": "false" + }, + "inputValue": { "type": { "name": "string", "description": "The input value." } }, + "isOptionEqualToValue": { + "type": { + "name": "(option: T, value: T) => boolean", + "description": "Used to determine if the option represents the given value.\nUses strict equality by default.\n⚠️ Both arguments need to be handled, an option can only match with one value." + } + }, + "multiple": { + "type": { + "name": "Multiple", + "description": "If `true`, `value` must be an array and the menu will support multiple selections." + }, + "default": "false" + }, + "onChange": { + "type": { + "name": "(event: React.SyntheticEvent, value: AutocompleteValue, reason: AutocompleteChangeReason, details?: AutocompleteChangeDetails) => void", + "description": "Callback fired when the value changes." + } + }, + "onClose": { + "type": { + "name": "(event: React.SyntheticEvent, reason: AutocompleteCloseReason) => void", + "description": "Callback fired when the popup requests to be closed.\nUse in controlled mode (see open)." + } + }, + "onHighlightChange": { + "type": { + "name": "(event: React.SyntheticEvent, option: T | null, reason: AutocompleteHighlightChangeReason) => void", + "description": "Callback fired when the highlight option changes." + } + }, + "onInputChange": { + "type": { + "name": "(event: React.SyntheticEvent, value: string, reason: AutocompleteInputChangeReason) => void", + "description": "Callback fired when the input value changes." + } + }, + "onOpen": { + "type": { + "name": "(event: React.SyntheticEvent) => void", + "description": "Callback fired when the popup requests to be opened.\nUse in controlled mode (see open)." + } + }, + "open": { "type": { "name": "boolean", "description": "If `true`, the component is shown." } }, + "openOnFocus": { + "type": { + "name": "boolean", + "description": "If `true`, the popup will open on input focus." + }, + "default": "false" + }, + "readOnly": { + "type": { + "name": "boolean", + "description": "If `true`, the component becomes readonly. It is also supported for multiple tags where the tag cannot be deleted." + }, + "default": "false" + }, + "selectOnFocus": { + "type": { + "name": "boolean", + "description": "If `true`, the input's text is selected on focus.\nIt helps the user clear the selected value." + }, + "default": "!props.freeSolo" + }, + "unstable_classNamePrefix": { + "type": { "name": "string", "description": "" }, + "default": "'Mui'" + }, + "unstable_isActiveElementInListbox": { + "type": { "name": "(listbox: React.RefObject) => boolean", "description": "" } + }, + "value": { + "type": { + "name": "AutocompleteValue", + "description": "The value of the autocomplete.\n\nThe value must have reference equality with the option in order to be selected.\nYou can customize the equality behavior with the `isOptionEqualToValue` prop." + } + } + }, + "returnValue": {}, + "name": "useAutocomplete", + "filename": "/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.js", + "demos": "
    " +} diff --git a/docs/pages/base/api/use-badge.js b/docs/pages/base/api/use-badge.js new file mode 100644 index 00000000000000..64a0510fed45bb --- /dev/null +++ b/docs/pages/base/api/use-badge.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-badge.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context('docs/translations/api-docs/use-badge', false, /use-badge.*.json$/); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-badge.json b/docs/pages/base/api/use-badge.json new file mode 100644 index 00000000000000..6c825b9208c105 --- /dev/null +++ b/docs/pages/base/api/use-badge.json @@ -0,0 +1,12 @@ +{ + "parameters": { + "badgeContent": { "type": { "name": "React.ReactNode", "description": "" } }, + "invisible": { "type": { "name": "boolean", "description": "" } }, + "max": { "type": { "name": "number", "description": "" } }, + "showZero": { "type": { "name": "boolean", "description": "" } } + }, + "returnValue": {}, + "name": "useBadge", + "filename": "/packages/mui-base/src/BadgeUnstyled/useBadge.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-button.js b/docs/pages/base/api/use-button.js new file mode 100644 index 00000000000000..69937621591725 --- /dev/null +++ b/docs/pages/base/api/use-button.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-button.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context('docs/translations/api-docs/use-button', false, /use-button.*.json$/); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-button.json b/docs/pages/base/api/use-button.json new file mode 100644 index 00000000000000..41469972d88dd0 --- /dev/null +++ b/docs/pages/base/api/use-button.json @@ -0,0 +1,66 @@ +{ + "parameters": { + "disabled": { + "type": { "name": "boolean", "description": "If `true`, the component is disabled." }, + "default": "false" + }, + "focusableWhenDisabled": { + "type": { + "name": "boolean", + "description": "If `true`, allows a disabled button to receive focus." + }, + "default": "false" + }, + "href": { "type": { "name": "string", "description": "" } }, + "onFocusVisible": { "type": { "name": "React.FocusEventHandler", "description": "" } }, + "ref": { "type": { "name": "React.Ref", "description": "" } }, + "tabIndex": { + "type": { "name": "NonNullable['tabIndex']>", "description": "" } + }, + "to": { "type": { "name": "string", "description": "" } }, + "type": { + "type": { + "name": "React.ButtonHTMLAttributes['type']", + "description": "Type attribute applied when the `component` is `button`." + }, + "default": "'button'" + } + }, + "returnValue": { + "active": { + "type": { "name": "boolean", "description": "If `true`, the component is active (pressed)." }, + "default": "false", + "required": true + }, + "disabled": { + "type": { "name": "boolean", "description": "If `true`, the component is disabled." }, + "default": "false", + "required": true + }, + "focusVisible": { + "type": { + "name": "boolean", + "description": "If `true`, the component is being focused using keyboard." + }, + "default": "false", + "required": true + }, + "getRootProps": { + "type": { + "name": "(otherHandlers?: TOther) => UseButtonRootSlotProps", + "description": "Resolver for the root slot's props." + }, + "required": true + }, + "setFocusVisible": { + "type": { + "name": "React.Dispatch>", + "description": "Callback for setting the `focusVisible` param." + }, + "required": true + } + }, + "name": "useButton", + "filename": "/packages/mui-base/src/ButtonUnstyled/useButton.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-form-control-unstyled-context.js b/docs/pages/base/api/use-form-control-unstyled-context.js new file mode 100644 index 00000000000000..bbbfa9c08e854b --- /dev/null +++ b/docs/pages/base/api/use-form-control-unstyled-context.js @@ -0,0 +1,23 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-form-control-unstyled-context.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docs/translations/api-docs/use-form-control-unstyled-context', + false, + /use-form-control-unstyled-context.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-form-control-unstyled-context.json b/docs/pages/base/api/use-form-control-unstyled-context.json new file mode 100644 index 00000000000000..1cc8e36b87ac2c --- /dev/null +++ b/docs/pages/base/api/use-form-control-unstyled-context.json @@ -0,0 +1,7 @@ +{ + "parameters": {}, + "returnValue": {}, + "name": "useFormControlUnstyledContext", + "filename": "/packages/mui-base/src/FormControlUnstyled/useFormControlUnstyledContext.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-input.js b/docs/pages/base/api/use-input.js new file mode 100644 index 00000000000000..0e68ff3a525281 --- /dev/null +++ b/docs/pages/base/api/use-input.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-input.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context('docs/translations/api-docs/use-input', false, /use-input.*.json$/); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-input.json b/docs/pages/base/api/use-input.json new file mode 100644 index 00000000000000..adfbec960b89e9 --- /dev/null +++ b/docs/pages/base/api/use-input.json @@ -0,0 +1,40 @@ +{ + "parameters": { + "defaultValue": { + "type": { + "name": "unknown", + "description": "The default value. Use when the component is not controlled." + } + }, + "disabled": { + "type": { + "name": "boolean", + "description": "If `true`, the component is disabled.\nThe prop defaults to the value (`false`) inherited from the parent FormControl component." + } + }, + "error": { + "type": { + "name": "boolean", + "description": "If `true`, the `input` will indicate an error by setting the `aria-invalid` attribute.\nThe prop defaults to the value (`false`) inherited from the parent FormControl component." + } + }, + "inputRef": { "type": { "name": "React.Ref", "description": "" } }, + "onBlur": { "type": { "name": "React.FocusEventHandler", "description": "" } }, + "onChange": { + "type": { "name": "React.ChangeEventHandler", "description": "" } + }, + "onClick": { "type": { "name": "React.MouseEventHandler", "description": "" } }, + "onFocus": { "type": { "name": "React.FocusEventHandler", "description": "" } }, + "required": { + "type": { + "name": "boolean", + "description": "If `true`, the `input` element is required.\nThe prop defaults to the value (`false`) inherited from the parent FormControl component." + } + }, + "value": { "type": { "name": "unknown", "description": "" } } + }, + "returnValue": {}, + "name": "useInput", + "filename": "/packages/mui-base/src/InputUnstyled/useInput.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-menu-item.js b/docs/pages/base/api/use-menu-item.js new file mode 100644 index 00000000000000..8257f26d86c6e4 --- /dev/null +++ b/docs/pages/base/api/use-menu-item.js @@ -0,0 +1,23 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-menu-item.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docs/translations/api-docs/use-menu-item', + false, + /use-menu-item.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-menu-item.json b/docs/pages/base/api/use-menu-item.json new file mode 100644 index 00000000000000..a153edeabd79a1 --- /dev/null +++ b/docs/pages/base/api/use-menu-item.json @@ -0,0 +1,12 @@ +{ + "parameters": { + "ref": { "type": { "name": "React.Ref", "description": "" }, "required": true }, + "disabled": { "type": { "name": "boolean", "description": "" } }, + "label": { "type": { "name": "string", "description": "" } }, + "onClick": { "type": { "name": "React.MouseEventHandler", "description": "" } } + }, + "returnValue": {}, + "name": "useMenuItem", + "filename": "/packages/mui-base/src/MenuItemUnstyled/useMenuItem.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-menu.js b/docs/pages/base/api/use-menu.js new file mode 100644 index 00000000000000..117cfbaf16986d --- /dev/null +++ b/docs/pages/base/api/use-menu.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-menu.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context('docs/translations/api-docs/use-menu', false, /use-menu.*.json$/); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-menu.json b/docs/pages/base/api/use-menu.json new file mode 100644 index 00000000000000..bdf43d9e8d24e5 --- /dev/null +++ b/docs/pages/base/api/use-menu.json @@ -0,0 +1,12 @@ +{ + "parameters": { + "listboxId": { "type": { "name": "string", "description": "" } }, + "listboxRef": { "type": { "name": "React.Ref", "description": "" } }, + "onClose": { "type": { "name": "() => void", "description": "" } }, + "open": { "type": { "name": "boolean", "description": "" } } + }, + "returnValue": {}, + "name": "useMenu", + "filename": "/packages/mui-base/src/MenuUnstyled/useMenu.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-select.js b/docs/pages/base/api/use-select.js new file mode 100644 index 00000000000000..0a6e408f0ed543 --- /dev/null +++ b/docs/pages/base/api/use-select.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-select.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context('docs/translations/api-docs/use-select', false, /use-select.*.json$/); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-select.json b/docs/pages/base/api/use-select.json new file mode 100644 index 00000000000000..07ef584de05724 --- /dev/null +++ b/docs/pages/base/api/use-select.json @@ -0,0 +1,7 @@ +{ + "parameters": {}, + "returnValue": {}, + "name": "useSelect", + "filename": "/packages/mui-base/src/SelectUnstyled/useSelect.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-slider.js b/docs/pages/base/api/use-slider.js new file mode 100644 index 00000000000000..5515708d4886f2 --- /dev/null +++ b/docs/pages/base/api/use-slider.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-slider.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context('docs/translations/api-docs/use-slider', false, /use-slider.*.json$/); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-slider.json b/docs/pages/base/api/use-slider.json new file mode 100644 index 00000000000000..d4723bb47f13b3 --- /dev/null +++ b/docs/pages/base/api/use-slider.json @@ -0,0 +1,35 @@ +{ + "parameters": { + "ref": { "type": { "name": "React.Ref", "description": "" }, "required": true }, + "aria-labelledby": { "type": { "name": "string", "description": "" } }, + "defaultValue": { "type": { "name": "number | number[]", "description": "" } }, + "disabled": { "type": { "name": "boolean", "description": "" } }, + "disableSwap": { "type": { "name": "boolean", "description": "" } }, + "isRtl": { "type": { "name": "boolean", "description": "" } }, + "marks": { "type": { "name": "boolean | Mark[]", "description": "" } }, + "max": { "type": { "name": "number", "description": "" } }, + "min": { "type": { "name": "number", "description": "" } }, + "name": { "type": { "name": "string", "description": "" } }, + "onChange": { + "type": { + "name": "(event: Event, value: number | number[], activeThumb: number) => void", + "description": "" + } + }, + "onChangeCommitted": { + "type": { + "name": "(event: React.SyntheticEvent | Event, value: number | number[]) => void", + "description": "" + } + }, + "orientation": { "type": { "name": "'horizontal' | 'vertical'", "description": "" } }, + "scale": { "type": { "name": "(value: number) => number", "description": "" } }, + "step": { "type": { "name": "number | null", "description": "" } }, + "tabIndex": { "type": { "name": "number", "description": "" } }, + "value": { "type": { "name": "number | number[]", "description": "" } } + }, + "returnValue": {}, + "name": "useSlider", + "filename": "/packages/mui-base/src/SliderUnstyled/useSlider.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-snackbar.js b/docs/pages/base/api/use-snackbar.js new file mode 100644 index 00000000000000..38b6b38c1bb46c --- /dev/null +++ b/docs/pages/base/api/use-snackbar.js @@ -0,0 +1,23 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-snackbar.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docs/translations/api-docs/use-snackbar', + false, + /use-snackbar.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-snackbar.json b/docs/pages/base/api/use-snackbar.json new file mode 100644 index 00000000000000..f4681170d07369 --- /dev/null +++ b/docs/pages/base/api/use-snackbar.json @@ -0,0 +1,36 @@ +{ + "parameters": { + "autoHideDuration": { + "type": { + "name": "number | null", + "description": "The number of milliseconds to wait before automatically calling the\n`onClose` function. `onClose` should then set the state of the `open`\nprop to hide the Snackbar. This behavior is disabled by default with\nthe `null` value." + }, + "default": "null" + }, + "disableWindowBlurListener": { + "type": { + "name": "boolean", + "description": "If `true`, the `autoHideDuration` timer will expire even if the window is not focused." + }, + "default": "false" + }, + "onClose": { + "type": { + "name": "(event: React.SyntheticEvent | Event | null, reason: SnackbarCloseReason) => void", + "description": "Callback fired when the component requests to be closed.\nTypically `onClose` is used to set state in the parent component,\nwhich is used to control the `Snackbar` `open` prop.\nThe `reason` parameter can optionally be used to control the response to `onClose`,\nfor example ignoring `clickaway`." + } + }, + "open": { "type": { "name": "boolean", "description": "If `true`, the component is shown." } }, + "ref": { "type": { "name": "React.Ref", "description": "" } }, + "resumeHideDuration": { + "type": { + "name": "number", + "description": "The number of milliseconds to wait before dismissing after user interaction.\nIf `autoHideDuration` prop isn't specified, it does nothing.\nIf `autoHideDuration` prop is specified but `resumeHideDuration` isn't,\nwe default to `autoHideDuration / 2` ms." + } + } + }, + "returnValue": {}, + "name": "useSnackbar", + "filename": "/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-switch.js b/docs/pages/base/api/use-switch.js new file mode 100644 index 00000000000000..2a15efd84815f8 --- /dev/null +++ b/docs/pages/base/api/use-switch.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-switch.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context('docs/translations/api-docs/use-switch', false, /use-switch.*.json$/); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-switch.json b/docs/pages/base/api/use-switch.json new file mode 100644 index 00000000000000..fe80263174cb87 --- /dev/null +++ b/docs/pages/base/api/use-switch.json @@ -0,0 +1,35 @@ +{ + "parameters": { + "checked": { + "type": { "name": "boolean", "description": "If `true`, the component is checked." } + }, + "defaultChecked": { + "type": { + "name": "boolean", + "description": "The default checked state. Use when the component is not controlled." + } + }, + "disabled": { + "type": { "name": "boolean", "description": "If `true`, the component is disabled." } + }, + "onBlur": { "type": { "name": "React.FocusEventHandler", "description": "" } }, + "onChange": { + "type": { + "name": "React.ChangeEventHandler", + "description": "Callback fired when the state is changed." + } + }, + "onFocus": { "type": { "name": "React.FocusEventHandler", "description": "" } }, + "onFocusVisible": { "type": { "name": "React.FocusEventHandler", "description": "" } }, + "readOnly": { + "type": { "name": "boolean", "description": "If `true`, the component is read only." } + }, + "required": { + "type": { "name": "boolean", "description": "If `true`, the `input` element is required." } + } + }, + "returnValue": {}, + "name": "useSwitch", + "filename": "/packages/mui-base/src/SwitchUnstyled/useSwitch.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-tab-panel.js b/docs/pages/base/api/use-tab-panel.js new file mode 100644 index 00000000000000..c970a34b83fc5b --- /dev/null +++ b/docs/pages/base/api/use-tab-panel.js @@ -0,0 +1,23 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-tab-panel.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docs/translations/api-docs/use-tab-panel', + false, + /use-tab-panel.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-tab-panel.json b/docs/pages/base/api/use-tab-panel.json new file mode 100644 index 00000000000000..ee2265ddcbf3d4 --- /dev/null +++ b/docs/pages/base/api/use-tab-panel.json @@ -0,0 +1,7 @@ +{ + "parameters": {}, + "returnValue": {}, + "name": "useTabPanel", + "filename": "/packages/mui-base/src/TabPanelUnstyled/useTabPanel.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-tab.js b/docs/pages/base/api/use-tab.js new file mode 100644 index 00000000000000..0d904e9df2d0e9 --- /dev/null +++ b/docs/pages/base/api/use-tab.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-tab.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context('docs/translations/api-docs/use-tab', false, /use-tab.*.json$/); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-tab.json b/docs/pages/base/api/use-tab.json new file mode 100644 index 00000000000000..28d44bc0470d59 --- /dev/null +++ b/docs/pages/base/api/use-tab.json @@ -0,0 +1,24 @@ +{ + "parameters": { + "ref": { "type": { "name": "React.Ref", "description": "" }, "required": true }, + "disabled": { "type": { "name": "boolean", "description": "" } }, + "onChange": { + "type": { + "name": "(event: React.SyntheticEvent, value: number | string) => void", + "description": "Callback invoked when new value is being set." + } + }, + "onClick": { "type": { "name": "React.MouseEventHandler", "description": "" } }, + "onFocus": { "type": { "name": "React.FocusEventHandler", "description": "" } }, + "value": { + "type": { + "name": "number | string", + "description": "You can provide your own value. Otherwise, we fall back to the child position index." + } + } + }, + "returnValue": {}, + "name": "useTab", + "filename": "/packages/mui-base/src/TabUnstyled/useTab.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-tabs-list.js b/docs/pages/base/api/use-tabs-list.js new file mode 100644 index 00000000000000..449d7971c96cc6 --- /dev/null +++ b/docs/pages/base/api/use-tabs-list.js @@ -0,0 +1,23 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-tabs-list.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docs/translations/api-docs/use-tabs-list', + false, + /use-tabs-list.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-tabs-list.json b/docs/pages/base/api/use-tabs-list.json new file mode 100644 index 00000000000000..3aba7c402dfa1e --- /dev/null +++ b/docs/pages/base/api/use-tabs-list.json @@ -0,0 +1,14 @@ +{ + "parameters": { + "ref": { "type": { "name": "React.Ref", "description": "" }, "required": true }, + "aria-label": { "type": { "name": "string", "description": "" } }, + "aria-labelledby": { "type": { "name": "string", "description": "" } }, + "children": { + "type": { "name": "React.ReactNode", "description": "The content of the component." } + } + }, + "returnValue": {}, + "name": "useTabsList", + "filename": "/packages/mui-base/src/TabsListUnstyled/useTabsList.ts", + "demos": "" +} diff --git a/docs/pages/base/api/use-tabs.js b/docs/pages/base/api/use-tabs.js new file mode 100644 index 00000000000000..376c14c382f399 --- /dev/null +++ b/docs/pages/base/api/use-tabs.js @@ -0,0 +1,19 @@ +import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './use-tabs.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context('docs/translations/api-docs/use-tabs', false, /use-tabs.*.json$/); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; diff --git a/docs/pages/base/api/use-tabs.json b/docs/pages/base/api/use-tabs.json new file mode 100644 index 00000000000000..5991f95ff20474 --- /dev/null +++ b/docs/pages/base/api/use-tabs.json @@ -0,0 +1,43 @@ +{ + "parameters": { + "defaultValue": { + "type": { + "name": "string | number | false", + "description": "The default value. Use when the component is not controlled." + } + }, + "direction": { + "type": { "name": "'ltr' | 'rtl'", "description": "The direction of the text." }, + "default": "'ltr'" + }, + "onChange": { + "type": { + "name": "(event: React.SyntheticEvent, value: number | string | boolean) => void", + "description": "Callback invoked when new value is being set." + } + }, + "orientation": { + "type": { + "name": "'horizontal' | 'vertical'", + "description": "The component orientation (layout flow direction)." + }, + "default": "'horizontal'" + }, + "selectionFollowsFocus": { + "type": { + "name": "boolean", + "description": "If `true` the selected tab changes on focus. Otherwise it only\nchanges on activation." + } + }, + "value": { + "type": { + "name": "string | number | false", + "description": "The value of the currently selected `Tab`.\nIf you don't want any selected `Tab`, you can set this prop to `false`." + } + } + }, + "returnValue": {}, + "name": "useTabs", + "filename": "/packages/mui-base/src/TabsUnstyled/useTabs.ts", + "demos": "" +} diff --git a/docs/src/modules/components/ApiPage.js b/docs/src/modules/components/ApiPage.js index 60b8d82a4b7ed8..a621915b7d7aa5 100644 --- a/docs/src/modules/components/ApiPage.js +++ b/docs/src/modules/components/ApiPage.js @@ -1,123 +1,15 @@ /* eslint-disable react/no-danger */ import * as React from 'react'; import PropTypes from 'prop-types'; -import clsx from 'clsx'; import { exactProp } from '@mui/utils'; -import { alpha, styled } from '@mui/material/styles'; -import Alert from '@mui/material/Alert'; import Typography from '@mui/material/Typography'; import { useTranslate, useUserLanguage } from 'docs/src/modules/utils/i18n'; +import PropertiesTable from 'docs/src/modules/components/PropertiesTable'; import HighlightedCode from 'docs/src/modules/components/HighlightedCode'; import MarkdownElement from 'docs/src/modules/components/MarkdownElement'; import AppLayoutDocs from 'docs/src/modules/components/AppLayoutDocs'; import Ad from 'docs/src/modules/components/Ad'; -const Asterisk = styled('abbr')(({ theme }) => ({ color: theme.palette.error.main })); - -const Wrapper = styled('div')({ - overflow: 'hidden', -}); -const Table = styled('table')(({ theme }) => { - const contentColor = - theme.palette.mode === 'dark' - ? alpha(theme.palette.primaryDark[900], 1) - : 'rgba(255, 255, 255, 1)'; - const contentColorTransparent = - theme.palette.mode === 'dark' - ? alpha(theme.palette.primaryDark[900], 0) - : 'rgba(255, 255, 255, 0)'; - const shadowColor = theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.7)' : 'rgba(0,0,0,0.2)'; - return { - borderRadius: 10, - background: ` - linear-gradient(to right, ${contentColor} 5%, ${contentColorTransparent}), - linear-gradient(to right, ${contentColorTransparent}, ${contentColor} 100%) 100%, - linear-gradient(to right, ${shadowColor}, rgba(0, 0, 0, 0) 5%), - linear-gradient(to left, ${shadowColor}, rgba(0, 0, 0, 0) 5%)`, - backgroundAttachment: 'local, local, scroll, scroll', - // the above background create thin line on the left and right sides of the table - // as a workaround, use negative margin with overflow `hidden` on the parent - marginLeft: -1, - marginRight: -1, - }; -}); - -function PropsTable(props) { - const { componentProps, propDescriptions } = props; - const t = useTranslate(); - - return ( - - - - - - - - - - - - {Object.entries(componentProps).map(([propName, propData]) => { - const typeDescription = propData.type.description || propData.type.name; - const propDefault = propData.default; - return ( - propData.description !== '@ignore' && ( - - - - - - - ) - ); - })} - -
    {t('api-docs.name')}{t('api-docs.type')}{t('api-docs.default')}{t('api-docs.description')}
    - - {propName} - {propData.required && ( - - * - - )} - - - - - {propDefault && {propDefault}} - - {propData.deprecated && ( - - {t('api-docs.deprecated')} - {propData.deprecationInfo && ' - '} - {propData.deprecationInfo && ( - - )} - - )} -
    -
    -
    - ); -} - -PropsTable.propTypes = { - componentProps: PropTypes.object.isRequired, - propDescriptions: PropTypes.object.isRequired, -}; - function ClassesTable(props) { const { componentStyles, classDescriptions } = props; const t = useTranslate(); @@ -348,7 +240,7 @@ import { ${componentName} } from '${source}';`} )}

    - +
    {cssComponent && ( diff --git a/docs/src/modules/components/HookApiPage.js b/docs/src/modules/components/HookApiPage.js new file mode 100644 index 00000000000000..942f9ef5dc5eb0 --- /dev/null +++ b/docs/src/modules/components/HookApiPage.js @@ -0,0 +1,173 @@ +/* eslint-disable react/no-danger */ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { exactProp } from '@mui/utils'; +import Typography from '@mui/material/Typography'; +import { useTranslate, useUserLanguage } from 'docs/src/modules/utils/i18n'; +import PropertiesTable from 'docs/src/modules/components/PropertiesTable'; +import HighlightedCode from 'docs/src/modules/components/HighlightedCode'; +import MarkdownElement from 'docs/src/modules/components/MarkdownElement'; +import AppLayoutDocs from 'docs/src/modules/components/AppLayoutDocs'; +import Ad from 'docs/src/modules/components/Ad'; + +function getTranslatedHeader(t, header) { + const translations = { + demos: t('api-docs.demos'), + import: t('api-docs.import'), + 'hook-name': t('api-docs.hookName'), + parameters: t('api-docs.parameters'), + 'return-value': t('api-docs.returnValue'), + }; + + // TODO Drop runtime type-checking once we type-check this file + if (!translations.hasOwnProperty(header)) { + throw new TypeError( + `Unable to translate header '${header}'. Did you mean one of '${Object.keys( + translations, + ).join("', '")}'`, + ); + } + + return translations[header] || header; +} + +function Heading(props) { + const { hash, level: Level = 'h2' } = props; + const t = useTranslate(); + + return ( + + {getTranslatedHeader(t, hash)} + + + + + + + ); +} + +Heading.propTypes = { + hash: PropTypes.string.isRequired, + level: PropTypes.string, +}; + +export default function ApiPage(props) { + const { descriptions, disableAd = false, pageContent } = props; + const t = useTranslate(); + const userLanguage = useUserLanguage(); + + const { demos, filename, inheritance, name: hookName, parameters, returnValue } = pageContent; + + const { + hookDescription, + hookDescriptionToc = [], + parametersDescriptions, + returnValueDescriptions, + } = descriptions[userLanguage]; + const description = t('api-docs.hooksPageDescription').replace(/{{name}}/, hookName); + + const source = filename + .replace(/\/packages\/mui(-(.+?))?\/src/, (match, dash, pkg) => `@mui/${pkg}`) + // convert things like `/Table/Table.js` to `` + .replace(/\/([^/]+)\/\1\.(js|tsx)$/, ''); + + // Prefer linking the .tsx or .d.ts for the "Edit this page" link. + const apiSourceLocation = filename.replace('.js', '.d.ts'); + + function createTocEntry(sectionName) { + return { + text: getTranslatedHeader(t, sectionName), + hash: sectionName, + children: [ + ...(sectionName === 'props' && inheritance + ? [{ text: t('api-docs.inheritance'), hash: 'inheritance', children: [] }] + : []), + ], + }; + } + + const toc = [ + createTocEntry('demos'), + createTocEntry('import'), + ...hookDescriptionToc, + createTocEntry('parameters'), + createTocEntry('return-value'), + ].filter(Boolean); + + return ( + + +

    {hookName} API

    + + {description} + {disableAd ? null : } + + +
    For examples and details on the usage of this React hook, visit the demo pages:

    + ${demos}`, + }} + /> + + + {/* TODO: Add this once the hooks are in dedicated folders */} + {/* */} + {hookDescription ? ( + +
    +
    + +
    + ) : null} + + + + +
    + {/* TODO: Add section for the hook output type */} + + + + + + + + ); +} + +ApiPage.propTypes = { + descriptions: PropTypes.object.isRequired, + disableAd: PropTypes.bool, + pageContent: PropTypes.object.isRequired, +}; + +if (process.env.NODE_ENV !== 'production') { + ApiPage.propTypes = exactProp(ApiPage.propTypes); +} diff --git a/docs/src/modules/components/PropertiesTable.js b/docs/src/modules/components/PropertiesTable.js new file mode 100644 index 00000000000000..76b9e837814ebc --- /dev/null +++ b/docs/src/modules/components/PropertiesTable.js @@ -0,0 +1,127 @@ +/* eslint-disable react/no-danger */ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import Alert from '@mui/material/Alert'; +import { alpha, styled } from '@mui/material/styles'; +import { useTranslate } from 'docs/src/modules/utils/i18n'; + +const Asterisk = styled('abbr')(({ theme }) => ({ color: theme.palette.error.main })); + +const Wrapper = styled('div')({ + overflow: 'hidden', +}); +const Table = styled('table')(({ theme }) => { + const contentColor = + theme.palette.mode === 'dark' + ? alpha(theme.palette.primaryDark[900], 1) + : 'rgba(255, 255, 255, 1)'; + const contentColorTransparent = + theme.palette.mode === 'dark' + ? alpha(theme.palette.primaryDark[900], 0) + : 'rgba(255, 255, 255, 0)'; + const shadowColor = theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.7)' : 'rgba(0,0,0,0.2)'; + return { + borderRadius: 10, + background: ` + linear-gradient(to right, ${contentColor} 5%, ${contentColorTransparent}), + linear-gradient(to right, ${contentColorTransparent}, ${contentColor} 100%) 100%, + linear-gradient(to right, ${shadowColor}, rgba(0, 0, 0, 0) 5%), + linear-gradient(to left, ${shadowColor}, rgba(0, 0, 0, 0) 5%)`, + backgroundAttachment: 'local, local, scroll, scroll', + // the above background create thin line on the left and right sides of the table + // as a workaround, use negative margin with overflow `hidden` on the parent + marginLeft: -1, + marginRight: -1, + }; +}); + +export default function PropertiesTable(props) { + const { properties, propertiesDescriptions, showOptionalAbbr = false } = props; + const t = useTranslate(); + + return ( + + + + + + + + + + + + {Object.entries(properties).map(([propName, propData]) => { + let typeName = propData.type.name; + typeName = typeName + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + const propDefault = propData.default; + return ( + propData.description !== '@ignore' && ( + + + + + + + ) + ); + })} + +
    {t('api-docs.name')}{t('api-docs.type')}{t('api-docs.default')}{t('api-docs.description')}
    + + {propName} + {propData.required && !showOptionalAbbr && ( + + * + + )} + {!propData.required && showOptionalAbbr && ( + + ? + + )} + + + + + {propDefault && {propDefault}} + + {propData.deprecated && ( + + {t('api-docs.deprecated')} + {propData.deprecationInfo && ' - '} + {propData.deprecationInfo && ( + + )} + + )} +
    +
    +
    + ); +} + +PropertiesTable.propTypes = { + properties: PropTypes.object.isRequired, + propertiesDescriptions: PropTypes.object.isRequired, + showOptionalAbbr: PropTypes.bool, +}; diff --git a/docs/src/modules/utils/helpers.ts b/docs/src/modules/utils/helpers.ts index 8139f09f08a3cd..c9c6c8d282011e 100644 --- a/docs/src/modules/utils/helpers.ts +++ b/docs/src/modules/utils/helpers.ts @@ -35,7 +35,7 @@ export function pageToTitle(page: Page): string | null { // TODO support more than React component API (PascalCase) if (path.indexOf('/api/') !== -1) { - return pascalCase(name); + return name.startsWith('use') ? camelCase(name) : pascalCase(name); } return titleize(name); diff --git a/docs/translations/api-docs/use-autocomplete/use-autocomplete.json b/docs/translations/api-docs/use-autocomplete/use-autocomplete.json new file mode 100644 index 00000000000000..365b442d731342 --- /dev/null +++ b/docs/translations/api-docs/use-autocomplete/use-autocomplete.json @@ -0,0 +1,42 @@ +{ + "hookDescription": "", + "parametersDescriptions": { + "autoComplete": "If true, the portion of the selected suggestion that has not been typed by the user,\nknown as the completion string, appears inline after the input cursor in the textbox.\nThe inline completion string is visually highlighted and has a selected state.", + "autoHighlight": "If true, the first option is automatically highlighted.", + "autoSelect": "If true, the selected option becomes the value of the input\nwhen the Autocomplete loses focus unless the user chooses\na different option or changes the character string in the input.", + "blurOnSelect": "Control if the input should be blurred when an option is selected:\n\n- false the input is not blurred.\n- true the input is always blurred.\n- touch the input is blurred after a touch event.\n- mouse the input is blurred after a mouse event.", + "clearOnBlur": "If true, the input's text is cleared on blur if no value is selected.\n\nSet to true if you want to help the user enter a new value.\nSet to false if you want to help the user resume their search.", + "clearOnEscape": "If true, clear all values when the user presses escape and the popup is closed.", + "componentName": "The component name that is using this hook. Used for warnings.", + "defaultValue": "The default value. Use when the component is not controlled.", + "disableClearable": "If true, the input can't be cleared.", + "disableCloseOnSelect": "If true, the popup won't close when a value is selected.", + "disabled": "If true, the component is disabled.", + "disabledItemsFocusable": "If true, will allow focus on disabled items.", + "disableListWrap": "If true, the list box in the popup will not wrap focus.", + "filterOptions": "A function that determines the filtered options to be rendered on search.", + "filterSelectedOptions": "If true, hide the selected options from the list box.", + "freeSolo": "If true, the Autocomplete is free solo, meaning that the user input is not bound to provided options.", + "getOptionDisabled": "Used to determine the disabled state for a given option.", + "getOptionLabel": "Used to determine the string value for a given option.\nIt's used to fill the input (and the list box options if renderOption is not provided).\n\nIf used in free solo mode, it must accept both the type of the options and a string.", + "groupBy": "If provided, the options will be grouped under the returned string.\nThe groupBy value is also used as the text for group headings when renderGroup is not provided.", + "handleHomeEndKeys": "If true, the component handles the \"Home\" and \"End\" keys when the popup is open.\nIt should move focus to the first option and last option, respectively.", + "id": "This prop is used to help implement the accessibility logic.\nIf you don't provide an id it will fall back to a randomly generated one.", + "includeInputInList": "If true, the highlight can move to the input.", + "inputValue": "The input value.", + "isOptionEqualToValue": "Used to determine if the option represents the given value.\nUses strict equality by default.\n⚠️ Both arguments need to be handled, an option can only match with one value.", + "multiple": "If true, value must be an array and the menu will support multiple selections.", + "onChange": "Callback fired when the value changes.", + "onClose": "Callback fired when the popup requests to be closed.\nUse in controlled mode (see open).", + "onHighlightChange": "Callback fired when the highlight option changes.", + "onInputChange": "Callback fired when the input value changes.", + "onOpen": "Callback fired when the popup requests to be opened.\nUse in controlled mode (see open).", + "open": "If true, the component is shown.", + "openOnFocus": "If true, the popup will open on input focus.", + "options": "Array of options.", + "readOnly": "If true, the component becomes readonly. It is also supported for multiple tags where the tag cannot be deleted.", + "selectOnFocus": "If true, the input's text is selected on focus.\nIt helps the user clear the selected value.", + "value": "The value of the autocomplete.\n\nThe value must have reference equality with the option in order to be selected.\nYou can customize the equality behavior with the isOptionEqualToValue prop." + }, + "returnValueDescriptions": {} +} diff --git a/docs/translations/api-docs/use-badge/use-badge.json b/docs/translations/api-docs/use-badge/use-badge.json new file mode 100644 index 00000000000000..e3eb65c6e43006 --- /dev/null +++ b/docs/translations/api-docs/use-badge/use-badge.json @@ -0,0 +1 @@ +{ "hookDescription": "", "parametersDescriptions": {}, "returnValueDescriptions": {} } diff --git a/docs/translations/api-docs/use-button/use-button.json b/docs/translations/api-docs/use-button/use-button.json new file mode 100644 index 00000000000000..a1fefeab7fc5a6 --- /dev/null +++ b/docs/translations/api-docs/use-button/use-button.json @@ -0,0 +1,15 @@ +{ + "hookDescription": "", + "parametersDescriptions": { + "disabled": "If true, the component is disabled.", + "focusableWhenDisabled": "If true, allows a disabled button to receive focus.", + "type": "Type attribute applied when the component is button." + }, + "returnValueDescriptions": { + "active": "If true, the component is active (pressed).", + "disabled": "If true, the component is disabled.", + "focusVisible": "If true, the component is being focused using keyboard.", + "getRootProps": "Resolver for the root slot's props.", + "setFocusVisible": "Callback for setting the focusVisible param." + } +} diff --git a/docs/translations/api-docs/use-form-control-unstyled-context/use-form-control-unstyled-context.json b/docs/translations/api-docs/use-form-control-unstyled-context/use-form-control-unstyled-context.json new file mode 100644 index 00000000000000..e3eb65c6e43006 --- /dev/null +++ b/docs/translations/api-docs/use-form-control-unstyled-context/use-form-control-unstyled-context.json @@ -0,0 +1 @@ +{ "hookDescription": "", "parametersDescriptions": {}, "returnValueDescriptions": {} } diff --git a/docs/translations/api-docs/use-input/use-input.json b/docs/translations/api-docs/use-input/use-input.json new file mode 100644 index 00000000000000..73535952943abe --- /dev/null +++ b/docs/translations/api-docs/use-input/use-input.json @@ -0,0 +1,10 @@ +{ + "hookDescription": "", + "parametersDescriptions": { + "defaultValue": "The default value. Use when the component is not controlled.", + "disabled": "If true, the component is disabled.\nThe prop defaults to the value (false) inherited from the parent FormControl component.", + "error": "If true, the input will indicate an error by setting the aria-invalid attribute.\nThe prop defaults to the value (false) inherited from the parent FormControl component.", + "required": "If true, the input element is required.\nThe prop defaults to the value (false) inherited from the parent FormControl component." + }, + "returnValueDescriptions": {} +} diff --git a/docs/translations/api-docs/use-menu-item/use-menu-item.json b/docs/translations/api-docs/use-menu-item/use-menu-item.json new file mode 100644 index 00000000000000..e3eb65c6e43006 --- /dev/null +++ b/docs/translations/api-docs/use-menu-item/use-menu-item.json @@ -0,0 +1 @@ +{ "hookDescription": "", "parametersDescriptions": {}, "returnValueDescriptions": {} } diff --git a/docs/translations/api-docs/use-menu/use-menu.json b/docs/translations/api-docs/use-menu/use-menu.json new file mode 100644 index 00000000000000..e3eb65c6e43006 --- /dev/null +++ b/docs/translations/api-docs/use-menu/use-menu.json @@ -0,0 +1 @@ +{ "hookDescription": "", "parametersDescriptions": {}, "returnValueDescriptions": {} } diff --git a/docs/translations/api-docs/use-select/use-select.json b/docs/translations/api-docs/use-select/use-select.json new file mode 100644 index 00000000000000..e3eb65c6e43006 --- /dev/null +++ b/docs/translations/api-docs/use-select/use-select.json @@ -0,0 +1 @@ +{ "hookDescription": "", "parametersDescriptions": {}, "returnValueDescriptions": {} } diff --git a/docs/translations/api-docs/use-slider/use-slider.json b/docs/translations/api-docs/use-slider/use-slider.json new file mode 100644 index 00000000000000..e3eb65c6e43006 --- /dev/null +++ b/docs/translations/api-docs/use-slider/use-slider.json @@ -0,0 +1 @@ +{ "hookDescription": "", "parametersDescriptions": {}, "returnValueDescriptions": {} } diff --git a/docs/translations/api-docs/use-snackbar/use-snackbar.json b/docs/translations/api-docs/use-snackbar/use-snackbar.json new file mode 100644 index 00000000000000..9e7fa070b5372d --- /dev/null +++ b/docs/translations/api-docs/use-snackbar/use-snackbar.json @@ -0,0 +1,11 @@ +{ + "hookDescription": "The basic building block for creating custom snackbar.", + "parametersDescriptions": { + "autoHideDuration": "The number of milliseconds to wait before automatically calling the\nonClose function. onClose should then set the state of the open\nprop to hide the Snackbar. This behavior is disabled by default with\nthe null value.", + "disableWindowBlurListener": "If true, the autoHideDuration timer will expire even if the window is not focused.", + "onClose": "Callback fired when the component requests to be closed.\nTypically onClose is used to set state in the parent component,\nwhich is used to control the Snackbar open prop.\nThe reason parameter can optionally be used to control the response to onClose,\nfor example ignoring clickaway.", + "open": "If true, the component is shown.", + "resumeHideDuration": "The number of milliseconds to wait before dismissing after user interaction.\nIf autoHideDuration prop isn't specified, it does nothing.\nIf autoHideDuration prop is specified but resumeHideDuration isn't,\nwe default to autoHideDuration / 2 ms." + }, + "returnValueDescriptions": {} +} diff --git a/docs/translations/api-docs/use-switch/use-switch.json b/docs/translations/api-docs/use-switch/use-switch.json new file mode 100644 index 00000000000000..d86c01fefbf08d --- /dev/null +++ b/docs/translations/api-docs/use-switch/use-switch.json @@ -0,0 +1,12 @@ +{ + "hookDescription": "The basic building block for creating custom switches.", + "parametersDescriptions": { + "checked": "If true, the component is checked.", + "defaultChecked": "The default checked state. Use when the component is not controlled.", + "disabled": "If true, the component is disabled.", + "onChange": "Callback fired when the state is changed.", + "readOnly": "If true, the component is read only.", + "required": "If true, the input element is required." + }, + "returnValueDescriptions": {} +} diff --git a/docs/translations/api-docs/use-tab-panel/use-tab-panel.json b/docs/translations/api-docs/use-tab-panel/use-tab-panel.json new file mode 100644 index 00000000000000..e3eb65c6e43006 --- /dev/null +++ b/docs/translations/api-docs/use-tab-panel/use-tab-panel.json @@ -0,0 +1 @@ +{ "hookDescription": "", "parametersDescriptions": {}, "returnValueDescriptions": {} } diff --git a/docs/translations/api-docs/use-tab/use-tab.json b/docs/translations/api-docs/use-tab/use-tab.json new file mode 100644 index 00000000000000..ad187ec0174b4d --- /dev/null +++ b/docs/translations/api-docs/use-tab/use-tab.json @@ -0,0 +1,8 @@ +{ + "hookDescription": "", + "parametersDescriptions": { + "onChange": "Callback invoked when new value is being set.", + "value": "You can provide your own value. Otherwise, we fall back to the child position index." + }, + "returnValueDescriptions": {} +} diff --git a/docs/translations/api-docs/use-tabs-list/use-tabs-list.json b/docs/translations/api-docs/use-tabs-list/use-tabs-list.json new file mode 100644 index 00000000000000..db744fcd075302 --- /dev/null +++ b/docs/translations/api-docs/use-tabs-list/use-tabs-list.json @@ -0,0 +1,5 @@ +{ + "hookDescription": "", + "parametersDescriptions": { "children": "The content of the component." }, + "returnValueDescriptions": {} +} diff --git a/docs/translations/api-docs/use-tabs/use-tabs.json b/docs/translations/api-docs/use-tabs/use-tabs.json new file mode 100644 index 00000000000000..6c6ac26d60e1dd --- /dev/null +++ b/docs/translations/api-docs/use-tabs/use-tabs.json @@ -0,0 +1,12 @@ +{ + "hookDescription": "", + "parametersDescriptions": { + "defaultValue": "The default value. Use when the component is not controlled.", + "direction": "The direction of the text.", + "onChange": "Callback invoked when new value is being set.", + "orientation": "The component orientation (layout flow direction).", + "selectionFollowsFocus": "If true the selected tab changes on focus. Otherwise it only\nchanges on activation.", + "value": "The value of the currently selected Tab.\nIf you don't want any selected Tab, you can set this prop to false." + }, + "returnValueDescriptions": {} +} diff --git a/docs/translations/translations.json b/docs/translations/translations.json index 105963b93ad5ee..bcde93ca7c1e2b 100644 --- a/docs/translations/translations.json +++ b/docs/translations/translations.json @@ -2,12 +2,14 @@ "adblock": "If you don't mind tech-related ads (no tracking or remarketing), and want to keep us running, please whitelist MUI in your blocker.", "api-docs": { "componentName": "Component name", + "hookName": "Hook name", "cssComponent": "As a CSS utility, the {{name}} component also supports all system properties. You can use them as props directly on the component.", "default": "Default", "demos": "Demos", "deprecated": "Deprecated", "description": "Description", "globalClass": "Global class", + "hooksPageDescription": "API reference docs for the {{name}} hook. Learn about the input parameters and other APIs of this exported module.", "import": "Import", "importDifference": "You can learn about the difference by reading this guide on minimizing bundle size.", "inheritance": "Inheritance", @@ -19,6 +21,8 @@ "overrideStylesStyledComponent": "", "pageDescription": "API reference docs for the React {{name}} component. Learn about the props, CSS, and other APIs of this exported module.", "props": "Props", + "parameters": "Parameters", + "returnValue": "Return value", "refNotHeld": "The component cannot hold a ref.", "refRootElement": "The ref is forwarded to the root element.", "ruleName": "Rule name", diff --git a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts index 476e43391e274d..4ca4c6e306261e 100644 --- a/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts +++ b/packages/api-docs-builder/ApiBuilders/ComponentApiBuilder.ts @@ -91,7 +91,10 @@ export function writePrettifiedFile( * why the source includes relative url. We transform them to absolute urls with * this method. */ -async function computeApiDescription(api: ReactApi, options: { host: string }): Promise { +export async function computeApiDescription( + api: { description: ReactApi['description'] }, + options: { host: string }, +): Promise { const { host } = options; const file = await remark() .use(function docsLinksAttacher() { @@ -278,7 +281,7 @@ function extractClassConditions(descriptions: any) { * @example toGitHubPath('/home/user/material-ui/packages/Accordion') === '/packages/Accordion' * @example toGitHubPath('C:\\Development\material-ui\packages\Accordion') === '/packages/Accordion' */ -function toGitHubPath(filepath: string): string { +export function toGitHubPath(filepath: string): string { return `/${path.relative(process.cwd(), filepath).replace(/\\/g, '/')}`; } diff --git a/packages/api-docs-builder/ApiBuilders/HookApiBuilder.ts b/packages/api-docs-builder/ApiBuilders/HookApiBuilder.ts new file mode 100644 index 00000000000000..98bfdd93a0cfb7 --- /dev/null +++ b/packages/api-docs-builder/ApiBuilders/HookApiBuilder.ts @@ -0,0 +1,543 @@ +import * as ts from 'typescript'; +import * as prettier from 'prettier'; +import * as astTypes from 'ast-types'; +import * as _ from 'lodash'; +import * as babel from '@babel/core'; +import traverse from '@babel/traverse'; +import { defaultHandlers, parse as docgenParse, ReactDocgenApi } from 'react-docgen'; +import { mkdirSync, readFileSync, writeFileSync } from 'fs'; +import path from 'path'; +import kebabCase from 'lodash/kebabCase'; +import upperFirst from 'lodash/upperFirst'; +import { renderInline as renderMarkdownInline } from '@mui/markdown'; +import { LANGUAGES } from 'docs/config'; +import { toGitHubPath, writePrettifiedFile, computeApiDescription } from './ComponentApiBuilder'; +import { HookInfo } from '../buildApiUtils'; +import { TypeScriptProject } from '../utils/createTypeScriptProject'; +import muiDefaultParamsHandler from '../utils/defaultParamsHandler'; + +interface ParsedProperty { + name: string; + description: string; + tags: { [tagName: string]: ts.JSDocTagInfo }; + required: boolean; + typeStr: string; +} + +export const getSymbolDescription = (symbol: ts.Symbol, project: TypeScriptProject) => + symbol + .getDocumentationComment(project.checker) + .flatMap((comment) => comment.text.split('\n')) + .filter((line) => !line.startsWith('TODO')) + .join('\n'); + +export const formatType = (rawType: string) => { + if (!rawType) { + return ''; + } + + const prefix = 'type FakeType = '; + const signatureWithTypeName = `${prefix}${rawType}`; + + const prettifiedSignatureWithTypeName = prettier.format(signatureWithTypeName, { + printWidth: 999, + singleQuote: true, + semi: false, + trailingComma: 'none', + parser: 'typescript', + }); + + return prettifiedSignatureWithTypeName.slice(prefix.length).replace(/\n$/, ''); +}; + +export const getSymbolJSDocTags = (symbol: ts.Symbol) => + Object.fromEntries(symbol.getJsDocTags().map((tag) => [tag.name, tag])); + +export const stringifySymbol = (symbol: ts.Symbol, project: TypeScriptProject) => { + let rawType: string; + + const declaration = symbol.declarations?.[0]; + if (declaration && ts.isPropertySignature(declaration)) { + rawType = declaration.type?.getText() ?? ''; + } else { + rawType = project.checker.typeToString( + project.checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration!), + symbol.valueDeclaration, + ts.TypeFormatFlags.NoTruncation, + ); + } + + return formatType(rawType); +}; + +const parseProperty = (propertySymbol: ts.Symbol, project: TypeScriptProject): ParsedProperty => ({ + name: propertySymbol.name, + description: getSymbolDescription(propertySymbol, project), + tags: getSymbolJSDocTags(propertySymbol), + required: !propertySymbol.declarations?.find(ts.isPropertySignature)?.questionToken, + typeStr: stringifySymbol(propertySymbol, project), +}); + +export interface ReactApi extends ReactDocgenApi { + demos: ReturnType; + EOL: string; + filename: string; + apiPathname: string; + parameters?: ParsedProperty[]; + returnValue?: ParsedProperty[]; + /** + * hook name + * @example 'useButton' + */ + name: string; + description: string; + /** + * result of path.readFileSync from the `filename` in utf-8 + */ + src: string; + parametersTable: _.Dictionary<{ + default: string | undefined; + required: boolean | undefined; + type: { name: string | undefined; description: string | undefined }; + deprecated: true | undefined; + deprecationInfo: string | undefined; + }>; + returnValueTable: _.Dictionary<{ + default: string | undefined; + required: boolean | undefined; + type: { name: string | undefined; description: string | undefined }; + deprecated: true | undefined; + deprecationInfo: string | undefined; + }>; + translations: { + hookDescription: string; + parametersDescriptions: { [key: string]: string | undefined }; + returnValueDescriptions: { [key: string]: string | undefined }; + }; +} + +/** + * Add demos & API comment block to type definitions, e.g.: + * /** + * * Demos: + * * + * * - [Unstyled Button](https://mui.com/base/react-button/) + * * + * * API: + * * + * * - [useButton API](https://mui.com/base/api/use-button/) + */ +async function annotateHookDefinition(api: ReactApi) { + const HOST = 'https://mui.com'; + + const typesFilename = api.filename.replace(/\.js$/, '.d.ts'); + const typesSource = readFileSync(typesFilename, { encoding: 'utf8' }); + const typesAST = await babel.parseAsync(typesSource, { + configFile: false, + filename: typesFilename, + presets: [require.resolve('@babel/preset-typescript')], + }); + if (typesAST === null) { + throw new Error('No AST returned from babel.'); + } + + let start = 0; + let end = null; + traverse(typesAST, { + ExportDefaultDeclaration(babelPath) { + /** + * export default function Menu() {} + */ + let node: babel.Node = babelPath.node; + if (node.declaration.type === 'Identifier') { + // declare const Menu: {}; + // export default Menu; + if (babel.types.isIdentifier(babelPath.node.declaration)) { + const bindingId = babelPath.node.declaration.name; + const binding = babelPath.scope.bindings[bindingId]; + + // The JSDoc MUST be located at the declaration + if (babel.types.isFunctionDeclaration(binding.path.node)) { + // For function declarations the binding is equal to the declaration + // /** + // */ + // function Component() {} + node = binding.path.node; + } else { + // For variable declarations the binding points to the declarator. + // /** + // */ + // const Component = () => {} + node = binding.path.parentPath!.node; + } + } + } + + const { leadingComments } = node; + const leadingCommentBlocks = + leadingComments != null + ? leadingComments.filter(({ type }) => type === 'CommentBlock') + : null; + const jsdocBlock = leadingCommentBlocks != null ? leadingCommentBlocks[0] : null; + if (leadingCommentBlocks != null && leadingCommentBlocks.length > 1) { + throw new Error( + `Should only have a single leading jsdoc block but got ${ + leadingCommentBlocks.length + }:\n${leadingCommentBlocks + .map(({ type, value }, index) => `#${index} (${type}): ${value}`) + .join('\n')}`, + ); + } + if (jsdocBlock?.start != null && jsdocBlock?.end != null) { + start = jsdocBlock.start; + end = jsdocBlock.end; + } else if (node.start != null) { + start = node.start - 1; + end = start; + } + }, + }); + + if (end === null || start === 0) { + throw new TypeError( + "Don't know where to insert the jsdoc block. Probably no `default export` found", + ); + } + + const markdownLines = (await computeApiDescription(api, { host: HOST })).split('\n'); + // Ensure a newline between manual and generated description. + if (markdownLines[markdownLines.length - 1] !== '') { + markdownLines.push(''); + } + + if (api.demos && api.demos.length > 0) { + markdownLines.push( + 'Demos:', + '', + ...api.demos.map((item) => { + return `- [${item.name}](${ + item.demoPathname.startsWith('http') ? item.demoPathname : `${HOST}${item.demoPathname}` + })`; + }), + '', + ); + } + + markdownLines.push( + 'API:', + '', + `- [${api.name} API](${ + api.apiPathname.startsWith('http') ? api.apiPathname : `${HOST}${api.apiPathname}` + })`, + ); + + const jsdoc = `/**\n${markdownLines + .map((line) => (line.length > 0 ? ` * ${line}` : ` *`)) + .join('\n')}\n */`; + const typesSourceNew = typesSource.slice(0, start) + jsdoc + typesSource.slice(end); + writeFileSync(typesFilename, typesSourceNew, { encoding: 'utf8' }); +} + +const attachTable = ( + reactApi: ReactApi, + params: ParsedProperty[], + tableName: 'parametersTable' | 'returnValueTable', +) => { + const propErrors: Array<[propName: string, error: Error]> = []; + const parameters: ReactApi[typeof tableName] = params + .map((p) => { + const { name: propName, ...propDescriptor } = p; + let prop: Omit | null; + try { + prop = propDescriptor; + } catch (error) { + propErrors.push([propName, error as Error]); + prop = null; + } + if (prop === null) { + // have to delete `componentProps.undefined` later + return [] as any; + } + + const defaultTag = propDescriptor.tags?.default; + const defaultValue: string | undefined = defaultTag?.text?.[0]?.text; + const requiredProp = prop.required; + + const deprecation = (propDescriptor.description || '').match(/@deprecated(\s+(?.*))?/); + + return { + [propName]: { + type: { + name: propDescriptor.typeStr, + description: propDescriptor.description ?? undefined, + }, + default: defaultValue, + // undefined values are not serialized => saving some bytes + required: requiredProp || undefined, + deprecated: !!deprecation || undefined, + deprecationInfo: + renderMarkdownInline(deprecation?.groups?.info || '').trim() || undefined, + }, + }; + }) + .reduce((acc, curr) => ({ ...acc, ...curr }), {}) as unknown as ReactApi['parametersTable']; + if (propErrors.length > 0) { + throw new Error( + `There were errors creating prop descriptions:\n${propErrors + .map(([propName, error]) => { + return ` - ${propName}: ${error}`; + }) + .join('\n')}`, + ); + } + + // created by returning the `[]` entry + delete parameters.undefined; + + reactApi[tableName] = parameters; +}; + +const generateTranslationDescription = (description: string) => { + return description + .replace(/\n@default.*$/, '') + .replace(/`([a-z]|[A-Z]|\()/g, '$1') + .replace(/`/g, ''); +}; + +const attachTranslations = (reactApi: ReactApi) => { + const translations: ReactApi['translations'] = { + hookDescription: reactApi.description, + parametersDescriptions: {}, + returnValueDescriptions: {}, + }; + + (reactApi.parameters ?? []).forEach(({ name: propName, description }) => { + if (description) { + translations.parametersDescriptions[propName] = generateTranslationDescription(description); + } + }); + + (reactApi.returnValue ?? []).forEach(({ name: propName, description }) => { + if (description) { + translations.returnValueDescriptions[propName] = generateTranslationDescription(description); + } + }); + + reactApi.translations = translations; +}; + +const generateApiPage = (outputDirectory: string, reactApi: ReactApi) => { + /** + * Gather the metadata needed for the component's API page. + */ + const pageContent = { + // Sorted by required DESC, name ASC + parameters: _.fromPairs( + Object.entries(reactApi.parametersTable).sort(([aName, aData], [bName, bData]) => { + if ((aData.required && bData.required) || (!aData.required && !bData.required)) { + return aName.localeCompare(bName); + } + if (aData.required) { + return -1; + } + return 1; + }), + ), + returnValue: _.fromPairs( + Object.entries(reactApi.returnValueTable).sort(([aName, aData], [bName, bData]) => { + if ((aData.required && bData.required) || (!aData.required && !bData.required)) { + return aName.localeCompare(bName); + } + if (aData.required) { + return -1; + } + return 1; + }), + ), + name: reactApi.name, + filename: toGitHubPath(reactApi.filename), + demos: `
      ${reactApi.demos + .map((item) => `
    • ${item.name}
    • `) + .join('\n')}
    `, + }; + + writePrettifiedFile( + path.resolve(outputDirectory, `${kebabCase(reactApi.name)}.json`), + JSON.stringify(pageContent), + ); + + writePrettifiedFile( + path.resolve(outputDirectory, `${kebabCase(reactApi.name)}.js`), + `import * as React from 'react'; +import HookApiPage from 'docs/src/modules/components/HookApiPage'; +import mapApiPageTranslations from 'docs/src/modules/utils/mapApiPageTranslations'; +import jsonPageContent from './${kebabCase(reactApi.name)}.json'; + +export default function Page(props) { + const { descriptions, pageContent } = props; + return ; +} + +Page.getInitialProps = () => { + const req = require.context( + 'docs/translations/api-docs/${kebabCase(reactApi.name)}', + false, + /${kebabCase(reactApi.name)}.*.json$/, + ); + const descriptions = mapApiPageTranslations(req); + + return { + descriptions, + pageContent: jsonPageContent, + }; +}; +`.replace(/\r?\n/g, reactApi.EOL), + ); +}; + +const generateApiTranslations = (outputDirectory: string, reactApi: ReactApi) => { + const hookName = reactApi.name; + const apiDocsTranslationPath = path.resolve(outputDirectory, kebabCase(hookName)); + function resolveApiDocsTranslationsComponentLanguagePath( + language: (typeof LANGUAGES)[0], + ): string { + const languageSuffix = language === 'en' ? '' : `-${language}`; + + return path.join(apiDocsTranslationPath, `${kebabCase(hookName)}${languageSuffix}.json`); + } + + mkdirSync(apiDocsTranslationPath, { + mode: 0o777, + recursive: true, + }); + + writePrettifiedFile( + resolveApiDocsTranslationsComponentLanguagePath('en'), + JSON.stringify(reactApi.translations), + ); + + LANGUAGES.forEach((language) => { + if (language !== 'en') { + try { + writePrettifiedFile( + resolveApiDocsTranslationsComponentLanguagePath(language), + JSON.stringify(reactApi.translations), + undefined, + { flag: 'wx' }, + ); + } catch (error) { + // File exists + } + } + }); +}; + +const extractInfoFromInterface = ( + interfaceName: string, + project: TypeScriptProject, +): ParsedProperty[] => { + // Generate the params + let result: ParsedProperty[] = []; + + try { + const exportedSymbol = project.exports[interfaceName]; + const type = project.checker.getDeclaredTypeOfSymbol(exportedSymbol); + // @ts-ignore + const typeDeclaration = type?.symbol?.declarations?.[0]; + if (!typeDeclaration || !ts.isInterfaceDeclaration(typeDeclaration)) { + return []; + } + + const properties: Record = {}; + // @ts-ignore + const propertiesOnProject = type.getProperties(); + + // @ts-ignore + propertiesOnProject.forEach((propertySymbol) => { + properties[propertySymbol.name] = parseProperty(propertySymbol, project); + }); + + result = Object.values(properties) + .filter((property) => !property.tags.ignore) + .sort((a, b) => a.name.localeCompare(b.name)); + } catch (e) { + console.error(`No declaration for ${interfaceName}`); + } + + return result; +}; + +const generateHookApi = async (hooksInfo: HookInfo, project: TypeScriptProject) => { + const { filename, name, apiPathname, apiPagesDirectory, getDemos, readFile, skipApiGeneration } = + hooksInfo; + + const { shouldSkip, EOL, src } = readFile(); + + if (shouldSkip) { + return null; + } + + const reactApi: ReactApi = docgenParse( + src, + (ast) => { + let node; + astTypes.visit(ast, { + visitFunctionDeclaration: (functionPath) => { + if (functionPath.node?.id?.name === name) { + node = functionPath; + } + return false; + }, + }); + return node; + }, + defaultHandlers.concat(muiDefaultParamsHandler), + { filename }, + ); + + const parameters = extractInfoFromInterface(`${upperFirst(name)}Parameters`, project); + const returnValue = extractInfoFromInterface(`${upperFirst(name)}ReturnValue`, project); + + // Ignore what we might have generated in `annotateHookDefinition` + const annotatedDescriptionMatch = reactApi.description.match(/(Demos|API):\r?\n\r?\n/); + if (annotatedDescriptionMatch !== null) { + reactApi.description = reactApi.description.slice(0, annotatedDescriptionMatch.index).trim(); + } + reactApi.filename = filename; + reactApi.name = name; + reactApi.apiPathname = apiPathname; + reactApi.EOL = EOL; + reactApi.demos = getDemos(); + if (reactApi.demos.length === 0) { + // TODO: Enable this error once all public hooks are documented + // throw new Error( + // 'Unable to find demos. \n' + + // `Be sure to include \`hooks: ${reactApi.name}\` in the markdown pages where the \`${reactApi.name}\` hook is relevant. ` + + // 'Every public hook should have a demo. ', + // ); + } + + attachTable(reactApi, parameters, 'parametersTable'); + reactApi.parameters = parameters; + + attachTable(reactApi, returnValue, 'returnValueTable'); + reactApi.returnValue = returnValue; + + attachTranslations(reactApi); + + // eslint-disable-next-line no-console + console.log('Built API docs for', reactApi.name); + + if (!skipApiGeneration) { + // Generate pages, json and translations + generateApiTranslations(path.join(process.cwd(), 'docs/translations/api-docs'), reactApi); + generateApiPage(apiPagesDirectory, reactApi); + + // Add comment about demo & api links to the component hook file + await annotateHookDefinition(reactApi); + } + + return reactApi; +}; + +export default generateHookApi; diff --git a/packages/api-docs-builder/buildApi.ts b/packages/api-docs-builder/buildApi.ts index d2c4c84e1ea7b8..9bb1c732711404 100644 --- a/packages/api-docs-builder/buildApi.ts +++ b/packages/api-docs-builder/buildApi.ts @@ -4,10 +4,13 @@ import path from 'path'; import kebabCase from 'lodash/kebabCase'; import * as yargs from 'yargs'; import findComponents from './utils/findComponents'; +import findHooks from './utils/findHooks'; import { ComponentInfo, + HookInfo, getMaterialComponentInfo, getBaseComponentInfo, + getBaseHookInfo, getSystemComponentInfo, extractApiPage, } from './buildApiUtils'; @@ -15,6 +18,7 @@ import generateComponentApi, { writePrettifiedFile, ReactApi, } from './ApiBuilders/ComponentApiBuilder'; +import generateHookApi from './ApiBuilders/HookApiBuilder'; import { createTypeScriptProject, TypeScriptProject } from './utils/createTypeScriptProject'; const apiDocsTranslationsDirectory = path.resolve('docs', 'translations', 'api-docs'); @@ -111,6 +115,7 @@ interface Settings { getProjects: () => TypeScriptProject[]; getApiPages: () => Array<{ pathname: string }>; getComponentInfo: (filename: string) => ComponentInfo; + getHookInfo?: (filename: string) => HookInfo; } const SETTINGS: Settings[] = [ @@ -146,6 +151,7 @@ const SETTINGS: Settings[] = [ ], getApiPages: () => findApiPages('docs/pages/base/api'), getComponentInfo: getBaseComponentInfo, + getHookInfo: getBaseHookInfo, }, { output: { @@ -181,7 +187,7 @@ async function run(argv: yargs.ArgumentsCamelCase) { mkdirSync(manifestDir, { recursive: true }); } - const componentBuilds = projects.flatMap((project) => { + const apiBuilds = projects.flatMap((project) => { const projectComponents = findComponents(path.join(project.rootPath, 'src')).filter( (component) => { if ( @@ -198,7 +204,14 @@ async function run(argv: yargs.ArgumentsCamelCase) { }, ); - return projectComponents.map(async (component) => { + const projectHooks = findHooks(path.join(project.rootPath, 'src')).filter((hook) => { + if (grep === null) { + return true; + } + return grep.test(hook.filename); + }); + + const componentsBuilds = projectComponents.map(async (component) => { try { const { filename } = component; const componentInfo = setting.getComponentInfo(filename); @@ -211,9 +224,27 @@ async function run(argv: yargs.ArgumentsCamelCase) { throw error; } }); + + const hooksBuilds = projectHooks.map(async (hook) => { + if (!setting.getHookInfo) { + return []; + } + try { + const { filename } = hook; + const hookInfo = setting.getHookInfo(filename); + + mkdirSync(hookInfo.apiPagesDirectory, { mode: 0o777, recursive: true }); + return generateHookApi(hookInfo, project); + } catch (error: any) { + error.message = `${path.relative(process.cwd(), hook.filename)}: ${error.message}`; + throw error; + } + }); + + return [...componentsBuilds, ...hooksBuilds]; }); - const builds = await Promise.allSettled(componentBuilds); + const builds = await Promise.allSettled(apiBuilds); const fails = builds.filter( (promise): promise is PromiseRejectedResult => promise.status === 'rejected', @@ -226,6 +257,7 @@ async function run(argv: yargs.ArgumentsCamelCase) { process.exit(1); } + // @ts-ignore ignore hooks builds for now allBuilds = [...allBuilds, ...builds]; const source = `module.exports = ${JSON.stringify(setting.getApiPages())}`; diff --git a/packages/api-docs-builder/buildApiUtils.ts b/packages/api-docs-builder/buildApiUtils.ts index 66ddb814f4e292..a36d102c6b15e5 100644 --- a/packages/api-docs-builder/buildApiUtils.ts +++ b/packages/api-docs-builder/buildApiUtils.ts @@ -94,6 +94,27 @@ export type ComponentInfo = { isSystemComponent?: boolean; }; +export type HookInfo = { + /** + * Full path to the file + */ + filename: string; + /** + * Hook name + */ + name: string; + apiPathname: string; + readFile: () => { + src: string; + spread: boolean; + shouldSkip: boolean; + EOL: string; + }; + getDemos: () => Array<{ name: string; demoPathname: string }>; + apiPagesDirectory: string; + skipApiGeneration?: boolean; +}; + const migratedBaseComponents = [ 'BadgeUnstyled', 'ButtonUnstyled', @@ -198,6 +219,22 @@ function findBaseDemos( })); } +function findBaseHooksDemos( + hookName: string, + pagesMarkdown: ReadonlyArray<{ pathname: string; title: string; hooks: readonly string[] }>, +) { + return pagesMarkdown + .filter((page) => page.hooks && page.hooks.includes(hookName)) + .map((page) => ({ + name: page.title, + demoPathname: page.pathname.match(/material\//) + ? replaceComponentLinks(`${page.pathname.replace(/^\/material/, '')}/`) + : `${page.pathname.replace('/components/', '/react-')}/#hook${ + page.hooks?.length > 1 ? 's' : '' + }`, + })); +} + interface PageMarkdown { pathname: string; title: string; @@ -281,6 +318,45 @@ export const getBaseComponentInfo = (filename: string): ComponentInfo => { }; }; +export const getBaseHookInfo = (filename: string): HookInfo => { + const { name } = extractPackageFile(filename); + let srcInfo: null | ReturnType = null; + if (!name) { + throw new Error(`Could not find the hook name from: ${filename}`); + } + const result = { + filename, + name, + apiPathname: `/base/api/${kebabCase(name)}/`, + apiPagesDirectory: path.join(process.cwd(), `docs/pages/base/api`), + readFile() { + srcInfo = parseFile(filename); + return srcInfo; + }, + getDemos: () => { + const allMarkdowns = findPagesMarkdownNew() + .filter((markdown) => { + if (migratedBaseComponents.some((component) => filename.includes(component))) { + return markdown.filename.match(/[\\/]data[\\/]base[\\/]/); + } + return true; + }) + .map((markdown) => { + const markdownContent = fs.readFileSync(markdown.filename, 'utf8'); + const markdownHeaders = getHeaders(markdownContent) as any; + + return { + ...markdown, + title: getTitle(markdownContent), + hooks: markdownHeaders.hooks as string[], + }; + }); + return findBaseHooksDemos(name, allMarkdowns); + }, + }; + return result; +}; + export const getSystemComponentInfo = (filename: string): ComponentInfo => { const { name } = extractPackageFile(filename); let srcInfo: null | ReturnType = null; diff --git a/packages/api-docs-builder/utils/defaultParamsHandler.ts b/packages/api-docs-builder/utils/defaultParamsHandler.ts new file mode 100644 index 00000000000000..2a8643902d82b8 --- /dev/null +++ b/packages/api-docs-builder/utils/defaultParamsHandler.ts @@ -0,0 +1,147 @@ +import { namedTypes as types } from 'ast-types'; +import { parse as parseDoctrine, Annotation } from 'doctrine'; +import { utils as docgenUtils, NodePath, Documentation, Importer, Handler } from 'react-docgen'; + +const { getPropertyName, printValue, resolveToValue } = docgenUtils; + +// based on https://github.com/reactjs/react-docgen/blob/735f39ef784312f4c0e740d4bfb812f0a7acd3d5/src/handlers/defaultPropsHandler.js#L1-L112 +// adjusted for material-ui getThemedProps + +function getDefaultValue(propertyPath: NodePath, importer: Importer) { + if (!types.AssignmentPattern.check(propertyPath.get('value').node)) { + return null; + } + + let path: NodePath = propertyPath.get('value', 'right'); + let node = path.node; + + let defaultValue: string | undefined; + if (types.Literal.check(path.node)) { + // @ts-expect-error TODO upstream fix + defaultValue = node.raw; + } else { + if (types.AssignmentPattern.check(path.node)) { + path = resolveToValue(path.get('right'), importer); + } else { + path = resolveToValue(path, importer); + } + if (types.ImportDeclaration.check(path.node)) { + if (types.TSAsExpression.check(node)) { + node = node.expression; + } + if (!types.Identifier.check(node)) { + const locationHint = + node.loc != null ? `${node.loc.start.line}:${node.loc.start.column}` : 'unknown location'; + throw new TypeError( + `Unable to follow data flow. Expected an 'Identifier' resolve to an 'ImportDeclaration'. Instead attempted to resolve a '${node.type}' at ${locationHint}.`, + ); + } + defaultValue = node.name; + } else { + node = path.node; + defaultValue = printValue(path); + } + } + if (defaultValue !== undefined) { + return { + value: defaultValue, + computed: + types.CallExpression.check(node) || + types.MemberExpression.check(node) || + types.Identifier.check(node), + }; + } + + return null; +} + +function getJsdocDefaultValue(jsdoc: Annotation): { value: string } | undefined { + const defaultTag = jsdoc.tags.find((tag) => tag.title === 'default'); + if (defaultTag === undefined) { + return undefined; + } + return { value: defaultTag.description || '' }; +} + +function getDefaultValuesFromProps( + properties: NodePath, + documentation: Documentation, + importer: Importer, +) { + const { props: documentedProps } = documentation.toObject(); + const implementedProps: Record = {}; + properties + .filter((propertyPath: NodePath) => types.Property.check(propertyPath.node), undefined) + .forEach((propertyPath: NodePath) => { + const propName = getPropertyName(propertyPath); + if (propName) { + implementedProps[propName] = propertyPath; + } + }); + + // Sometimes we list props in .propTypes even though they're implemented by another component + // These props are spread so they won't appear in the component implementation. + Object.entries(documentedProps || []).forEach(([propName, propDescriptor]) => { + if (propDescriptor.description === undefined) { + // private props have no propsType validator and therefore + // not description. + // They are either not subject to eslint react/prop-types + // or are and then we catch these issues during linting. + return; + } + + const jsdocDefaultValue = getJsdocDefaultValue( + parseDoctrine(propDescriptor.description, { + sloppy: true, + }), + ); + if (jsdocDefaultValue) { + propDescriptor.jsdocDefaultValue = jsdocDefaultValue; + } + + const propertyPath = implementedProps[propName]; + if (propertyPath !== undefined) { + const defaultValue = getDefaultValue(propertyPath, importer); + if (defaultValue) { + propDescriptor.defaultValue = defaultValue; + } + } + }); +} + +function getRenderBody(hookDefinition: NodePath, importer: Importer): NodePath { + const value = resolveToValue(hookDefinition, importer); + return value.get('body', 'body'); +} + +function getParamsPath(functionBody: NodePath): NodePath | undefined { + let propsPath: NodePath | undefined; + // visitVariableDeclarator, can't use visit body.node since it looses scope information + functionBody + .filter((path: NodePath) => { + return types.VariableDeclaration.check(path.node); + }, undefined) + .forEach((path: NodePath) => { + const declaratorPath = path.get('declarations', 0); + // find `const {} = props` + // but not `const ownerState = props` + if ( + declaratorPath.get('init', 'name').value === 'parameters' && + declaratorPath.get('id', 'type').value === 'ObjectPattern' + ) { + propsPath = declaratorPath.get('id'); + } + }); + + return propsPath; +} + +const defaultParamsHandler: Handler = (documentation, hookDefinition, importer) => { + const renderBody = getRenderBody(hookDefinition, importer); + const params = getParamsPath(renderBody); + if (params !== undefined) { + getDefaultValuesFromProps(params.get('properties'), documentation, importer); + } +}; + +export default defaultParamsHandler; diff --git a/packages/api-docs-builder/utils/findHooks.ts b/packages/api-docs-builder/utils/findHooks.ts new file mode 100644 index 00000000000000..758c76bf2cee74 --- /dev/null +++ b/packages/api-docs-builder/utils/findHooks.ts @@ -0,0 +1,32 @@ +import fs from 'fs'; +import path from 'path'; + +const hooksRegexp = /use([A-Z][a-z]+)+\.(js|tsx|ts)/; + +/** + * Returns the hook source in a flat array. + * @param {string} directory + * @param {Array<{ filename: string }>} hooks + */ +export default function findHooks(directory: string, hooks: { filename: string }[] = []) { + const items = fs.readdirSync(directory); + + items.forEach((item) => { + const itemPath = path.resolve(directory, item); + + if (fs.statSync(itemPath).isDirectory()) { + findHooks(itemPath, hooks); + return; + } + + if (!hooksRegexp.test(item)) { + return; + } + + hooks.push({ + filename: itemPath, + }); + }); + + return hooks; +} diff --git a/packages/markdown/parseMarkdown.js b/packages/markdown/parseMarkdown.js index c9a7e6774d9af6..02f3d84154c353 100644 --- a/packages/markdown/parseMarkdown.js +++ b/packages/markdown/parseMarkdown.js @@ -112,6 +112,15 @@ function getHeaders(markdown) { headers.components = []; } + if (headers.hooks) { + headers.hooks = headers.hooks + .split(',') + .map((x) => x.trim()) + .sort(); + } else { + headers.hooks = []; + } + return headers; } catch (err) { throw new Error(`${err.message} in getHeader(markdown) with markdown: \n\n${header}`); @@ -446,6 +455,13 @@ ${headers.components )})`; }) .join('\n')} +${headers.hooks + .map((hook) => { + const componentPkgMap = componentPackageMapping[headers.product]; + const componentPkg = componentPkgMap ? componentPkgMap[hook] : null; + return `- [\`${hook}\`](${resolveComponentApiUrl(headers.product, componentPkg, hook)})`; + }) + .join('\n')} `); } diff --git a/packages/markdown/parseMarkdown.test.js b/packages/markdown/parseMarkdown.test.js index 2e39a0f32c72b8..19213e08348ea8 100644 --- a/packages/markdown/parseMarkdown.test.js +++ b/packages/markdown/parseMarkdown.test.js @@ -61,6 +61,7 @@ describe('parseMarkdown', () => { --- title: React Alert component components: Alert, AlertTitle +hooks: useAlert githubLabel: 'component: alert' packageName: '@mui/lab' waiAria: https://www.w3.org/TR/wai-aria-practices/#alert @@ -69,6 +70,7 @@ authors: ['foo', 'bar'] `), ).to.deep.equal({ components: ['Alert', 'AlertTitle'], + hooks: ['useAlert'], githubLabel: 'component: alert', packageName: '@mui/lab', title: 'React Alert component', @@ -92,6 +94,7 @@ authors: `), ).to.deep.equal({ components: ['Alert', 'AlertTitle'], + hooks: [], githubLabel: 'component: alert', packageName: '@mui/lab', title: 'React Alert component', @@ -118,6 +121,7 @@ authors: `), ).to.deep.equal({ components: ['Alert', 'AlertTitle'], + hooks: [], githubLabel: 'component: alert', packageName: '@mui/lab', title: 'React Alert component', diff --git a/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.d.ts b/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.d.ts index b6bfe09b77cfea..1f7a47eacbcffd 100644 --- a/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.d.ts +++ b/packages/mui-base/src/AutocompleteUnstyled/useAutocomplete.d.ts @@ -292,6 +292,13 @@ export interface UseAutocompleteProps< value?: AutocompleteValue; } +export interface UseAutocompleteParameters< + T, + Multiple extends boolean | undefined, + DisableClearable extends boolean | undefined, + FreeSolo extends boolean | undefined, +> extends UseAutocompleteProps {} + export type AutocompleteHighlightChangeReason = 'keyboard' | 'mouse' | 'auto'; export type AutocompleteChangeReason = @@ -318,7 +325,12 @@ export type AutocompleteGetTagProps = ({ index }: { index: number }) => { tabIndex: -1; onDelete: (event: any) => void; }; - +/** + * + * API: + * + * - [useAutocomplete API](https://mui.com/base/api/use-autocomplete/) + */ export default function useAutocomplete< T, Multiple extends boolean | undefined = false, diff --git a/packages/mui-base/src/BadgeUnstyled/useBadge.ts b/packages/mui-base/src/BadgeUnstyled/useBadge.ts index 7a56c0aaa6c38c..a3896e6331107c 100644 --- a/packages/mui-base/src/BadgeUnstyled/useBadge.ts +++ b/packages/mui-base/src/BadgeUnstyled/useBadge.ts @@ -7,7 +7,16 @@ export interface UseBadgeParameters { max?: number; showZero?: boolean; } - +/** + * + * Demos: + * + * - [Unstyled badge](https://mui.com/base/react-badge/#hook) + * + * API: + * + * - [useBadge API](https://mui.com/base/api/use-badge/) + */ export default function useBadge(parameters: UseBadgeParameters) { const { badgeContent: badgeContentProp, diff --git a/packages/mui-base/src/ButtonUnstyled/useButton.ts b/packages/mui-base/src/ButtonUnstyled/useButton.ts index 5ad1c81dc671a8..356a29c80aac6f 100644 --- a/packages/mui-base/src/ButtonUnstyled/useButton.ts +++ b/packages/mui-base/src/ButtonUnstyled/useButton.ts @@ -3,11 +3,24 @@ import { unstable_useForkRef as useForkRef, unstable_useIsFocusVisible as useIsFocusVisible, } from '@mui/utils'; -import { UseButtonParameters, UseButtonRootSlotProps } from './useButton.types'; +import { + UseButtonParameters, + UseButtonReturnValue, + UseButtonRootSlotProps, +} from './useButton.types'; import extractEventHandlers from '../utils/extractEventHandlers'; import { EventHandlers } from '../utils/types'; - -export default function useButton(parameters: UseButtonParameters) { +/** + * + * Demos: + * + * - [Unstyled Button](https://mui.com/base/react-button/#hook) + * + * API: + * + * - [useButton API](https://mui.com/base/api/use-button/) + */ +export default function useButton(parameters: UseButtonParameters): UseButtonReturnValue { const { disabled = false, focusableWhenDisabled, diff --git a/packages/mui-base/src/ButtonUnstyled/useButton.types.ts b/packages/mui-base/src/ButtonUnstyled/useButton.types.ts index d2665a2ac3dd10..77e5fcb0899eab 100644 --- a/packages/mui-base/src/ButtonUnstyled/useButton.types.ts +++ b/packages/mui-base/src/ButtonUnstyled/useButton.types.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import { EventHandlers } from '../utils/types'; export interface UseButtonRootSlotOwnProps { 'aria-disabled'?: React.AriaAttributes['aria-disabled']; @@ -39,3 +40,33 @@ export interface UseButtonParameters { */ type?: React.ButtonHTMLAttributes['type']; } + +export interface UseButtonReturnValue { + /** + * Resolver for the root slot's props. + * @param otherHandlers event handlers for the root slot + * @returns props that should be spread on the root slot + */ + getRootProps: ( + otherHandlers?: TOther, + ) => UseButtonRootSlotProps; + /** + * If `true`, the component is being focused using keyboard. + * @default false + */ + focusVisible: boolean; + /** + * Callback for setting the `focusVisible` param. + */ + setFocusVisible: React.Dispatch>; + /** + * If `true`, the component is disabled. + * @default false + */ + disabled: boolean; + /** + * If `true`, the component is active (pressed). + * @default false + */ + active: boolean; +} diff --git a/packages/mui-base/src/FormControlUnstyled/useFormControlUnstyledContext.ts b/packages/mui-base/src/FormControlUnstyled/useFormControlUnstyledContext.ts index f6427e61985da8..0ea71335f789ba 100644 --- a/packages/mui-base/src/FormControlUnstyled/useFormControlUnstyledContext.ts +++ b/packages/mui-base/src/FormControlUnstyled/useFormControlUnstyledContext.ts @@ -1,6 +1,15 @@ import * as React from 'react'; import FormControlUnstyledContext from './FormControlUnstyledContext'; - +/** + * + * Demos: + * + * - [Unstyled Form Control](https://mui.com/base/react-form-control/#hook) + * + * API: + * + * - [useFormControlUnstyledContext API](https://mui.com/base/api/use-form-control-unstyled-context/) + */ export default function useFormControlUnstyledContext() { return React.useContext(FormControlUnstyledContext); } diff --git a/packages/mui-base/src/InputUnstyled/useInput.ts b/packages/mui-base/src/InputUnstyled/useInput.ts index 519899c019db1b..52780452ea1f50 100644 --- a/packages/mui-base/src/InputUnstyled/useInput.ts +++ b/packages/mui-base/src/InputUnstyled/useInput.ts @@ -8,7 +8,16 @@ import { UseInputParameters, UseInputRootSlotProps, } from './useInput.types'; - +/** + * + * Demos: + * + * - [Unstyled Input](https://mui.com/base/react-input/#hook) + * + * API: + * + * - [useInput API](https://mui.com/base/api/use-input/) + */ export default function useInput(parameters: UseInputParameters) { const { defaultValue: defaultValueProp, diff --git a/packages/mui-base/src/ListboxUnstyled/useControllableReducer.ts b/packages/mui-base/src/ListboxUnstyled/useControllableReducer.ts index 5294101392c9f0..062a94be5c10ea 100644 --- a/packages/mui-base/src/ListboxUnstyled/useControllableReducer.ts +++ b/packages/mui-base/src/ListboxUnstyled/useControllableReducer.ts @@ -108,6 +108,9 @@ function useStateChangeDetection( ]); } +/** + * @ignore - do not document. + */ export default function useControllableReducer( internalReducer: ListboxReducer, externalReducer: ListboxReducer | undefined, diff --git a/packages/mui-base/src/ListboxUnstyled/useListbox.ts b/packages/mui-base/src/ListboxUnstyled/useListbox.ts index 30d5eee08f574b..fbc5624d1f271e 100644 --- a/packages/mui-base/src/ListboxUnstyled/useListbox.ts +++ b/packages/mui-base/src/ListboxUnstyled/useListbox.ts @@ -20,6 +20,9 @@ const defaultIsOptionDisabled = () => false; const defaultOptionStringifier = (option: TOption) => typeof option === 'string' ? option : String(option); +/** + * @ignore - do not document. + */ export default function useListbox(props: UseListboxParameters) { const { disabledItemsFocusable = false, diff --git a/packages/mui-base/src/MenuItemUnstyled/useMenuItem.ts b/packages/mui-base/src/MenuItemUnstyled/useMenuItem.ts index c3e12c937086e9..9e49184417c13a 100644 --- a/packages/mui-base/src/MenuItemUnstyled/useMenuItem.ts +++ b/packages/mui-base/src/MenuItemUnstyled/useMenuItem.ts @@ -1,9 +1,20 @@ import * as React from 'react'; import { unstable_useId as useId, unstable_useForkRef as useForkRef } from '@mui/utils'; +import { EventHandlers } from '../utils/types'; import { MenuUnstyledContext } from '../MenuUnstyled'; import { useButton } from '../ButtonUnstyled'; import { UseMenuItemParameters } from './useMenuItem.types'; +/** + * + * Demos: + * + * - [Unstyled Menu](https://mui.com/base/react-menu/#hooks) + * + * API: + * + * - [useMenuItem API](https://mui.com/base/api/use-menu-item/) + */ export default function useMenuItem(props: UseMenuItemParameters) { const { disabled = false, ref, label } = props; @@ -61,7 +72,7 @@ export default function useMenuItem(props: UseMenuItemParameters) { if (id === undefined) { return { - getRootProps: (other?: Record) => ({ + getRootProps: (other?: EventHandlers) => ({ ...other, ...getButtonProps(other), role: 'menuitem', @@ -72,7 +83,7 @@ export default function useMenuItem(props: UseMenuItemParameters) { } return { - getRootProps: (other?: Record) => { + getRootProps: (other?: EventHandlers) => { const optionProps = menuContext.getItemProps(id, other); return { diff --git a/packages/mui-base/src/MenuUnstyled/useMenu.ts b/packages/mui-base/src/MenuUnstyled/useMenu.ts index 5b5b064e2c6cb4..07a191ce6278f7 100644 --- a/packages/mui-base/src/MenuUnstyled/useMenu.ts +++ b/packages/mui-base/src/MenuUnstyled/useMenu.ts @@ -42,7 +42,16 @@ function stateReducer( return newState; } - +/** + * + * Demos: + * + * - [Unstyled Menu](https://mui.com/base/react-menu/#hooks) + * + * API: + * + * - [useMenu API](https://mui.com/base/api/use-menu/) + */ export default function useMenu(parameters: UseMenuParameters = {}) { const { listboxRef: listboxRefProp, open = false, onClose, listboxId } = parameters; diff --git a/packages/mui-base/src/SelectUnstyled/useSelect.ts b/packages/mui-base/src/SelectUnstyled/useSelect.ts index 34606663cdb63a..4e70cdcf551251 100644 --- a/packages/mui-base/src/SelectUnstyled/useSelect.ts +++ b/packages/mui-base/src/SelectUnstyled/useSelect.ts @@ -28,6 +28,16 @@ import defaultOptionStringifier from './defaultOptionStringifier'; function useSelect(props: UseSelectSingleParameters): UseSelectSingleResult; function useSelect(props: UseSelectMultiParameters): UseSelectMultiResult; +/** + * + * Demos: + * + * - [Unstyled Select](https://mui.com/base/react-select/#hook) + * + * API: + * + * - [useSelect API](https://mui.com/base/api/use-select/) + */ function useSelect(props: UseSelectParameters) { const { buttonRef: buttonRefProp, diff --git a/packages/mui-base/src/SliderUnstyled/useSlider.ts b/packages/mui-base/src/SliderUnstyled/useSlider.ts index 54188800429c1b..41a2ce2859032c 100644 --- a/packages/mui-base/src/SliderUnstyled/useSlider.ts +++ b/packages/mui-base/src/SliderUnstyled/useSlider.ts @@ -177,7 +177,16 @@ function doesSupportTouchActionNone() { } return cachedSupportsTouchActionNone; } - +/** + * + * Demos: + * + * - [Unstyled Slider](https://mui.com/base/react-slider/#hook) + * + * API: + * + * - [useSlider API](https://mui.com/base/api/use-slider/) + */ export default function useSlider(parameters: UseSliderParameters) { const { 'aria-labelledby': ariaLabelledby, diff --git a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts index 801885686c4165..f073971dd4b45d 100644 --- a/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts +++ b/packages/mui-base/src/SnackbarUnstyled/useSnackbar.ts @@ -12,7 +12,11 @@ import extractEventHandlers from '../utils/extractEventHandlers'; * * Demos: * - * - [Snackbar](https://mui.com/base/react-snackbar/) + * - [Unstyled Snackbar](https://mui.com/base/react-snackbar/#hook) + * + * API: + * + * - [useSnackbar API](https://mui.com/base/api/use-snackbar/) */ export default function useSnackbar(parameters: UseSnackbarParameters) { const { diff --git a/packages/mui-base/src/SwitchUnstyled/useSwitch.ts b/packages/mui-base/src/SwitchUnstyled/useSwitch.ts index f7ee498b7dbe84..055cdb44ef6826 100644 --- a/packages/mui-base/src/SwitchUnstyled/useSwitch.ts +++ b/packages/mui-base/src/SwitchUnstyled/useSwitch.ts @@ -11,7 +11,11 @@ import { UseSwitchInputSlotProps, UseSwitchParameters } from './useSwitch.types' * * Demos: * - * - [Switches](https://mui.com/components/switches/) + * - [Unstyled Switch](https://mui.com/base/react-switch/#hook) + * + * API: + * + * - [useSwitch API](https://mui.com/base/api/use-switch/) */ export default function useSwitch(props: UseSwitchParameters) { const { diff --git a/packages/mui-base/src/TabPanelUnstyled/useTabPanel.ts b/packages/mui-base/src/TabPanelUnstyled/useTabPanel.ts index a4934902bb20ef..1c1f73625a1086 100644 --- a/packages/mui-base/src/TabPanelUnstyled/useTabPanel.ts +++ b/packages/mui-base/src/TabPanelUnstyled/useTabPanel.ts @@ -1,7 +1,16 @@ import { useTabContext, getPanelId, getTabId } from '../TabsUnstyled'; import { UseTabPanelParameters } from './useTabPanel.types'; - -const useTabPanel = (parameters: UseTabPanelParameters) => { +/** + * + * Demos: + * + * - [Unstyled Tabs](https://mui.com/base/react-tabs/#hooks) + * + * API: + * + * - [useTabPanel API](https://mui.com/base/api/use-tab-panel/) + */ +function useTabPanel(parameters: UseTabPanelParameters) { const { value } = parameters; const context = useTabContext(); @@ -25,6 +34,6 @@ const useTabPanel = (parameters: UseTabPanelParameters) => { hidden, getRootProps, }; -}; +} export default useTabPanel; diff --git a/packages/mui-base/src/TabUnstyled/useTab.ts b/packages/mui-base/src/TabUnstyled/useTab.ts index 4e4757055d9085..7c5c612f291062 100644 --- a/packages/mui-base/src/TabUnstyled/useTab.ts +++ b/packages/mui-base/src/TabUnstyled/useTab.ts @@ -2,8 +2,17 @@ import { useTabContext, getTabId, getPanelId } from '../TabsUnstyled'; import { useButton } from '../ButtonUnstyled'; import { UseTabParameters, UseTabRootSlotProps } from './useTab.types'; import { EventHandlers } from '../utils'; - -const useTab = (parameters: UseTabParameters) => { +/** + * + * Demos: + * + * - [Unstyled Tabs](https://mui.com/base/react-tabs/#hooks) + * + * API: + * + * - [useTab API](https://mui.com/base/api/use-tab/) + */ +function useTab(parameters: UseTabParameters) { const { value: valueProp, onChange, onClick, onFocus } = parameters; const { getRootProps: getRootPropsButton, ...otherButtonProps } = useButton(parameters); @@ -84,6 +93,6 @@ const useTab = (parameters: UseTabParameters) => { ...otherButtonProps, selected, }; -}; +} export default useTab; diff --git a/packages/mui-base/src/TabsListUnstyled/useTabsList.ts b/packages/mui-base/src/TabsListUnstyled/useTabsList.ts index 49895c6a5a3965..1e848f0bf3a197 100644 --- a/packages/mui-base/src/TabsListUnstyled/useTabsList.ts +++ b/packages/mui-base/src/TabsListUnstyled/useTabsList.ts @@ -67,8 +67,17 @@ const moveFocus = ( } } }; - -const useTabsList = (parameters: UseTabsListParameters) => { +/** + * + * Demos: + * + * - [Unstyled Tabs](https://mui.com/base/react-tabs/#hooks) + * + * API: + * + * - [useTabsList API](https://mui.com/base/api/use-tabs-list/) + */ +function useTabsList(parameters: UseTabsListParameters) { const { 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, children, ref } = parameters; const tabsListRef = React.createRef(); @@ -198,6 +207,6 @@ const useTabsList = (parameters: UseTabsListParameters) => { processChildren, getRootProps, }; -}; +} export default useTabsList; diff --git a/packages/mui-base/src/TabsUnstyled/useTabs.ts b/packages/mui-base/src/TabsUnstyled/useTabs.ts index ca27bb92cf0961..cca59a99039f98 100644 --- a/packages/mui-base/src/TabsUnstyled/useTabs.ts +++ b/packages/mui-base/src/TabsUnstyled/useTabs.ts @@ -31,8 +31,17 @@ export interface UseTabsParameters { */ selectionFollowsFocus?: boolean; } - -const useTabs = (parameters: UseTabsParameters) => { +/** + * + * Demos: + * + * - [Unstyled Tabs](https://mui.com/base/react-tabs/#hooks) + * + * API: + * + * - [useTabs API](https://mui.com/base/api/use-tabs/) + */ +function useTabs(parameters: UseTabsParameters) { const { value: valueProp, defaultValue, @@ -68,6 +77,6 @@ const useTabs = (parameters: UseTabsParameters) => { return { tabsContextValue, }; -}; +} export default useTabs; diff --git a/packages/mui-base/src/utils/useSlotProps.ts b/packages/mui-base/src/utils/useSlotProps.ts index 7ec2e93501d47e..aa022d2575469a 100644 --- a/packages/mui-base/src/utils/useSlotProps.ts +++ b/packages/mui-base/src/utils/useSlotProps.ts @@ -50,6 +50,7 @@ export type UseSlotPropsResult< >; /** + * @ignore - do not document. * Builds the props to be passed into the slot of an unstyled component. * It merges the internal props of the component with the ones supplied by the user, allowing to customize the behavior. * If the slot component is not a host component, it also merges in the `ownerState`.