w-design-system 0.0.29 → 0.0.30

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.
@@ -0,0 +1,90 @@
1
+ import { MouseEvent, PropsWithChildren, useCallback, useEffect, useState } from 'react'
2
+ import { SidebarGroupContainer, SidebarGroupHeaderContainer } from './styles'
3
+ import React from 'react'
4
+ import { IconWrapper } from '../../IconWrapper'
5
+ import { Paragraph } from '../../Paragraph'
6
+ import { theme } from '../../Themes'
7
+ import { chevronDownSVG, lockSVG } from '../../../assets/icon'
8
+
9
+ export interface SidebarGroupProps {
10
+ title: string
11
+ icon: string
12
+ sidebarOpen: boolean
13
+ disabled?: boolean
14
+ isCurrentGroup?: boolean
15
+ onClick: () => void
16
+ }
17
+
18
+ const SidebarGroup = ({
19
+ title,
20
+ icon,
21
+ children,
22
+ sidebarOpen,
23
+ disabled,
24
+ onClick,
25
+ isCurrentGroup,
26
+ }: PropsWithChildren<SidebarGroupProps>) => {
27
+ const [groupOpen, setGroupOpen] = useState(false)
28
+
29
+ useEffect(() => {
30
+ if (groupOpen && disabled) setGroupOpen(false)
31
+ }, [groupOpen, disabled])
32
+
33
+ const onClickHandler = useCallback(
34
+ (event: MouseEvent<HTMLDivElement>) => {
35
+ if (disabled) return
36
+
37
+ if (!sidebarOpen) {
38
+ onClick()
39
+ } else {
40
+ event.stopPropagation()
41
+ setGroupOpen(!groupOpen)
42
+ }
43
+ },
44
+ [disabled, sidebarOpen, groupOpen, onClick],
45
+ )
46
+
47
+ let color = '#fff'
48
+
49
+ if (disabled) {
50
+ color = 'rgba(255,255,255,0.3)'
51
+ }
52
+
53
+ if (isCurrentGroup) {
54
+ color = '#217efd'
55
+ }
56
+
57
+ return (
58
+ <SidebarGroupContainer open={sidebarOpen} groupOpen={groupOpen}>
59
+ <SidebarGroupHeaderContainer
60
+ disabled={disabled}
61
+ sidebarOpen={sidebarOpen}
62
+ groupOpen={groupOpen}
63
+ onClick={onClickHandler}
64
+ >
65
+ <IconWrapper src={icon} color={color} />
66
+ {sidebarOpen && (
67
+ <span className="title">
68
+ <Paragraph size="medium" heavyBod={isCurrentGroup} color={disabled ? theme.colors.shade40 : 'white'}>
69
+ {title}
70
+ </Paragraph>
71
+ </span>
72
+ )}
73
+ {sidebarOpen &&
74
+ (!disabled ? (
75
+ <IconWrapper
76
+ className={`icon ${groupOpen ? 'icon-rotate' : ''}`}
77
+ src={chevronDownSVG}
78
+ width="16px"
79
+ color={theme.colors.shade30}
80
+ />
81
+ ) : (
82
+ <IconWrapper src={lockSVG} width="14px" color="white" />
83
+ ))}
84
+ </SidebarGroupHeaderContainer>
85
+ {children}
86
+ </SidebarGroupContainer>
87
+ )
88
+ }
89
+
90
+ export default SidebarGroup
@@ -0,0 +1,83 @@
1
+ import styled from 'styled-components'
2
+ import { theme } from '../../Themes'
3
+
4
+ interface SidebarGroupContainerProps {
5
+ open: boolean
6
+ groupOpen: boolean
7
+ }
8
+
9
+ export const SidebarGroupContainer = styled.div<SidebarGroupContainerProps>`
10
+ display: flex;
11
+ flex-direction: column;
12
+ cursor: pointer;
13
+
14
+ height: ${({ groupOpen, open }) => (groupOpen && open ? 'auto' : '40px')};
15
+ background-color: ${({ groupOpen, open }) => (groupOpen && open ? 'rgba(255, 255, 255, 0.1)' : 'inherit')};
16
+ overflow: hidden;
17
+ border-radius: 4px;
18
+
19
+ &:hover {
20
+ background-color: #192d53;
21
+ }
22
+
23
+ .wrapperTitle {
24
+ cursor: pointer;
25
+ min-height: ${({ groupOpen, open }) => (groupOpen && open ? '40px' : '54px')};
26
+ padding: 0 14px;
27
+ display: flex;
28
+ align-items: center;
29
+ height: 100%;
30
+ gap: 30px;
31
+ justify-content: left;
32
+ svg {
33
+ width: 20px;
34
+ }
35
+ }
36
+
37
+ &.locked {
38
+ pointer-events: none;
39
+ }
40
+ `
41
+
42
+ interface SidebarGroupContainerHeaderProps {
43
+ sidebarOpen: boolean
44
+ groupOpen: boolean
45
+ disabled?: boolean
46
+ }
47
+
48
+ export const SidebarGroupHeaderContainer = styled.div<SidebarGroupContainerHeaderProps>`
49
+ display: flex;
50
+ align-items: center;
51
+ min-height: ${({ groupOpen, sidebarOpen }) => (groupOpen && sidebarOpen ? '40px' : '40px')};
52
+ ${({ groupOpen, sidebarOpen }) => groupOpen && sidebarOpen && 'border-bottom: 1px solid rgba(255, 255, 255, 0.1)'};
53
+ justify-content: ${sidebarOpen => (sidebarOpen ? 'space-between' : 'center')};
54
+ text-wrap: nowrap;
55
+
56
+ padding: 8px 12px;
57
+ cursor: pointer;
58
+ height: 100%;
59
+ gap: 24px;
60
+
61
+ color: ${({ disabled }) => (disabled ? 'rgba(255, 255, 255, 0.3)' : '#fff')};
62
+
63
+ .title {
64
+ width: 100%;
65
+ }
66
+
67
+ svg {
68
+ width: 20px;
69
+ }
70
+ `
71
+
72
+ export const LockedModal = styled.div`
73
+ width: 400px;
74
+ height: 150px;
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ text-align: center;
79
+
80
+ .item-locked {
81
+ color: ${theme.colors.honey40};
82
+ }
83
+ `
@@ -0,0 +1,69 @@
1
+ import React from 'react'
2
+ import { IconWrapper } from '../../IconWrapper'
3
+ import { Paragraph } from '../../Paragraph'
4
+ import { Bullet, Container } from './styles'
5
+ import { theme } from '../../Themes'
6
+
7
+ export interface SidebarItemProps {
8
+ disabled?: boolean
9
+ title: string
10
+ icon?: string
11
+ onClick: () => void
12
+ isCurrentItem: boolean
13
+ sidebarOpen: boolean
14
+ isInsideGroup?: boolean
15
+ }
16
+
17
+ const SidebarItem = ({
18
+ title,
19
+ disabled,
20
+ icon,
21
+ isInsideGroup,
22
+ isCurrentItem,
23
+ sidebarOpen,
24
+ onClick,
25
+ }: SidebarItemProps) => {
26
+ let color = '#fff'
27
+
28
+ if (disabled) {
29
+ color = 'rgba(255,255,255,0.3)'
30
+ }
31
+
32
+ if (isCurrentItem) {
33
+ color = theme.colors.cyan30
34
+ }
35
+
36
+ return (
37
+ <Container
38
+ isCurrentItem={isCurrentItem}
39
+ disabled={disabled ?? false}
40
+ sidebarOpen={sidebarOpen}
41
+ isInsideGroup={isInsideGroup}
42
+ onClick={() => !disabled && onClick()}
43
+ >
44
+ {isInsideGroup ? (
45
+ <Bullet />
46
+ ) : (
47
+ icon && (
48
+ <div className="icon">
49
+ <IconWrapper src={icon} color={color} />
50
+ </div>
51
+ )
52
+ )}
53
+ {sidebarOpen && (
54
+ <span className="title">
55
+ <Paragraph
56
+ size={!isInsideGroup ? 'medium' : 'small'}
57
+ /* eslint-disable-next-line no-nested-ternary */
58
+ color={disabled ? theme.colors.shade40 : !isInsideGroup ? 'white' : theme.colors.shade10}
59
+ heavyBod={isCurrentItem}
60
+ >
61
+ {title}
62
+ </Paragraph>
63
+ </span>
64
+ )}
65
+ </Container>
66
+ )
67
+ }
68
+
69
+ export default SidebarItem
@@ -0,0 +1,70 @@
1
+ import styled from 'styled-components'
2
+
3
+ interface SidebarItemContainerProps {
4
+ sidebarOpen: boolean
5
+ disabled: boolean
6
+ isCurrentItem: boolean
7
+ isInsideGroup?: boolean
8
+ }
9
+
10
+ export const Container = styled.div<SidebarItemContainerProps>`
11
+ display: flex;
12
+ justify-content: ${({ sidebarOpen }) => (sidebarOpen ? 'space-between' : 'center')};
13
+ align-items: center;
14
+ min-height: 38px;
15
+ padding: 8px;
16
+ gap: 24px;
17
+
18
+ color: ${({ disabled }) => (disabled ? 'rgba(255,255,255,0.4)' : '#fff')};
19
+ cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')};
20
+ border-radius: ${({ isInsideGroup }) => !isInsideGroup && '4px'};
21
+ &:hover {
22
+ background-color: #192d53;
23
+ }
24
+ &:not(:last-child) {
25
+ border-bottom: ${({ isInsideGroup }) => isInsideGroup && '1px solid rgba(255, 255, 255, 0.1)'};
26
+ }
27
+
28
+ .wrapper {
29
+ padding: 4px;
30
+ cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')};
31
+ display: flex;
32
+ align-items: center;
33
+ }
34
+
35
+ .title {
36
+ text-decoration: none;
37
+ width: 100%;
38
+ text-wrap: nowrap;
39
+ }
40
+
41
+ .lock {
42
+ position: absolute;
43
+ right: 20px;
44
+ svg {
45
+ opacity: 0.5;
46
+ position: absolute;
47
+ margin-bottom: 16px;
48
+ font-size: 10px;
49
+ }
50
+ }
51
+ `
52
+
53
+ export const Bullet = styled.div`
54
+ position: relative;
55
+ width: 24px;
56
+ height: 100%;
57
+
58
+ &::after {
59
+ position: absolute;
60
+ background: #fff;
61
+
62
+ right: 0px;
63
+ top: -2.5px;
64
+ content: '';
65
+ width: 5px;
66
+ height: 5px;
67
+
68
+ border-radius: 50%;
69
+ }
70
+ `
@@ -0,0 +1,23 @@
1
+ import React from 'react'
2
+ import { chevronLeftSVG, chevronRightSVG } from '../../../assets/icon'
3
+ import { IconWrapper } from '../../IconWrapper'
4
+ import { SidebarLogoContainer } from './styles'
5
+
6
+ export interface SidebarLogoProps {
7
+ sidebarOpen: boolean
8
+ logoOpacity?: number
9
+ setSidebarOpen: (value: boolean) => void
10
+ logoUrl: string
11
+ }
12
+
13
+ const SidebarLogo = ({ sidebarOpen, setSidebarOpen, logoOpacity, logoUrl }: SidebarLogoProps) => {
14
+ return (
15
+ <SidebarLogoContainer logoUrl={logoUrl} logoOpacity={logoOpacity} sidebarOpen={sidebarOpen}>
16
+ <button type="button" onClick={() => setSidebarOpen(!sidebarOpen)} className="close" aria-label="Fechar menu">
17
+ <IconWrapper src={sidebarOpen ? chevronRightSVG : chevronLeftSVG} color="#fff" width="12px" height="12px" />
18
+ </button>
19
+ <div className="logo" />
20
+ </SidebarLogoContainer>
21
+ )
22
+ }
23
+ export default SidebarLogo
@@ -0,0 +1,53 @@
1
+ import styled from 'styled-components'
2
+
3
+ interface SidebarLogoContainerProps {
4
+ logoOpacity?: number
5
+ sidebarOpen: boolean
6
+ logoUrl: string
7
+ }
8
+
9
+ export const SidebarLogoContainer = styled.div<SidebarLogoContainerProps>`
10
+ position: relative;
11
+
12
+ --padding: 8px;
13
+
14
+
15
+ margin-bottom: 32px;
16
+
17
+ .logo {
18
+ height: 40px;
19
+
20
+ background: url(${({ logoUrl }) => logoUrl});
21
+ background-size: ${({ sidebarOpen: open }) => (open ? `202px 40px` : '141px 28px')};
22
+ background-repeat: no-repeat;
23
+ background-position: -8px 0px;
24
+
25
+ opacity: ${({ logoOpacity }) => logoOpacity ?? 1};
26
+ }
27
+
28
+ .close {
29
+ --width: 28px;
30
+ --height: 32px;
31
+ display: flex;
32
+ position: absolute;
33
+
34
+ align-items: center;
35
+ justify-content: center;
36
+
37
+ height: var(--height);
38
+ width: var(--width);
39
+
40
+ background: #001640;
41
+ color: #fff;
42
+
43
+ border-radius: 0 var(--width) var(--width) 0;
44
+
45
+ top: 42px;
46
+ right: calc((var(--padding) + var(--width)) * -1);
47
+
48
+ border: none;
49
+ cursor: pointer;
50
+
51
+ z-index: 2;
52
+ }
53
+ `
@@ -0,0 +1,127 @@
1
+ import { useState } from 'react'
2
+ import SidebarGroup from './SidebarGroup'
3
+ import SidebarItem from './SidebarItem'
4
+ import SidebarLogo from './SidebarLogo'
5
+
6
+ import React from 'react'
7
+ import { SidebarContainer, SidebarScrollContainer, SidebarScrollWrapperContainer, SidebarSpacer } from './styles'
8
+
9
+ export interface SidebarLayoutItemTypeItem {
10
+ id: string
11
+ type: 'item'
12
+ }
13
+
14
+ export interface SidebarLayoutItemTypeGroup {
15
+ id: string
16
+ type: 'group'
17
+ }
18
+
19
+ export interface SidebarLayoutItemTypeSpacer {
20
+ type: 'spacer'
21
+ }
22
+
23
+ export interface SidebarItemData {
24
+ showInMenu?: boolean
25
+ menuTitle: string
26
+ icon: string
27
+ disabled: boolean
28
+ link?: string
29
+ }
30
+
31
+ export interface SidebarGroupData {
32
+ name: string
33
+ link?: string
34
+ icon: string
35
+ itemIds: string[]
36
+ }
37
+
38
+ export interface SidebarProps {
39
+ layout: (SidebarLayoutItemTypeItem | SidebarLayoutItemTypeGroup | SidebarLayoutItemTypeSpacer)[]
40
+ currentItemId: string
41
+ currentGroupId: string
42
+ setLink: (link: string) => void
43
+ items: Record<string, SidebarItemData>
44
+ groups: Record<string, SidebarGroupData>
45
+ logoUrl: string
46
+ logoOpacity?: number
47
+ }
48
+
49
+ export const Sidebar = ({
50
+ logoUrl,
51
+ layout,
52
+ currentItemId,
53
+ currentGroupId,
54
+ setLink,
55
+ items,
56
+ logoOpacity,
57
+ groups,
58
+ }: SidebarProps) => {
59
+ const [sidebarOpen, setSidebarOpen] = useState(true)
60
+
61
+ const sidebarItemClickHandler = (itemId: string) => {
62
+ const item = items[itemId]
63
+ const link = item?.link
64
+ if (link) setLink(link)
65
+ }
66
+
67
+ return (
68
+ <SidebarContainer sidebarOpen={sidebarOpen}>
69
+ <SidebarLogo
70
+ logoUrl={logoUrl}
71
+ sidebarOpen={sidebarOpen}
72
+ setSidebarOpen={setSidebarOpen}
73
+ logoOpacity={logoOpacity}
74
+ />
75
+ <SidebarScrollWrapperContainer>
76
+ <SidebarScrollContainer>
77
+ {layout
78
+ .map((item, index) => ({ id: index, disabled: undefined, ...item }))
79
+ .map(item => {
80
+ if (item.type === 'spacer') return <SidebarSpacer key={item.id} />
81
+ if (item.type === 'item')
82
+ return (
83
+ <SidebarItem
84
+ key={item.id}
85
+ title={items[item.id]?.menuTitle}
86
+ icon={items[item.id]?.icon}
87
+ disabled={items[item.id]?.disabled}
88
+ onClick={() => sidebarItemClickHandler(item.id)}
89
+ sidebarOpen={sidebarOpen}
90
+ isCurrentItem={currentItemId === item.id}
91
+ />
92
+ )
93
+ if (item.type === 'group') {
94
+ const group = groups[item.id]
95
+ return (
96
+ <SidebarGroup
97
+ disabled={group.itemIds.every(id => items[id]?.disabled)}
98
+ key={item.id}
99
+ title={group.name}
100
+ icon={group.icon}
101
+ sidebarOpen={sidebarOpen}
102
+ onClick={() => group.link && setLink(group.link)}
103
+ isCurrentGroup={currentGroupId === item.id}
104
+ >
105
+ {group.itemIds
106
+ .filter(id => items[id]?.showInMenu !== false)
107
+ .map(id => (
108
+ <SidebarItem
109
+ key={id}
110
+ title={items[id]?.menuTitle}
111
+ disabled={items[id]?.disabled}
112
+ onClick={() => sidebarItemClickHandler(id)}
113
+ isCurrentItem={currentItemId === id}
114
+ sidebarOpen={sidebarOpen}
115
+ isInsideGroup
116
+ />
117
+ ))}
118
+ </SidebarGroup>
119
+ )
120
+ }
121
+ return null
122
+ })}
123
+ </SidebarScrollContainer>
124
+ </SidebarScrollWrapperContainer>
125
+ </SidebarContainer>
126
+ )
127
+ }
@@ -0,0 +1,51 @@
1
+ import React from 'react'
2
+ import { Meta, StoryObj } from '@storybook/react'
3
+ import { Sidebar } from './index'
4
+ import { action } from '@storybook/addon-actions'
5
+ import { dashboardSVG } from '../../assets/icon'
6
+
7
+ const meta: Meta<typeof Sidebar> = {
8
+ title: 'Components/Sidebar',
9
+ tags: ['autodocs'],
10
+
11
+ component: Sidebar,
12
+ } satisfies Meta<typeof Sidebar>
13
+
14
+ export default meta
15
+
16
+ type Story = StoryObj<typeof Sidebar>
17
+
18
+ export const Playground: Story = {
19
+ render: () => {
20
+ return (
21
+ <Sidebar
22
+ layout={[
23
+ {
24
+ type: 'item',
25
+ id: 'DASHBOARD',
26
+ },
27
+ { type: 'group', id: 'group1' },
28
+ ]}
29
+ items={{
30
+ DASHBOARD: {
31
+ menuTitle: 'Dashboard',
32
+ icon: dashboardSVG,
33
+ link: '/dashboard',
34
+ disabled: false,
35
+ },
36
+ }}
37
+ currentItemId={''}
38
+ currentGroupId={''}
39
+ setLink={action('setLink')}
40
+ groups={{
41
+ group1: {
42
+ name: 'Group 1',
43
+ icon: dashboardSVG,
44
+ itemIds: ['DASHBOARD'],
45
+ },
46
+ }}
47
+ logoUrl={''}
48
+ />
49
+ )
50
+ },
51
+ }
@@ -0,0 +1,80 @@
1
+ import styled from 'styled-components'
2
+ import { theme } from '../Themes'
3
+
4
+ interface SidebarContainerProps {
5
+ sidebarOpen: boolean
6
+ }
7
+
8
+ export const SidebarContainer = styled.div<SidebarContainerProps>`
9
+ display: flex;
10
+ flex-direction: column;
11
+ grid-area: sidebar;
12
+ position: relative;
13
+ box-sizing: border-box;
14
+ color: white;
15
+ background: ${theme.colors.cyan50};
16
+ height: 100vh;
17
+ height: 100dvh;
18
+ padding: 16px 16px 0 16px;
19
+ width: min-content;
20
+ border-radius: ${({ sidebarOpen }) => sidebarOpen && '0 60px 0px 0;'};
21
+
22
+ /* Mantenha o estilo do container ao passar o mouse */
23
+ &:hover {
24
+ &::-webkit-scrollbar {
25
+ width: 0;
26
+ }
27
+
28
+ &::-webkit-scrollbar-track {
29
+ -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
30
+ }
31
+
32
+ &::-webkit-scrollbar-thumb {
33
+ -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
34
+ }
35
+ }
36
+ `
37
+
38
+ export const SidebarScrollContainer = styled.div`
39
+ display: flex;
40
+ flex-direction: column;
41
+ gap: 16px;
42
+ min-height: calc(100% - 36px);
43
+ margin-bottom: 36px;
44
+ `
45
+
46
+ export const SidebarScrollWrapperContainer = styled.div`
47
+ height: 100%;
48
+ overflow-y: auto;
49
+ overflow-x: hidden;
50
+
51
+ &::-webkit-scrollbar {
52
+ width: 0;
53
+ }
54
+
55
+ &::-webkit-scrollbar-track {
56
+ -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
57
+ }
58
+
59
+ &::-webkit-scrollbar-thumb {
60
+ -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
61
+ }
62
+
63
+ &:hover {
64
+ &::-webkit-scrollbar {
65
+ width: 0;
66
+ }
67
+
68
+ &::-webkit-scrollbar-track {
69
+ -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
70
+ }
71
+
72
+ &::-webkit-scrollbar-thumb {
73
+ -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
74
+ }
75
+ }
76
+ `
77
+
78
+ export const SidebarSpacer = styled.div`
79
+ flex-grow: 1;
80
+ `
@@ -1,9 +1,17 @@
1
1
  import { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from 'react'
2
2
  import type { DatePickerProps } from '../DatePicker'
3
3
  import type { DateRangePickerProps } from '../DateRangePicker'
4
- import type { SelectProps } from '../Select'
5
- import type { SelectRef } from '../Select'
6
4
  import type { PaginateProps } from '../Paginate'
5
+ import type { SelectProps, SelectRef } from '../Select'
6
+ import type {
7
+ SidebarGroupData,
8
+ SidebarItemData,
9
+ SidebarLayoutItemTypeGroup,
10
+ SidebarLayoutItemTypeItem,
11
+ SidebarLayoutItemTypeSpacer,
12
+ SidebarProps,
13
+ } from '../Sidebar'
14
+
7
15
  type ColorsVariant = 'primary' | 'secondary' | 'success' | 'danger' | 'gray' | 'warning' | 'yellow'
8
16
  type ColorsVariantBanner = 'notice' | 'success' | 'danger' | 'warning'
9
17
 
@@ -53,4 +61,16 @@ export interface TabsTypes extends HTMLAttributes<HTMLDivElement> {
53
61
  onTabChange?: (activeTab: string) => void
54
62
  }
55
63
 
56
- export { DatePickerProps, DateRangePickerProps, SelectProps, SelectRef, PaginateProps }
64
+ export {
65
+ DatePickerProps,
66
+ DateRangePickerProps,
67
+ PaginateProps,
68
+ SelectProps,
69
+ SelectRef,
70
+ SidebarGroupData,
71
+ SidebarItemData,
72
+ SidebarLayoutItemTypeGroup,
73
+ SidebarLayoutItemTypeItem,
74
+ SidebarLayoutItemTypeSpacer,
75
+ SidebarProps,
76
+ }