@rovula/ui 0.0.48 → 0.0.50

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 (33) hide show
  1. package/dist/cjs/bundle.css +9 -3
  2. package/dist/cjs/bundle.js +3 -3
  3. package/dist/cjs/bundle.js.map +1 -1
  4. package/dist/cjs/types/components/Tree/Tree.d.ts +4 -45
  5. package/dist/cjs/types/components/Tree/Tree.stories.d.ts +9 -1
  6. package/dist/cjs/types/components/Tree/TreeItem.d.ts +4 -0
  7. package/dist/cjs/types/components/Tree/index.d.ts +4 -0
  8. package/dist/cjs/types/components/Tree/type.d.ts +76 -0
  9. package/dist/cjs/types/index.d.ts +1 -0
  10. package/dist/components/Tree/Tree.js +20 -52
  11. package/dist/components/Tree/Tree.stories.js +2210 -8
  12. package/dist/components/Tree/TreeItem.js +81 -0
  13. package/dist/components/Tree/index.js +4 -0
  14. package/dist/components/Tree/type.js +1 -0
  15. package/dist/esm/bundle.css +9 -3
  16. package/dist/esm/bundle.js +1 -1
  17. package/dist/esm/bundle.js.map +1 -1
  18. package/dist/esm/types/components/Tree/Tree.d.ts +4 -45
  19. package/dist/esm/types/components/Tree/Tree.stories.d.ts +9 -1
  20. package/dist/esm/types/components/Tree/TreeItem.d.ts +4 -0
  21. package/dist/esm/types/components/Tree/index.d.ts +4 -0
  22. package/dist/esm/types/components/Tree/type.d.ts +76 -0
  23. package/dist/esm/types/index.d.ts +1 -0
  24. package/dist/index.d.ts +82 -2
  25. package/dist/index.js +1 -0
  26. package/dist/src/theme/global.css +12 -4
  27. package/package.json +1 -1
  28. package/src/components/Tree/Tree.stories.tsx +2397 -8
  29. package/src/components/Tree/Tree.tsx +52 -188
  30. package/src/components/Tree/TreeItem.tsx +232 -0
  31. package/src/components/Tree/index.ts +5 -0
  32. package/src/components/Tree/type.ts +90 -0
  33. package/src/index.ts +1 -0
@@ -1,170 +1,26 @@
1
- import { ActionButton, Checkbox, Icon, Loading, cn } from "@/index";
2
- import React, {
3
- FC,
4
- ReactNode,
5
- useCallback,
6
- useMemo,
7
- useState,
8
- useEffect,
9
- } from "react";
1
+ import React, { FC, useCallback, useEffect, useState } from "react";
2
+ import TreeItem from "./TreeItem";
3
+ import { TreeData, TreeProps } from "./type";
10
4
 
