@linagora/twake-mui 8.0.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/components/Button/index.d.ts +11 -4
  3. package/dist/components/Layout/Content.d.ts +7 -0
  4. package/dist/components/Layout/Content.js +29 -0
  5. package/dist/components/Layout/Layout.d.ts +21 -0
  6. package/dist/components/Layout/Layout.js +31 -0
  7. package/dist/components/Layout/Layout.stories.d.ts +7 -0
  8. package/dist/components/Layout/Layout.stories.js +89 -0
  9. package/dist/components/Layout/Main.d.ts +7 -0
  10. package/dist/components/Layout/Main.js +37 -0
  11. package/dist/components/Layout/index.d.ts +6 -0
  12. package/dist/components/Layout/index.js +3 -0
  13. package/dist/components/Nav/index.d.ts +8 -0
  14. package/dist/components/Nav/index.js +14 -0
  15. package/dist/components/NavDesktopDropdown/index.d.ts +8 -0
  16. package/dist/components/NavDesktopDropdown/index.js +24 -0
  17. package/dist/components/NavDesktopLimiter/index.d.ts +6 -0
  18. package/dist/components/NavDesktopLimiter/index.js +31 -0
  19. package/dist/components/NavDesktopLimiter/locales/en.json +6 -0
  20. package/dist/components/NavDesktopLimiter/locales/fr.json +6 -0
  21. package/dist/components/NavDesktopLimiter/locales/ru.json +6 -0
  22. package/dist/components/NavDesktopLimiter/locales/vi.json +6 -0
  23. package/dist/components/NavIcon/index.d.ts +7 -0
  24. package/dist/components/NavIcon/index.js +17 -0
  25. package/dist/components/NavItem/index.d.ts +8 -0
  26. package/dist/components/NavItem/index.js +29 -0
  27. package/dist/components/NavLink/index.d.ts +3 -0
  28. package/dist/components/NavLink/index.js +36 -0
  29. package/dist/components/NavText/index.d.ts +7 -0
  30. package/dist/components/NavText/index.js +14 -0
  31. package/dist/components/Sidebar/Sidebar.stories.d.ts +7 -0
  32. package/dist/components/Sidebar/Sidebar.stories.js +57 -0
  33. package/dist/components/Sidebar/index.d.ts +8 -0
  34. package/dist/components/Sidebar/index.js +28 -0
  35. package/dist/index.d.ts +18 -0
  36. package/dist/index.js +9 -0
  37. package/dist/stories/Checkbox.stories.js +2 -15
  38. package/package.json +4 -2