11
- export type TreeData = {
12
- id: string;
13
- title: string;
14
- icon?: ReactNode;
15
- children?: TreeData[];
16
- };
17
-
18
- export interface TreeItemProps extends TreeData {
19
- isFirstLevel?: boolean;
20
- isLastItem: boolean;
21
- checkIsExpanded: (id: string) => boolean;
22
- checkIsChecked: (id: string) => boolean;
23
- onExpandChange?: (id: string, expanded: boolean) => void;
24
- onCheckedChange?: (id: string, checked: boolean) => void;
25
- }
26
-
27
- const TreeItem: FC<TreeItemProps> = ({
28
- id,
29
- title,
30
- children,
31
- isFirstLevel = false,
32
- isLastItem,
33
- checkIsExpanded,
34
- checkIsChecked,
35
- onExpandChange,
36
- onCheckedChange,
37
- }) => {
38
- const isChecked = useMemo(() => checkIsChecked(id), [checkIsChecked, id]);
39
- const isExpanded = useMemo(() => checkIsExpanded(id), [checkIsExpanded, id]);
40
- const hasChildren = useMemo(() => !!children?.length, [children]);
41
-
42
- const handleExpandToggle = useCallback(() => {
43
- onExpandChange?.(id, !isExpanded);
44
- }, [id, isExpanded, onExpandChange]);
45
-
46
- // TODO move to props
47
- const lineSize = 2;
48
- const horizontalLineWidth = 4;
49
- const expandButtonSize = 30;
50
- const spacing = 2;
51
-
52
- const styles = {
53
- branch: {
54
- height: isLastItem
55
- ? `calc(50% + ${lineSize}px)`
56
- : `calc(100% + ${lineSize}px)`,
57
- width: lineSize,
58
- marginTop: -lineSize,
59
- borderBottomLeftRadius: lineSize / 2,
60
- },
61
- horizontalLine: {
62
- height: lineSize,
63
- width:
64
- lineSize +
65
- horizontalLineWidth +
66
- (hasChildren ? 0 : expandButtonSize + spacing),
67
- marginLeft: -lineSize + 0.1,
68
- borderBottomLeftRadius: lineSize / 2,
69
- },
70
- expandButton: {
71
- width: expandButtonSize,
72
- height: expandButtonSize,
73
- },
74
- childPadding: {
75
- paddingLeft: isFirstLevel
76
- ? expandButtonSize / 2 - lineSize / 2
77
- : expandButtonSize / 2 + horizontalLineWidth - lineSize / 2,
78
- },
79
- };
80
-
81
- return (
82
- <div className="flex flex-row w-full">
83
- <div
84
- className={cn("bg-grey-150", { "h-1/2": isLastItem })}
85
- style={styles.branch}
86
- />
87
- <div className="flex flex-col w-full">
88
- <div className="flex items-center py-2 min-h-10">
89
- {!isFirstLevel && (
90
- <div className="bg-grey-150" style={styles.horizontalLine} />
91
- )}
92
-
93
- {isFirstLevel && !hasChildren && (
94
- <div className="flex mr-[2px]" style={styles.expandButton} />
95
- )}
96
-
97
- {hasChildren && (
98
- <div className="flex mr-[2px]" style={styles.expandButton}>
99
- <ActionButton
100
- variant="icon"
101
- size="sm"
102
- onClick={handleExpandToggle}
103
- >
104
- <Icon name={isExpanded ? "chevron-down" : "chevron-right"} />
105
- </ActionButton>
106
- </div>
107
- )}
108
- <Checkbox
109
- id={id}
110
- className="size-[16pt]"
111
- checked={isChecked}
112
- onCheckedChange={(newChecked) =>
113
- onCheckedChange?.(id, newChecked as boolean)
114
- }
115
- />
116
- <div className="ml-2 gap-1 flex items-center text-foreground">
117
- <Icon
118
- name={isExpanded ? "folder-open" : "folder"}
119
- className="fill-warning"
120
- />
121
- <label
122
- htmlFor={id}
123
- className="flex-1 cursor-pointer text-subtitle5 text-ellipsis"
124
- >
125
- {title}
126
- </label>
127
- </div>
128
- </div>
129
- {isExpanded && hasChildren && (
130
- <div className="flex flex-col" style={styles.childPadding}>
131
- {children?.map((child, idx) => (
132
- <TreeItem
133
- key={child.id}
134
- {...child}
135
- isLastItem={idx === children.length - 1}
136
- checkIsExpanded={checkIsExpanded}
137
- checkIsChecked={checkIsChecked}
138
- onExpandChange={onExpandChange}
139
- onCheckedChange={onCheckedChange}
140
- />
141
- ))}
142
- </div>
143
- )}
144
- </div>
145
- </div>
146
- );
147
- };
148
- export type TreeProps = {
149
- data: TreeData[];
150
- defaultExpandedId?: string[];
151
- defaultCheckedId?: string[];
152
- checkedId?: string[];
153
- onCheckedChange?: (checkedId: string[]) => void;
154
- defaultExpandAll?: boolean;
155
- defaultCheckAll?: boolean;
156
- hierarchicalCheck?: boolean;
157
- };
158
-
159
- export const Tree: FC<TreeProps> = ({
5
+ const Tree: FC<TreeProps> = ({
6
+ classes,
160
7
  data,
161
- defaultExpandedId = [],
162
- defaultCheckedId = [],
8
+ defaultExpandedId,
9
+ defaultCheckedId,
163
10
  checkedId,
11
+ loadingId,
12
+ renderIcon,
13
+ renderRightSection,
14
+ renderElement,
15
+ renderTitle,
16
+ onExpandChange,
164
17
  onCheckedChange,
165
18
  defaultExpandAll = false,
166
19
  defaultCheckAll = false,
167
20
  hierarchicalCheck = false,
21
+ showIcon = true,
22
+ disabled,
23
+ enableSeparatorLine = true,
168
24
  }) => {
169
25
  const [checkedState, setCheckedState] = useState<Record<string, boolean>>({});
170
26
  const [expandedState, setExpandedState] = useState<Record<string, boolean>>(
@@ -184,13 +40,13 @@ export const Tree: FC<TreeProps> = ({
184
40
  };
185
41
 
186
42
  useEffect(() => {
187
- if (defaultExpandAll) {
43
+ if (data.length && defaultExpandAll) {
188
44
  const allExpanded: Record<string, boolean> = {};
189
45
  traverseTree(data, (node) => {
190
46
  allExpanded[node.id] = true;
191
47
  });
192
48
  setExpandedState(allExpanded);
193
- } else {
49
+ } else if (defaultExpandedId?.length) {
194
50
  const initialExpandedState = defaultExpandedId.reduce((acc, id) => {
195
51
  acc[id] = true;
196
52
  return acc;
@@ -200,24 +56,31 @@ export const Tree: FC<TreeProps> = ({
200
56
  }, [data, defaultExpandedId, defaultExpandAll]);
201
57
 
202
58
  useEffect(() => {
203
- if (defaultCheckAll) {
59
+ if (data.length && defaultCheckAll) {
204
60
  const allChecked: Record<string, boolean> = {};
205
61
  traverseTree(data, (node) => {
206
62
  allChecked[node.id] = true;
207
63
  });
208
64
  setCheckedState(allChecked);
209
- } else if (!checkedId) {
65
+ } else if (
66
+ Object.keys(checkedState)?.length === 0 &&
67
+ defaultCheckedId?.length
68
+ ) {
210
69
  const initialCheckedState = defaultCheckedId.reduce((acc, id) => {
211
70
  acc[id] = true;
212
71
  return acc;
213
72
  }, {} as Record<string, boolean>);
214
73
  setCheckedState(initialCheckedState);
215
74
  }
216
- }, [data, defaultCheckedId, checkedId, defaultCheckAll]);
75
+ }, [data, defaultCheckedId, defaultCheckAll]);
217
76
 
218
- const handleExpandChange = useCallback((id: string, expanded: boolean) => {
219
- setExpandedState((prev) => ({ ...prev, [id]: expanded }));
220
- }, []);
77
+ const handleExpandChange = useCallback(
78
+ (id: string, expanded: boolean) => {
79
+ onExpandChange?.(id, expanded);
80
+ setExpandedState((prev) => ({ ...prev, [id]: expanded }));
81
+ },
82
+ [onExpandChange]
83
+ );
221
84
 
222
85
  const handleCheckedChange = useCallback(
223
86
  (id: string, checked: boolean) => {
@@ -293,39 +156,40 @@ export const Tree: FC<TreeProps> = ({
293
156
  [checkedId, checkedState]
294
157
  );
295
158
 
159
+ const checkIsLoading = useCallback(
160
+ (id: string) => {
161
+ if (loadingId) {
162
+ return loadingId.includes(id);
163
+ }
164
+ },
165
+ [loadingId]
166
+ );
167
+
296
168
  return (
297
169
  <div className="w-full">
298
170
  {data.map((item, idx) => (
299
171
  <TreeItem
300
172
  key={item.id}
301
- {...item}
173
+ classes={classes}
302
174
  isFirstLevel
303
175
  isLastItem={idx === data.length - 1}
304
176
  checkIsExpanded={checkIsExpanded}
305
177
  checkIsChecked={checkIsChecked}
306
178
  onExpandChange={handleExpandChange}
307
179
  onCheckedChange={handleCheckedChange}
180
+ checkIsLoading={checkIsLoading}
181
+ renderIcon={renderIcon}
182
+ renderElement={renderElement}
183
+ renderTitle={renderTitle}
184
+ renderRightSection={renderRightSection}
185
+ enableSeparatorLine={enableSeparatorLine}
186
+ disabled={disabled}
187
+ showIcon={showIcon}
188
+ {...item}
308
189
  />
309
190
  ))}
310
191
  </div>
311
192
  );
312
193
  };
313
- /**
314
- * TODO
315
- * -----
316
- * - Custom style
317
- * - Custom icon, elm and render props -> callback with selected*expanded
318
- * - OnClick item
319
- * - OnClick expandButton
320
- * - disabled props
321
- * - right section icon
322
- * - props for show icon, line
323
- * - props for render item
324
- * - OnLoad mode
325
- * -----
326
- * - props onLoad item
327
- * - props for hasChildren * for check to trigger on load
328
- * - animate expand
329
- * - check duplicate reversive on updateChildren
330
- * - write storybook
331
- */
194
+
195
+ export default Tree;
@@ -0,0 +1,232 @@
1
+ import { ActionButton, Checkbox, Loading } from "@/index";
2
+ import { cn } from "@/utils/cn";
3
+ import React, { FC, ReactNode, useCallback, useEffect, useMemo } from "react";
4
+ import Icon from "../Icon/Icon";
5
+ import { TreeItemProps } from "./type";
6
+
7
+ const TreeItem: FC<TreeItemProps> = ({
8
+ id,
9
+ title,
10
+ classes,
11
+ children,
12
+ isFirstLevel = false,
13
+ disabled,
14
+ icon,
15
+ showIcon,
16
+ showExpandButton,
17
+ enableSeparatorLine = true,
18
+ isLastItem,
19
+ checkIsExpanded,
20
+ checkIsChecked,
21
+ checkIsLoading,
22
+ onExpandChange,
23
+ onCheckedChange,
24
+ onClickItem,
25
+ renderIcon,
26
+ renderElement,
27
+ renderTitle,
28
+ renderRightSection,
29
+ }) => {
30
+ const isLoading = useMemo(() => checkIsLoading?.(id), [checkIsLoading, id]);
31
+ const isChecked = useMemo(() => checkIsChecked(id), [checkIsChecked, id]);
32
+ const isExpanded = useMemo(() => checkIsExpanded(id), [checkIsExpanded, id]);
33
+ const hasChildren = useMemo(() => !!children?.length, [children]);
34
+ const shouldExpandButton = useMemo(
35
+ () => (showExpandButton !== undefined ? showExpandButton : hasChildren),
36
+ [hasChildren, showExpandButton]
37
+ );
38
+
39
+ const handleExpandToggle = useCallback(() => {
40
+ onExpandChange?.(id, !isExpanded);
41
+ }, [id, isExpanded, onExpandChange]);
42
+
43
+ // TODO move to props
44
+ const lineSize = 2;
45
+ const horizontalLineWidth = 4;
46
+ const expandButtonSize = 30;
47
+ const spacing = 2;
48
+
49
+ const styles = {
50
+ branch: {
51
+ height:
52
+ isLastItem && !(isExpanded && hasChildren)
53
+ ? `calc(50% + ${lineSize}px)`
54
+ : `calc(100% + ${lineSize}px)`,
55
+ width: lineSize,
56
+ marginTop: -lineSize,
57
+ borderBottomLeftRadius: lineSize / 2,
58
+ },
59
+ horizontalLine: {
60
+ height: lineSize,
61
+ width:
62
+ lineSize +
63
+ horizontalLineWidth +
64
+ (shouldExpandButton ? 0 : expandButtonSize + spacing),
65
+ marginLeft: -lineSize + 0.1,
66
+ borderBottomLeftRadius: lineSize / 2,
67
+ },
68
+ expandButton: {
69
+ width: expandButtonSize,
70
+ height: expandButtonSize,
71
+ },
72
+ childPadding: {
73
+ paddingLeft: isFirstLevel
74
+ ? expandButtonSize / 2 - lineSize / 2
75
+ : expandButtonSize / 2 + horizontalLineWidth - lineSize / 2,
76
+ },
77
+ };
78
+
79
+ useEffect(() => {
80
+ if (isExpanded && !isLoading && !hasChildren) {
81
+ handleExpandToggle();
82
+ }
83
+ }, [isLoading, handleExpandToggle]);
84
+
85
+ const handleOnClickItem = useCallback(() => {
86
+ onClickItem?.(id);
87
+ }, [onClickItem, id]);
88
+
89
+ const defaultIcon = (
90
+ <Icon
91
+ name={isExpanded ? "folder-open" : "folder"}
92
+ className="fill-warning"
93
+ />
94
+ );
95
+
96
+ const customIcon =
97
+ icon ??
98
+ renderIcon?.({
99
+ id,
100
+ expanded: isExpanded,
101
+ selected: isChecked,
102
+ });
103
+
104
+ const rightIcon = renderRightSection?.({
105
+ id,
106
+ expanded: isExpanded,
107
+ selected: isChecked,
108
+ });
109
+
110
+ const titleContent = renderTitle
111
+ ? renderTitle({ id, title, expanded: isExpanded, selected: isChecked })
112
+ : title;
113
+
114
+ const elementWrapper = (content: ReactNode) =>
115
+ renderElement
116
+ ? renderElement({
117
+ id,
118
+ expanded: isExpanded,
119
+ selected: isChecked,
120
+ children: content,
121
+ styles,
122
+ onClick: handleOnClickItem,
123
+ })
124
+ : content;
125
+
126
+ return elementWrapper(
127
+ <div className={cn("flex flex-row w-full ", classes?.elementWrapper)}>
128
+ <div
129
+ className={cn("bg-grey-150", classes?.branch)}
130
+ style={styles.branch}
131
+ />
132
+ <div className={cn("flex flex-col w-full", classes?.itemWrapper)}>
133
+ <div
134
+ className={cn(
135
+ "flex items-center py-2 min-h-10",
136
+ classes?.itemContainer
137
+ )}
138
+ >
139
+ {!isFirstLevel && (
140
+ <div
141
+ className={cn("bg-grey-150", classes?.horizontalLine)}
142
+ style={styles.horizontalLine}
143
+ />
144
+ )}
145
+ {isFirstLevel && !shouldExpandButton && (
146
+ <div
147
+ className={cn("flex mr-[2px]", classes?.expandButton)}
148
+ style={styles.expandButton}
149
+ />
150
+ )}
151
+ {shouldExpandButton && (
152
+ <div
153
+ className={cn("flex mr-[2px]", classes?.expandButton)}
154
+ style={styles.expandButton}
155
+ onClick={!isLoading && handleExpandToggle}
156
+ >
157
+ <ActionButton variant="icon" size="sm">
158
+ {isLoading ? (
159
+ <Loading />
160
+ ) : (
161
+ <Icon name={isExpanded ? "chevron-down" : "chevron-right"} />
162
+ )}
163
+ </ActionButton>
164
+ </div>
165
+ )}
166
+ <Checkbox
167
+ id={id}
168
+ className={cn("size-[16pt]", classes?.checkbox)}
169
+ checked={isChecked}
170
+ disabled={disabled}
171
+ onCheckedChange={(newChecked) =>
172
+ onCheckedChange?.(id, newChecked as boolean)
173
+ }
174
+ />
175
+ <div
176
+ className={cn(
177
+ "ml-2 gap-1 flex flex-1 items-center text-foreground",
178
+ classes?.item
179
+ )}
180
+ onClick={handleOnClickItem}
181
+ >
182
+ {showIcon ? customIcon || defaultIcon : null}
183
+ <div
184
+ className={cn(
185
+ "flex flex-1 cursor-pointer text-subtitle5 text-ellipsis",
186
+ classes?.title
187
+ )}
188
+ >
189
+ {titleContent}
190
+ </div>
191
+ </div>
192
+ {rightIcon}
193
+ </div>
194
+ {isExpanded && hasChildren && (
195
+ <div
196
+ className={cn("flex flex-col", classes?.childrenWrapper)}
197
+ style={styles.childPadding}
198
+ >
199
+ {children?.map((child, idx) => (
200
+ <TreeItem
201
+ key={child.id}
202
+ classes={classes}
203
+ isLastItem={idx === children.length - 1}
204
+ checkIsExpanded={checkIsExpanded}
205
+ checkIsChecked={checkIsChecked}
206
+ checkIsLoading={checkIsLoading}
207
+ onExpandChange={onExpandChange}
208
+ onCheckedChange={onCheckedChange}
209
+ renderIcon={renderIcon}
210
+ renderElement={renderElement}
211
+ renderTitle={renderTitle}
212
+ disabled={disabled}
213
+ showIcon={showIcon}
214
+ {...child}
215
+ />
216
+ ))}
217
+ </div>
218
+ )}
219
+ {enableSeparatorLine && isFirstLevel && !isLastItem && (
220
+ <div
221
+ className={cn(
222
+ "bg-grey-150 w-full h-[2px] rounded",
223
+ classes?.separatorLine
224
+ )}
225
+ />
226
+ )}
227
+ </div>
228
+ </div>
229
+ );
230
+ };
231
+
232
+ export default TreeItem;
@@ -0,0 +1,5 @@
1
+ import Tree from "./Tree";
2
+ import TreeItem from "./TreeItem";
3
+
4
+ export * from "./type";
5
+ export { Tree, TreeItem };
@@ -0,0 +1,90 @@
1
+ import { CSSProperties, ReactNode } from "react";
2
+
3
+ export type TreeData = {
4
+ id: string;
5
+ title: string;
6
+ icon?: ReactNode;
7
+ disabled?: boolean;
8
+ onClickItem?: (id: string) => void;
9
+ children?: TreeData[];
10
+ renderIcon?: (params: {
11
+ id: string;
12
+ expanded: boolean;
13
+ selected: boolean;
14
+ }) => ReactNode;
15
+ };
16
+
17
+ export interface TreeItemProps extends TreeData {
18
+ isFirstLevel?: boolean;
19
+ isLastItem: boolean;
20
+ disabled?: boolean;
21
+ showIcon?: boolean;
22
+ showExpandButton?: boolean;
23
+ enableSeparatorLine?: boolean;
24
+ checkIsExpanded: (id: string) => boolean;
25
+ checkIsChecked: (id: string) => boolean;
26
+ checkIsLoading?: (id: string) => void;
27
+ onExpandChange?: (id: string, expanded: boolean) => void;
28
+ onCheckedChange?: (id: string, checked: boolean) => void;
29
+ renderRightSection?: (params: {
30
+ id: string;
31
+ expanded: boolean;
32
+ selected: boolean;
33
+ }) => ReactNode;
34
+ renderElement?: (params: {
35
+ id: string;
36
+ expanded: boolean;
37
+ selected: boolean;
38
+ children: ReactNode;
39
+ styles: {
40
+ branch: CSSProperties;
41
+ horizontalLine: CSSProperties;
42
+ expandButton: CSSProperties;
43
+ childPadding: CSSProperties;
44
+ };
45
+ onClick?: TreeItemProps["onClickItem"];
46
+ }) => ReactNode;
47
+ renderTitle?: (params: {
48
+ id: string;
49
+ title: string;
50
+ expanded: boolean;
51
+ selected: boolean;
52
+ }) => ReactNode;
53
+ classes?: Partial<{
54
+ elementWrapper: string;
55
+ branch: string;
56
+ itemWrapper: string;
57
+ itemContainer: string;
58
+ horizontalLine: string;
59
+ expandButton: string;
60
+ separatorLine: string;
61
+ checkbox: string;
62
+ item: string;
63
+ title: string;
64
+ childrenWrapper: string;
65
+ }>;
66
+ }
67
+
68
+ export interface TreeProps
69
+ extends Pick<
70
+ TreeItemProps,
71
+ | "renderIcon"
72
+ | "renderRightSection"
73
+ | "renderElement"
74
+ | "renderTitle"
75
+ | "showIcon"
76
+ | "disabled"
77
+ | "enableSeparatorLine"
78
+ | "classes"
79
+ > {
80
+ data: TreeData[];
81
+ defaultExpandedId?: string[];
82
+ defaultCheckedId?: string[];
83
+ checkedId?: string[];
84
+ loadingId?: string[];
85
+ onExpandChange?: (id: string, expanded: boolean) => void;
86
+ onCheckedChange?: (checkedId: string[]) => void;
87
+ defaultExpandAll?: boolean;
88
+ defaultCheckAll?: boolean;
89
+ hierarchicalCheck?: boolean;
90
+ }
package/src/index.ts CHANGED
@@ -39,6 +39,7 @@ export * from "./components/Tooltip/TooltipSimple";
39
39
  export * from "./components/Toast/Toast";
40
40
  export * from "./components/Toast/Toaster";
41
41
  export * from "./components/Toast/useToast";
42
+ export * from "./components/Tree";
42
43
 
43
44
  // Export component types
44
45
  export type { ButtonProps } from "./components/Button/Button";