package/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # @linagora/twake-mui [9.1.0](https://github.com/linagora/twake-ui/compare/@linagora/twake-mui@9.0.0...@linagora/twake-mui@9.1.0) (2026-09-22)
2
+
3
+
4
+ ### Features
5
+
6
+ * **twake-mui:** Migrate Layout from cozy-ui ([fdcb499](https://github.com/linagora/twake-ui/commit/fdcb499c9790e4b8fc7aaaa12087a8eebf250d42))
7
+
8
+ # @linagora/twake-mui [9.0.0](https://github.com/linagora/twake-ui/compare/@linagora/twake-mui@8.0.0...@linagora/twake-mui@9.0.0) (2026-09-21)
9
+
10
+
11
+ ### Features
12
+
13
+ * **twake-mui:** Migrate Nav from cozy-ui ([5dd0516](https://github.com/linagora/twake-ui/commit/5dd05165741159437f796935f81d9d907eb808f2))
14
+ * **twake-mui:** Migrate Sidebar from cozy-ui ([08f4179](https://github.com/linagora/twake-ui/commit/08f4179493090399cca1e59878d405b945777225))
15
+
16
+
17
+ ### BREAKING CHANGES
18
+
19
+ * **twake-mui:** You need "twake-i18n" >= "0.5.0"
20
+
1
21
  # @linagora/twake-mui [8.0.0](https://github.com/linagora/twake-ui/compare/@linagora/twake-mui@7.1.0...@linagora/twake-mui@8.0.0) (2026-09-18)
2
22
 
3
23
 
@@ -1,12 +1,19 @@
1
- import { ButtonProps as MuiButtonProps } from '@mui/material';
1
+ import { ButtonProps as MuiButtonProps, ButtonTypeMap as MuiButtonTypeMap } from '@mui/material';
2
+ import { OverridableComponent, OverrideProps } from '@mui/material/OverridableComponent';
2
3
  import React from 'react';
3
4
  declare const legacyVariants: {
4
5
  readonly primary: "contained";
5
6
  readonly secondary: "outlined";
6
7
  };
7
8
  type LegacyVariant = keyof typeof legacyVariants;
8
- export interface ButtonProps extends Omit<MuiButtonProps, 'variant'> {
9
- variant?: MuiButtonProps['variant'] | LegacyVariant;
9
+ export interface ButtonTypeMap<AdditionalProps = object, RootComponent extends React.ElementType = 'button'> {
10
+ props: AdditionalProps & Omit<MuiButtonTypeMap['props'], 'variant'> & {
11
+ variant?: MuiButtonProps['variant'] | LegacyVariant;
12
+ };
13
+ defaultComponent: RootComponent;
10
14
  }
11
- export declare const Button: React.ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & React.RefAttributes<HTMLButtonElement>>;
15
+ export type ButtonProps<RootComponent extends React.ElementType = 'button', AdditionalProps = object> = OverrideProps<ButtonTypeMap<AdditionalProps, RootComponent>, RootComponent>;
16
+ export declare const Button: OverridableComponent<ButtonTypeMap> & {
17
+ displayName?: string;
18
+ };
12
19
  export default Button;
@@ -0,0 +1,7 @@
1
+ import { SxProps } from '@mui/material';
2
+ import { Theme } from '@mui/material/styles';
3
+ import React from 'react';
4
+ export interface ContentProps extends React.ComponentPropsWithoutRef<'div'> {
5
+ sx?: SxProps<Theme>;
6
+ }
7
+ export declare const Content: (props: ContentProps) => React.ReactElement;
@@ -0,0 +1,29 @@
1
+ import { styled } from '@mui/material/styles';
2
+ import React, { useContext } from 'react';
3
+ import { LayoutContext } from './Layout';
4
+ const ContentRoot = styled('div', {
5
+ shouldForwardProp: prop => prop !== 'monoColumn'
6
+ })(({ theme }) => ({
7
+ position: 'relative',
8
+ display: 'flex',
9
+ flexDirection: 'column',
10
+ flex: '1 1 auto',
11
+ boxSizing: 'border-box',
12
+ overflow: 'hidden auto',
13
+ backgroundColor: theme.vars.palette.background.paper,
14
+ height: '100%',
15
+ variants: [
16
+ {
17
+ props: { monoColumn: false },
18
+ style: {
19
+ margin: '16px 16px 16px 0',
20
+ borderRadius: 16,
21
+ [theme.breakpoints.down('lg')]: { margin: 0, borderRadius: 0 }
22
+ }
23
+ }
24
+ ]
25
+ }));
26
+ export const Content = (props) => {
27
+ const { monoColumn } = useContext(LayoutContext);
28
+ return React.createElement(ContentRoot, { role: "main", monoColumn: monoColumn, ...props });
29
+ };
@@ -0,0 +1,21 @@
1
+ import { SxProps } from '@mui/material';
2
+ import { CSSObject, Theme } from '@mui/material/styles';
3
+ import React from 'react';
4
+ interface LayoutContextValue {
5
+ monoColumn: boolean;
6
+ withTopBar: boolean;
7
+ }
8
+ export declare const LayoutContext: React.Context<LayoutContextValue>;
9
+ export interface TwoPanesProps {
10
+ monoColumn?: boolean;
11
+ }
12
+ export declare const twoPanes: (theme: Theme) => CSSObject;
13
+ export interface LayoutProps extends React.ComponentPropsWithoutRef<'div'> {
14
+ sx?: SxProps<Theme>;
15
+ /** Used to add/remove top spacing when using with or without a topbar */
16
+ withTopBar?: boolean;
17
+ /** Should be true if no sidebar in the app */
18
+ monoColumn?: boolean;
19
+ }
20
+ export declare const Layout: ({ children, withTopBar, monoColumn, ...props }: LayoutProps) => React.ReactElement;
21
+ export {};
@@ -0,0 +1,31 @@
1
+ import { styled } from '@mui/material/styles';
2
+ import React, { createContext, useMemo } from 'react';
3
+ import { SIDEBAR_MOBILE_HEIGHT } from '../Sidebar';
4
+ export const LayoutContext = createContext({
5
+ monoColumn: false,
6
+ withTopBar: true
7
+ });
8
+ export const twoPanes = (theme) => ({
9
+ backgroundColor: theme.vars.palette.background.default,
10
+ [theme.breakpoints.down('lg')]: {
11
+ height: `calc(100vh - var(${SIDEBAR_MOBILE_HEIGHT}, 0px))`,
12
+ backgroundColor: 'transparent'
13
+ }
14
+ });
15
+ const LayoutRoot = styled('div', {
16
+ shouldForwardProp: prop => prop !== 'monoColumn'
17
+ })(({ theme }) => ({
18
+ boxSizing: 'border-box',
19
+ display: 'flex',
20
+ maxWidth: '100%',
21
+ width: '100%',
22
+ height: '100%',
23
+ backgroundColor: theme.vars.palette.background.paper,
24
+ color: theme.vars.palette.text.primary,
25
+ variants: [{ props: { monoColumn: false }, style: twoPanes(theme) }]
26
+ }));
27
+ export const Layout = ({ children, withTopBar = true, monoColumn = false, ...props }) => {
28
+ const value = useMemo(() => ({ monoColumn, withTopBar }), [monoColumn, withTopBar]);
29
+ return (React.createElement(LayoutContext.Provider, { value: value },
30
+ React.createElement(LayoutRoot, { monoColumn: monoColumn, ...props }, children)));
31
+ };
@@ -0,0 +1,7 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Layout } from './index';
3
+ declare const meta: Meta<typeof Layout>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof meta>;
6
+ export declare const Default: Story;
7
+ export declare const Screenshot: Story;
@@ -0,0 +1,89 @@
1
+ import { Check, Download, Warn } from '@linagora/twake-icons';
2
+ import { Box, Typography } from '@mui/material';
3
+ import React, { useState } from 'react';
4
+ import { Content, Layout, Main } from './index';
5
+ import { Nav } from '../Nav';
6
+ import { NavDesktopDropdown } from '../NavDesktopDropdown';
7
+ import { NavDesktopLimiter } from '../NavDesktopLimiter';
8
+ import { NavIcon } from '../NavIcon';
9
+ import { NavItem } from '../NavItem';
10
+ import { NavLink } from '../NavLink';
11
+ import { NavText } from '../NavText';
12
+ import { Sidebar } from '../Sidebar';
13
+ const paragraph = 'Ada Lovelace was an English mathematician chiefly known for her work on Charles Babbage’s proposed mechanical general-purpose computer, the Analytical Engine. She was the first to recognise that the machine had applications beyond pure calculation.';
14
+ const TopBar = () => (React.createElement(Box, { id: "coz-bar", role: "banner", sx: theme => ({
15
+ display: 'flex',
16
+ alignItems: 'center',
17
+ px: 2,
18
+ minHeight: 48,
19
+ width: '100%',
20
+ boxSizing: 'border-box',
21
+ backgroundColor: theme.vars.palette.background.paper,
22
+ borderBottom: `1px solid ${theme.vars.palette.divider}`,
23
+ position: 'fixed',
24
+ top: 0,
25
+ right: 0,
26
+ zIndex: theme.zIndex.appBar,
27
+ [theme.breakpoints.up('lg')]: { position: 'relative' }
28
+ }) }, "Fake TopBar"));
29
+ const LayoutDemo = ({ monoColumn = false, withTopBar = true, ...props }) => {
30
+ const [active, setActive] = useState(['Section 1', 'Subsection 1']);
31
+ const makeProps = (route) => ({
32
+ selected: active.slice(0, route.length).join('/') === route.join('/'),
33
+ onClick: () => setActive(route)
34
+ });
35
+ return (React.createElement(Box, { sx: { height: '100vh', display: 'flex', flexDirection: 'column' } },
36
+ withTopBar && React.createElement(TopBar, null),
37
+ React.createElement(Box, { role: "application", sx: { flex: '1 1 auto', minHeight: 0 } },
38
+ React.createElement(Layout, { monoColumn: monoColumn, withTopBar: withTopBar, ...props },
39
+ !monoColumn && (React.createElement(Sidebar, null,
40
+ React.createElement(Nav, null,
41
+ React.createElement(NavItem, null,
42
+ React.createElement(NavLink, { ...makeProps(['Section 1']) },
43
+ React.createElement(NavIcon, { icon: Warn }),
44
+ React.createElement(NavText, null, "Section 1"))),
45
+ React.createElement(NavDesktopLimiter, null, Array.from(Array(10).keys()).map(i => (React.createElement(NavItem, { secondary: true, key: i },
46
+ React.createElement(NavLink, { ...makeProps(['Section 1', `Subsection ${i}`]) },
47
+ React.createElement(NavText, null,
48
+ "Subsection ",
49
+ i)))))),
50
+ React.createElement(NavItem, null,
51
+ React.createElement(NavLink, { ...makeProps(['Section 2']) },
52
+ React.createElement(NavIcon, { icon: Check }),
53
+ React.createElement(NavText, null, "Section 2"))),
54
+ React.createElement(NavItem, null,
55
+ React.createElement(NavLink, { ...makeProps(['Section 3']) },
56
+ React.createElement(NavIcon, { icon: Download }),
57
+ React.createElement(NavText, null, "Section 3"))),
58
+ React.createElement(NavDesktopDropdown, { label: "Section 4" }, Array.from(Array(6).keys()).map(i => (React.createElement(NavItem, { secondary: true, key: i },
59
+ React.createElement(NavLink, { ...makeProps(['Section 4', `Subsection ${i}`]) },
60
+ React.createElement(NavText, null,
61
+ "Subsection ",
62
+ i))))))))),
63
+ React.createElement(Main, null,
64
+ React.createElement(Content, { sx: { p: 2 } },
65
+ React.createElement(Typography, { variant: "h5" }, active.join(' / ')),
66
+ React.createElement("p", null, "---Start---"),
67
+ Array.from(Array(6).keys()).map(i => (React.createElement("p", { key: i }, paragraph))),
68
+ React.createElement("p", null, "---END---")))))));
69
+ };
70
+ const meta = {
71
+ title: 'Layout',
72
+ component: Layout,
73
+ parameters: { layout: 'fullscreen' },
74
+ tags: ['autodocs'],
75
+ argTypes: {
76
+ monoColumn: { control: 'boolean' },
77
+ withTopBar: { control: 'boolean' }
78
+ },
79
+ render: args => React.createElement(LayoutDemo, { ...args })
80
+ };
81
+ export default meta;
82
+ export const Default = {
83
+ args: { monoColumn: false, withTopBar: true }
84
+ };
85
+ // Visual Regression - Combined view for Argos testing
86
+ export const Screenshot = {
87
+ tags: ['argos'],
88
+ args: { monoColumn: false, withTopBar: true }
89
+ };
@@ -0,0 +1,7 @@
1
+ import { SxProps } from '@mui/material';
2
+ import { Theme } from '@mui/material/styles';
3
+ import React from 'react';
4
+ export interface MainProps extends React.ComponentPropsWithoutRef<'main'> {
5
+ sx?: SxProps<Theme>;
6
+ }
7
+ export declare const Main: (props: MainProps) => React.ReactElement;
@@ -0,0 +1,37 @@
1
+ import { styled } from '@mui/material/styles';
2
+ import React, { useContext } from 'react';
3
+ import { LayoutContext, twoPanes } from './Layout';
4
+ /** Height of the top bar, which is out of the Layout but overlaps it */
5
+ const TOP_BAR_HEIGHT = 48;
6
+ const MainRoot = styled('main', {
7
+ shouldForwardProp: prop => prop !== 'monoColumn' && prop !== 'withTopBar'
8
+ })(({ theme }) => ({
9
+ display: 'flex',
10
+ flexDirection: 'column',
11
+ position: 'relative',
12
+ flex: '1 1 auto',
13
+ boxSizing: 'border-box',
14
+ overflow: 'hidden',
15
+ height: '100%',
16
+ variants: [
17
+ { props: { monoColumn: false }, style: twoPanes(theme) },
18
+ {
19
+ props: { withTopBar: true },
20
+ style: {
21
+ [theme.breakpoints.down('lg')]: {
22
+ '&::before': {
23
+ content: '""',
24
+ display: 'block',
25
+ height: TOP_BAR_HEIGHT,
26
+ width: '100%',
27
+ backgroundColor: theme.vars.palette.background.paper
28
+ }
29
+ }
30
+ }
31
+ }
32
+ ]
33
+ }));
34
+ export const Main = (props) => {
35
+ const { monoColumn, withTopBar } = useContext(LayoutContext);
36
+ return React.createElement(MainRoot, { monoColumn: monoColumn, withTopBar: withTopBar, ...props });
37
+ };
@@ -0,0 +1,6 @@
1
+ export { Layout } from './Layout';
2
+ export { Main } from './Main';
3
+ export { Content } from './Content';
4
+ export type { LayoutProps } from './Layout';
5
+ export type { MainProps } from './Main';
6
+ export type { ContentProps } from './Content';
@@ -0,0 +1,3 @@
1
+ export { Layout } from './Layout';
2
+ export { Main } from './Main';
3
+ export { Content } from './Content';
@@ -0,0 +1,8 @@
1
+ import { Theme } from '@mui/material/styles';
2
+ import React from 'react';
3
+ declare const NavRoot: import("@emotion/styled").StyledComponent<import("@mui/material").ListOwnProps & Omit<import("@mui/material").ListOwnProps, keyof import("@mui/material").ListOwnProps> & import("@mui/material/OverridableComponent").CommonProps & Omit<Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>, "ref"> & {
4
+ ref?: ((instance: HTMLUListElement | null) => void) | React.RefObject<HTMLUListElement> | null | undefined;
5
+ }, "style" | "className" | "children" | "classes" | "sx" | "dense" | "disablePadding" | "subheader"> & import("@mui/system").MUIStyledCommonProps<Theme>, {}, {}>;
6
+ export type NavProps = React.ComponentProps<typeof NavRoot>;
7
+ export declare const Nav: (props: NavProps) => React.ReactElement;
8
+ export {};
@@ -0,0 +1,14 @@
1
+ import { List } from '@mui/material';
2
+ import { styled } from '@mui/material/styles';
3
+ import React from 'react';
4
+ const NavRoot = styled(List)(({ theme }) => ({
5
+ margin: '24px 0',
6
+ padding: 0,
7
+ [theme.breakpoints.down('lg')]: {
8
+ display: 'flex',
9
+ justifyContent: 'space-around',
10
+ margin: '6px 0 4px'
11
+ }
12
+ }));
13
+ export const Nav = (props) => (React.createElement("nav", null,
14
+ React.createElement(NavRoot, { ...props })));
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export interface NavDesktopDropdownProps {
3
+ label: string;
4
+ children: React.ReactNode;
5
+ defaultOpen?: boolean;
6
+ limit?: number;
7
+ }
8
+ export declare const NavDesktopDropdown: ({ label, children, defaultOpen, limit }: NavDesktopDropdownProps) => React.ReactElement | null;
@@ -0,0 +1,24 @@
1
+ import { Bottom, Icon } from '@linagora/twake-icons';
2
+ import { ListItem, Typography } from '@mui/material';
3
+ import React, { Children, isValidElement, useState } from 'react';
4
+ import { useBreakpoints } from '../../hooks/useBreakpoints';
5
+ export const NavDesktopDropdown = ({ label, children, defaultOpen = true, limit = 5 }) => {
6
+ const { isDesktop } = useBreakpoints();
7
+ const [open, setOpen] = useState(defaultOpen);
8
+ const isActivated = Children.toArray(children).filter(isValidElement).length > limit;
9
+ const onToggle = () => setOpen(current => !current);
10
+ if (!isDesktop)
11
+ return null;
12
+ return (React.createElement(React.Fragment, null,
13
+ React.createElement(ListItem, { onClick: isActivated ? onToggle : undefined, sx: {
14
+ minHeight: 48,
15
+ py: 1,
16
+ px: 2,
17
+ justifyContent: 'space-between',
18
+ cursor: isActivated ? 'pointer' : undefined
19
+ } },
20
+ React.createElement(Typography, { variant: "caption", sx: { color: 'text.secondary' } }, label),
21
+ isActivated && (React.createElement(Typography, { component: "span", sx: { display: 'flex', ml: '5px', color: 'text.secondary' } },
22
+ React.createElement(Icon, { icon: Bottom, size: 10, rotate: open ? 0 : -90 })))),
23
+ open && children));
24
+ };
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ export interface NavDesktopLimiterProps {
3
+ children: React.ReactNode;
4
+ max?: number;
5
+ }
6
+ export declare const NavDesktopLimiter: ({ children, max }: NavDesktopLimiterProps) => React.ReactElement | null;
@@ -0,0 +1,31 @@
1
+ import { Bottom, Top } from '@linagora/twake-icons';
2
+ import React, { Children, isValidElement, useState } from 'react';
3
+ import { useExtendI18n, useI18n } from 'twake-i18n';
4
+ import { NavIcon } from '../NavIcon';
5
+ import { NavItem } from '../NavItem';
6
+ import { NavLink } from '../NavLink';
7
+ import { NavText } from '../NavText';
8
+ import en from './locales/en.json';
9
+ import fr from './locales/fr.json';
10
+ import ru from './locales/ru.json';
11
+ import vi from './locales/vi.json';
12
+ import { useBreakpoints } from '../../hooks/useBreakpoints';
13
+ const locales = { en, fr, ru, vi };
14
+ export const NavDesktopLimiter = ({ children, max = 5 }) => {
15
+ useExtendI18n(locales);
16
+ const { t } = useI18n();
17
+ const [viewingAll, setViewingAll] = useState(false);
18
+ const { isMobile } = useBreakpoints();
19
+ const items = Children.toArray(children).filter(isValidElement);
20
+ const amountHidden = Math.max(0, items.length - max);
21
+ if (isMobile)
22
+ return null;
23
+ return (React.createElement(React.Fragment, null,
24
+ viewingAll ? items : items.slice(0, max),
25
+ amountHidden > 0 && (React.createElement(NavItem, { secondary: true },
26
+ React.createElement(NavLink, { onClick: () => setViewingAll(current => !current) },
27
+ React.createElement(NavIcon, { icon: viewingAll ? Top : Bottom }),
28
+ React.createElement(NavText, null, viewingAll
29
+ ? t('NavDesktopLimiter.showLess')
30
+ : `${t('NavDesktopLimiter.showMore')} (${amountHidden})`))))));
31
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "NavDesktopLimiter": {
3
+ "showMore": "Show More",
4
+ "showLess": "Show Less"
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "NavDesktopLimiter": {
3
+ "showMore": "Voir plus",
4
+ "showLess": "Voir moins"
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "NavDesktopLimiter": {
3
+ "showMore": "Показать больше",
4
+ "showLess": "Показать меньше"
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "NavDesktopLimiter": {
3
+ "showMore": "Hiện thêm",
4
+ "showLess": "Hiện ít hơn"
5
+ }
6
+ }
@@ -0,0 +1,7 @@
1
+ import { IconProps } from '@linagora/twake-icons';
2
+ import { ListItemIconProps } from '@mui/material';
3
+ import React from 'react';
4
+ export interface NavIconProps extends ListItemIconProps {
5
+ icon: IconProps['icon'];
6
+ }
7
+ export declare const NavIcon: ({ icon, ...props }: NavIconProps) => React.ReactElement;
@@ -0,0 +1,17 @@
1
+ import { Icon } from '@linagora/twake-icons';
2
+ import { ListItemIcon } from '@mui/material';
3
+ import { styled } from '@mui/material/styles';
4
+ import React from 'react';
5
+ const NavIconRoot = styled(ListItemIcon)(({ theme }) => ({
6
+ minWidth: 0,
7
+ marginRight: 12,
8
+ color: theme.vars.palette.text.primary,
9
+ [theme.breakpoints.down('lg')]: {
10
+ display: 'block',
11
+ marginRight: 0,
12
+ color: theme.vars.palette.text.secondary,
13
+ '& svg': { margin: '4px auto 5px', width: 16, height: 16 }
14
+ }
15
+ }));
16
+ export const NavIcon = ({ icon, ...props }) => (React.createElement(NavIconRoot, { ...props },
17
+ React.createElement(Icon, { icon: icon, "aria-hidden": "true", focusable: "false" })));
@@ -0,0 +1,8 @@
1
+ import { Theme } from '@mui/material/styles';
2
+ import React from 'react';
3
+ export declare const NavItem: import("@emotion/styled").StyledComponent<import("@mui/material").ListItemOwnProps & import("@mui/material/OverridableComponent").CommonProps & Omit<Omit<React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, "ref"> & {
4
+ ref?: ((instance: HTMLLIElement | null) => void) | React.RefObject<HTMLLIElement> | null | undefined;
5
+ }, "style" | "className" | "children" | "classes" | "sx" | "alignItems" | "dense" | "divider" | "slotProps" | "slots" | "disableGutters" | "disablePadding" | "secondaryAction"> & import("@mui/system").MUIStyledCommonProps<Theme> & {
6
+ secondary?: boolean;
7
+ }, {}, {}>;
8
+ export type NavItemProps = React.ComponentProps<typeof NavItem>;
@@ -0,0 +1,29 @@
1
+ import { ListItem, listItemButtonClasses } from '@mui/material';
2
+ import { styled } from '@mui/material/styles';
3
+ export const NavItem = styled(ListItem, {
4
+ shouldForwardProp: prop => prop !== 'secondary'
5
+ })(({ theme, secondary }) => ({
6
+ padding: 0,
7
+ height: 36,
8
+ [theme.breakpoints.down('lg')]: {
9
+ display: 'block',
10
+ height: 'auto',
11
+ margin: '0 12px',
12
+ flex: '0 0 40px'
13
+ },
14
+ ...(secondary && {
15
+ height: 'auto',
16
+ margin: '3px 0',
17
+ [theme.breakpoints.down('lg')]: { display: 'none' },
18
+ [`& .${listItemButtonClasses.root}`]: {
19
+ margin: '0 16px 0 2.8rem',
20
+ padding: '8px 16px',
21
+ height: 'auto',
22
+ fontSize: theme.typography.pxToRem(14),
23
+ [`&.${listItemButtonClasses.selected}, &.active`]: {
24
+ color: theme.vars.palette.secondary.contrastText,
25
+ backgroundColor: theme.vars.palette.secondary.main
26
+ }
27
+ }
28
+ })
29
+ }));
@@ -0,0 +1,3 @@
1
+ import { ListItemButton, ListItemButtonProps } from '@mui/material';
2
+ export type NavLinkProps = ListItemButtonProps;
3
+ export declare const NavLink: typeof ListItemButton;
@@ -0,0 +1,36 @@
1
+ import { ListItemButton, listItemButtonClasses, listItemIconClasses } from '@mui/material';
2
+ import { styled } from '@mui/material/styles';
3
+ // Cast keeps ListItemButton's `component` generic (react-router NavLink etc.)
4
+ export const NavLink = styled(ListItemButton)(({ theme }) => ({
5
+ margin: '0 16px',
6
+ padding: '0 8px',
7
+ height: '100%',
8
+ borderRadius: 8,
9
+ lineHeight: 1.375,
10
+ color: theme.vars.palette.text.primary,
11
+ [`&.${listItemButtonClasses.selected}, &.active`]: {
12
+ color: theme.vars.palette.primary.main,
13
+ backgroundColor: theme.vars.palette.action.selected,
14
+ '&:hover': { backgroundColor: theme.vars.palette.action.selected },
15
+ [`& .${listItemIconClasses.root}`]: {
16
+ color: theme.vars.palette.primary.main
17
+ }
18
+ },
19
+ [theme.breakpoints.down('lg')]: {
20
+ display: 'block',
21
+ height: 'auto',
22
+ margin: 0,
23
+ padding: 0,
24
+ textAlign: 'center',
25
+ fontSize: theme.typography.pxToRem(11),
26
+ lineHeight: '12px',
27
+ color: theme.vars.palette.text.secondary,
28
+ [`&.${listItemButtonClasses.selected}, &.active`]: {
29
+ color: theme.vars.palette.text.primary,
30
+ backgroundColor: 'transparent',
31
+ [`& .${listItemIconClasses.root}`]: {
32
+ color: theme.vars.palette.text.primary
33
+ }
34
+ }
35
+ }
36
+ }));
@@ -0,0 +1,7 @@
1
+ import { SxProps } from '@mui/material';
2
+ import { Theme } from '@mui/material/styles';
3
+ import React from 'react';
4
+ export interface NavTextProps extends React.ComponentPropsWithoutRef<'span'> {
5
+ sx?: SxProps<Theme>;
6
+ }
7
+ export declare const NavText: React.FC<NavTextProps>;
@@ -0,0 +1,14 @@
1
+ import { styled } from '@mui/material/styles';
2
+ // Explicit annotation: declaration emit for styled('span') otherwise
3
+ // widens the element props to the whole JSX.IntrinsicElements map.
4
+ export const NavText = styled('span')(({ theme }) => ({
5
+ fontSize: theme.typography.pxToRem(14),
6
+ fontWeight: 500,
7
+ letterSpacing: '.15px',
8
+ [theme.breakpoints.down('lg')]: {
9
+ display: 'block',
10
+ textAlign: 'center',
11
+ whiteSpace: 'nowrap',
12
+ fontSize: theme.typography.pxToRem(12)
13
+ }
14
+ }));
@@ -0,0 +1,7 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Sidebar } from './index';
3
+ declare const meta: Meta<typeof Sidebar>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof meta>;
6
+ export declare const Default: Story;
7
+ export declare const Screenshot: Story;
@@ -0,0 +1,57 @@
1
+ import { Check, Download, Warn } from '@linagora/twake-icons';
2
+ import { Box, Typography } from '@mui/material';
3
+ import React, { useState } from 'react';
4
+ import { Nav } from '../Nav';
5
+ import { NavDesktopDropdown } from '../NavDesktopDropdown';
6
+ import { NavDesktopLimiter } from '../NavDesktopLimiter';
7
+ import { NavIcon } from '../NavIcon';
8
+ import { NavItem } from '../NavItem';
9
+ import { NavLink } from '../NavLink';
10
+ import { NavText } from '../NavText';
11
+ import { Sidebar } from './index';
12
+ const SidebarDemo = (props) => {
13
+ const [active, setActive] = useState(['Section 1', 'Subsection 1']);
14
+ const makeProps = (route) => ({
15
+ selected: active.slice(0, route.length).join('/') === route.join('/'),
16
+ onClick: () => setActive(route)
17
+ });
18
+ return (React.createElement(Box, { sx: { display: 'flex' } },
19
+ React.createElement(Sidebar, { ...props },
20
+ React.createElement(Nav, null,
21
+ React.createElement(NavItem, null,
22
+ React.createElement(NavLink, { ...makeProps(['Section 1']) },
23
+ React.createElement(NavIcon, { icon: Warn }),
24
+ React.createElement(NavText, null, "Section 1"))),
25
+ React.createElement(NavDesktopLimiter, null, Array.from(Array(10).keys()).map(i => (React.createElement(NavItem, { secondary: true, key: i },
26
+ React.createElement(NavLink, { ...makeProps(['Section 1', `Subsection ${i}`]) },
27
+ React.createElement(NavText, null,
28
+ "Subsection ",
29
+ i)))))),
30
+ React.createElement(NavItem, null,
31
+ React.createElement(NavLink, { ...makeProps(['Section 2']) },
32
+ React.createElement(NavIcon, { icon: Check }),
33
+ React.createElement(NavText, null, "Section 2"))),
34
+ React.createElement(NavItem, null,
35
+ React.createElement(NavLink, { ...makeProps(['Section 3']) },
36
+ React.createElement(NavIcon, { icon: Download }),
37
+ React.createElement(NavText, null, "Section 3"))),
38
+ React.createElement(NavDesktopDropdown, { label: "Section 4" }, Array.from(Array(6).keys()).map(i => (React.createElement(NavItem, { secondary: true, key: i },
39
+ React.createElement(NavLink, { ...makeProps(['Section 4', `Subsection ${i}`]) },
40
+ React.createElement(NavText, null,
41
+ "Subsection ",
42
+ i)))))))),
43
+ React.createElement(Box, { sx: { flex: 1, p: 2 } },
44
+ React.createElement(Typography, { variant: "h5" }, active.join(' / ')))));
45
+ };
46
+ const meta = {
47
+ title: 'Sidebar',
48
+ component: Sidebar,
49
+ tags: ['autodocs'],
50
+ render: args => React.createElement(SidebarDemo, { ...args })
51
+ };
52
+ export default meta;
53
+ export const Default = {};
54
+ // Visual Regression - Combined view for Argos testing
55
+ export const Screenshot = {
56
+ tags: ['argos']
57
+ };
@@ -0,0 +1,8 @@
1
+ import { SxProps } from '@mui/material';
2
+ import { Theme } from '@mui/material/styles';
3
+ import React from 'react';
4
+ export declare const SIDEBAR_MOBILE_HEIGHT = "--sidebarHeight";
5
+ export interface SidebarProps extends React.ComponentPropsWithoutRef<'aside'> {
6
+ sx?: SxProps<Theme>;
7
+ }
8
+ export declare const Sidebar: React.FC<SidebarProps>;
@@ -0,0 +1,28 @@
1
+ import { GlobalStyles } from '@mui/material';
2
+ import { styled } from '@mui/material/styles';
3
+ import React from 'react';
4
+ export const SIDEBAR_MOBILE_HEIGHT = '--sidebarHeight';
5
+ const makeSidebarHeightCSSVar = (React.createElement(GlobalStyles, { styles: { ':root': { [SIDEBAR_MOBILE_HEIGHT]: '52px' } } }));
6
+ const SidebarRoot = styled('aside')(({ theme }) => ({
7
+ width: 236,
8
+ flex: '0 0 auto',
9
+ display: 'flex',
10
+ flexDirection: 'column',
11
+ overflowX: 'hidden',
12
+ overflowY: 'auto',
13
+ backgroundColor: theme.vars.palette.background.default,
14
+ [theme.breakpoints.down('lg')]: {
15
+ position: 'fixed',
16
+ bottom: 0,
17
+ left: 0,
18
+ width: '100%',
19
+ height: `var(${SIDEBAR_MOBILE_HEIGHT})`,
20
+ boxSizing: 'content-box',
21
+ display: 'block',
22
+ borderTop: `1px solid ${theme.vars.palette.divider}`,
23
+ zIndex: theme.zIndex.appBar
24
+ }
25
+ }));
26
+ export const Sidebar = props => (React.createElement(React.Fragment, null,
27
+ makeSidebarHeightCSSVar,
28
+ React.createElement(SidebarRoot, { ...props })));
package/dist/index.d.ts CHANGED
@@ -12,6 +12,15 @@ export { default as Chip } from './components/Chip';
12
12
  export { Dialog, default as DialogDefault } from './components/Dialog';
13
13
  export { Tabs, default as TabsDefault } from './components/Tabs';
14
14
  export { Switch, default as SwitchDefault } from './components/Switch';
15
+ export { Sidebar, SIDEBAR_MOBILE_HEIGHT } from './components/Sidebar';
16
+ export { Layout, Main, Content } from './components/Layout';
17
+ export { Nav } from './components/Nav';
18
+ export { NavItem } from './components/NavItem';
19
+ export { NavLink } from './components/NavLink';
20
+ export { NavIcon } from './components/NavIcon';
21
+ export { NavText } from './components/NavText';
22
+ export { NavDesktopLimiter } from './components/NavDesktopLimiter';
23
+ export { NavDesktopDropdown } from './components/NavDesktopDropdown';
15
24
  export { nameToColor, supportedColors } from './components/Avatar/helpers';
16
25
  export { default as AccordionExpandIcon } from './components/AccordionExpandIcon';
17
26
  export { useBreakpoints } from './hooks/useBreakpoints';
@@ -23,4 +32,13 @@ export type { ChipProps } from './components/Chip';
23
32
  export type { DialogProps, DialogSize } from './components/Dialog';
24
33
  export type { TabsProps } from './components/Tabs';
25
34
  export type { SwitchProps } from './components/Switch';
35
+ export type { SidebarProps } from './components/Sidebar';
36
+ export type { LayoutProps, MainProps, ContentProps } from './components/Layout';
37
+ export type { NavProps } from './components/Nav';
38
+ export type { NavItemProps } from './components/NavItem';
39
+ export type { NavLinkProps } from './components/NavLink';
40
+ export type { NavIconProps } from './components/NavIcon';
41
+ export type { NavTextProps } from './components/NavText';
42
+ export type { NavDesktopLimiterProps } from './components/NavDesktopLimiter';
43
+ export type { NavDesktopDropdownProps } from './components/NavDesktopDropdown';
26
44
  export type { Breakpoints } from './hooks/useBreakpoints';
package/dist/index.js CHANGED
@@ -15,6 +15,15 @@ export { default as Chip } from './components/Chip';
15
15
  export { Dialog, default as DialogDefault } from './components/Dialog';
16
16
  export { Tabs, default as TabsDefault } from './components/Tabs';
17
17
  export { Switch, default as SwitchDefault } from './components/Switch';
18
+ export { Sidebar, SIDEBAR_MOBILE_HEIGHT } from './components/Sidebar';
19
+ export { Layout, Main, Content } from './components/Layout';
20
+ export { Nav } from './components/Nav';
21
+ export { NavItem } from './components/NavItem';
22
+ export { NavLink } from './components/NavLink';
23
+ export { NavIcon } from './components/NavIcon';
24
+ export { NavText } from './components/NavText';
25
+ export { NavDesktopLimiter } from './components/NavDesktopLimiter';
26
+ export { NavDesktopDropdown } from './components/NavDesktopDropdown';
18
27
  export { nameToColor, supportedColors } from './components/Avatar/helpers';
19
28
  export { default as AccordionExpandIcon } from './components/AccordionExpandIcon';
20
29
  export { useBreakpoints } from './hooks/useBreakpoints';
@@ -1,7 +1,4 @@
1
- import FormatBoldIcon from '@mui/icons-material/FormatBold';
2
- import FormatItalicIcon from '@mui/icons-material/FormatItalic';
3
- import FormatUnderlinedIcon from '@mui/icons-material/FormatUnderlined';
4
- import { Checkbox, FormControlLabel, ToggleButton, ToggleButtonGroup, Stack, Box } from '@mui/material';
1
+ import { Checkbox, FormControlLabel, Stack } from '@mui/material';
5
2
  import React from 'react';
6
3
  const meta = {
7
4
  title: 'Checkbox',
@@ -58,15 +55,5 @@ export const Screenshot = {
58
55
  React.createElement(FormControlLabel, { control: React.createElement(Checkbox, null), label: React.createElement(React.Fragment, null,
59
56
  "This is a ",
60
57
  React.createElement("strong", null, "complex"),
61
- " text") })),
62
- React.createElement("section", null,
63
- React.createElement("h3", { style: { marginBottom: '12px' } }, "Toggle Buttons"),
64
- React.createElement(Box, null,
65
- React.createElement(ToggleButtonGroup, { value: ['bold'] },
66
- React.createElement(ToggleButton, { value: "bold", "aria-label": "bold" },
67
- React.createElement(FormatBoldIcon, null)),
68
- React.createElement(ToggleButton, { value: "italic", "aria-label": "italic" },
69
- React.createElement(FormatItalicIcon, null)),
70
- React.createElement(ToggleButton, { value: "underlined", "aria-label": "underlined" },
71
- React.createElement(FormatUnderlinedIcon, null)))))))
58
+ " text") }))))
72
59
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linagora/twake-mui",
3
3
  "type": "module",
4
- "version": "8.0.0",
4
+ "version": "9.1.0",
5
5
  "description": "MUI v9 theme system for Twake applications",
6
6
  "author": "Twake",
7
7
  "license": "MIT",
@@ -54,7 +54,8 @@
54
54
  "@mui/lab": "^9.0.0-beta.6",
55
55
  "@mui/material": "^9.2.0",
56
56
  "react": "^18",
57
- "react-dom": "^18"
57
+ "react-dom": "^18",
58
+ "twake-i18n": ">=0.5.0"
58
59
  },
59
60
  "dependencies": {
60
61
  "@mui/utils": "^9.2.0",
@@ -88,6 +89,7 @@
88
89
  "react": "^18.2.0",
89
90
  "react-dom": "^18.2.0",
90
91
  "storybook": "10.3.5",
92
+ "twake-i18n": "^0.5.0",
91
93
  "vite": "8.0.9",
92
94
  "vitest": "4.1.5"
93
95
  }