@blockbite/ui 1.0.4 → 1.0.5
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.
- package/package.json +2 -2
- package/src/AutocompleteDropdown.tsx +109 -0
- package/src/Badge.tsx +21 -0
- package/src/BitePreview.tsx +88 -0
- package/src/Button.tsx +60 -0
- package/src/ButtonToggle.tsx +165 -0
- package/src/Chapter.tsx +22 -0
- package/src/ChapterDivider.tsx +25 -0
- package/src/Checkbox.tsx +30 -0
- package/src/DisappearingMessage.tsx +41 -0
- package/src/DropdownPicker.tsx +72 -0
- package/src/EmptyState.tsx +32 -0
- package/src/FloatingPanel.tsx +59 -0
- package/src/FocalPointControl.tsx +58 -0
- package/src/Icon.tsx +17 -0
- package/src/LinkPicker.tsx +94 -0
- package/src/MediaPicker.tsx +161 -0
- package/src/MetricsControl.tsx +179 -0
- package/src/Modal.tsx +171 -0
- package/src/NewWindowPortal.tsx +68 -0
- package/src/Notice.tsx +32 -0
- package/src/PasswordInput.tsx +53 -0
- package/src/Popover.tsx +68 -0
- package/src/RangeSlider.tsx +68 -0
- package/src/ResponsiveImage.tsx +42 -0
- package/src/ResponsiveVideo.tsx +20 -0
- package/src/ScrollList.tsx +24 -0
- package/src/SectionList.tsx +166 -0
- package/src/SelectControlWrapper.tsx +47 -0
- package/src/SingleBlockTypeAppender.tsx +37 -0
- package/src/SlideIn.tsx +34 -0
- package/src/Spinner.tsx +24 -0
- package/src/Tabs.tsx +102 -0
- package/src/Tag.tsx +44 -0
- package/src/TextControl.tsx +74 -0
- package/src/TextControlLabel.tsx +27 -0
- package/src/ToggleGroup.tsx +72 -0
- package/src/ToggleSwitch.tsx +37 -0
- package/src/Wrap.tsx +23 -0
- package/src/_dev/App.css +42 -0
- package/src/_dev/App.tsx +183 -0
- package/src/_dev/assets/react.svg +1 -0
- package/src/_dev/index.css +68 -0
- package/src/_dev/main.tsx +10 -0
- package/src/_dev/vite-env.d.ts +1 -0
- package/src/types/ui-fallbacks.d.ts +7 -0
- package/src/types.ts +4 -0
- package/src/ui.css +66 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Wrap } from "./Wrap";
|
|
2
|
+
import { Button } from "@wordpress/components";
|
|
3
|
+
import { useRef, useState } from "@wordpress/element";
|
|
4
|
+
|
|
5
|
+
import { TextControl } from "./TextControl";
|
|
6
|
+
import classNames from "classnames";
|
|
7
|
+
|
|
8
|
+
import PlusIcon from "@blockbite/icons/dist/Plus";
|
|
9
|
+
import TrashIcon from "@blockbite/icons/dist/Trash";
|
|
10
|
+
|
|
11
|
+
type SectionTypeProps = {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
ref?: any;
|
|
15
|
+
code?: string;
|
|
16
|
+
content?: any;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type SectionListProps = {
|
|
20
|
+
sections: SectionTypeProps[];
|
|
21
|
+
setSections: React.Dispatch<React.SetStateAction<any[]>>;
|
|
22
|
+
activeSection: any;
|
|
23
|
+
setActiveSection: any;
|
|
24
|
+
Update: any;
|
|
25
|
+
newSection: any;
|
|
26
|
+
setCode: (code: string) => void;
|
|
27
|
+
addons?: JSX.Element;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const SectionList = ({
|
|
31
|
+
sections,
|
|
32
|
+
setSections,
|
|
33
|
+
activeSection,
|
|
34
|
+
setActiveSection,
|
|
35
|
+
Update,
|
|
36
|
+
newSection,
|
|
37
|
+
setCode,
|
|
38
|
+
addons,
|
|
39
|
+
}: SectionListProps) => {
|
|
40
|
+
const [renameSection, setRenameSection] = useState({
|
|
41
|
+
id: "",
|
|
42
|
+
name: "",
|
|
43
|
+
});
|
|
44
|
+
const textFieldRef = useRef<HTMLInputElement | null>(null);
|
|
45
|
+
|
|
46
|
+
const addSection = () => {
|
|
47
|
+
const createNewSection = { ...newSection() };
|
|
48
|
+
setSections([...sections, createNewSection]);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const removeSection = () => {
|
|
52
|
+
if (!activeSection || sections.length === 0) return;
|
|
53
|
+
|
|
54
|
+
const newSections = sections.filter(
|
|
55
|
+
(section) => section.id !== activeSection
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
let newActiveSection = null;
|
|
59
|
+
|
|
60
|
+
if (newSections.length > 0) {
|
|
61
|
+
// If we're not deleting the last section
|
|
62
|
+
const currentIndex = sections.findIndex((s) => s.id === activeSection);
|
|
63
|
+
const nextIndex = Math.min(currentIndex, newSections.length - 1);
|
|
64
|
+
newActiveSection = newSections[nextIndex]?.id || newSections[0]?.id;
|
|
65
|
+
} else {
|
|
66
|
+
// If we're deleting the last section, create a new one
|
|
67
|
+
const createNewSection = { ...newSection() };
|
|
68
|
+
newSections.push(createNewSection);
|
|
69
|
+
newActiveSection = createNewSection.id;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setSections(newSections);
|
|
73
|
+
setActiveSection(newActiveSection);
|
|
74
|
+
setCode(newSections.find((s) => s.id === newActiveSection)?.code || "");
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const onBlurHandler = (value) => {
|
|
78
|
+
setSections((prevSections: SectionTypeProps[]) =>
|
|
79
|
+
prevSections.map(
|
|
80
|
+
(section: SectionTypeProps): SectionTypeProps =>
|
|
81
|
+
section.id === renameSection.id
|
|
82
|
+
? { ...section, name: value }
|
|
83
|
+
: section
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
setRenameSection({
|
|
88
|
+
id: "",
|
|
89
|
+
name: "",
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Wrap important className="w-[200px] border-r">
|
|
95
|
+
<ul className="h-[calc(100%-80px)] divide-y overflow-auto">
|
|
96
|
+
{addons}
|
|
97
|
+
<li>
|
|
98
|
+
<ul className="divide-y border-b">
|
|
99
|
+
{sections.map((section, index) => (
|
|
100
|
+
<li
|
|
101
|
+
key={`section__${index}__${section?.id}`}
|
|
102
|
+
className="relative m-0"
|
|
103
|
+
>
|
|
104
|
+
<button
|
|
105
|
+
className={classNames(
|
|
106
|
+
"text-gray-medium w-full truncate bg-opacity-50 px-3 py-2 text-left text-sm/6 font-semibold hover:bg-easy hover:text-wordpress",
|
|
107
|
+
{
|
|
108
|
+
"bg-easy": section.id === activeSection,
|
|
109
|
+
}
|
|
110
|
+
)}
|
|
111
|
+
onClick={() => {
|
|
112
|
+
setActiveSection(section.id);
|
|
113
|
+
Update(activeSection, section.id);
|
|
114
|
+
}}
|
|
115
|
+
onDoubleClick={() => {
|
|
116
|
+
setRenameSection({
|
|
117
|
+
id: section.id,
|
|
118
|
+
name: section.name,
|
|
119
|
+
});
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
{section.name}
|
|
123
|
+
</button>
|
|
124
|
+
|
|
125
|
+
{renameSection.id === section.id && (
|
|
126
|
+
<TextControl
|
|
127
|
+
className="absolute left-0 top-0 h-full w-full"
|
|
128
|
+
defaultValue={renameSection.name}
|
|
129
|
+
onChange={(value) =>
|
|
130
|
+
setRenameSection({
|
|
131
|
+
...renameSection,
|
|
132
|
+
name: value,
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
ref={textFieldRef}
|
|
136
|
+
onBlur={(value) => onBlurHandler(value)}
|
|
137
|
+
/>
|
|
138
|
+
)}
|
|
139
|
+
</li>
|
|
140
|
+
))}
|
|
141
|
+
</ul>
|
|
142
|
+
</li>
|
|
143
|
+
</ul>
|
|
144
|
+
<Wrap className="flex justify-center gap-2 p-2">
|
|
145
|
+
<Button
|
|
146
|
+
onClick={addSection}
|
|
147
|
+
icon={<PlusIcon />}
|
|
148
|
+
variant="tertiary"
|
|
149
|
+
size="compact"
|
|
150
|
+
>
|
|
151
|
+
Add
|
|
152
|
+
</Button>
|
|
153
|
+
<Button
|
|
154
|
+
onClick={removeSection}
|
|
155
|
+
icon={<TrashIcon />}
|
|
156
|
+
variant="tertiary"
|
|
157
|
+
size="compact"
|
|
158
|
+
>
|
|
159
|
+
Remove
|
|
160
|
+
</Button>
|
|
161
|
+
</Wrap>
|
|
162
|
+
</Wrap>
|
|
163
|
+
);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export default SectionList;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { SelectControl as WordpressSelect } from '@wordpress/components';
|
|
2
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
3
|
+
|
|
4
|
+
type SelectControlWrapperProps = {
|
|
5
|
+
className?: string;
|
|
6
|
+
defaultValue: string;
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
options: any[];
|
|
9
|
+
label?: string;
|
|
10
|
+
onChange?: (value: string) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const SelectControlWrapper = ({
|
|
14
|
+
onChange,
|
|
15
|
+
className,
|
|
16
|
+
defaultValue,
|
|
17
|
+
options,
|
|
18
|
+
label = '',
|
|
19
|
+
}: SelectControlWrapperProps) => {
|
|
20
|
+
const [value, setValue] = useState('');
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
setValue(defaultValue);
|
|
24
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
const handleChange = (val: string) => {
|
|
28
|
+
setValue(val);
|
|
29
|
+
if (onChange) {
|
|
30
|
+
onChange(val);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className={className}>
|
|
36
|
+
{options && options.length > 0 && (
|
|
37
|
+
<WordpressSelect
|
|
38
|
+
label={label || ''}
|
|
39
|
+
value={value || ''}
|
|
40
|
+
onChange={handleChange}
|
|
41
|
+
options={[{ value: '', label: 'Select an option' }, ...options]}
|
|
42
|
+
/>
|
|
43
|
+
)}
|
|
44
|
+
{!options && <p>No options available</p>}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createBlock } from '@wordpress/blocks';
|
|
2
|
+
import { Button } from '@wordpress/components';
|
|
3
|
+
|
|
4
|
+
const { select, dispatch } = wp.data;
|
|
5
|
+
|
|
6
|
+
interface SingleBlockTypeAppenderProps {
|
|
7
|
+
buttonText?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const SingleBlockTypeAppender = ({
|
|
11
|
+
buttonText = 'Add block',
|
|
12
|
+
}: SingleBlockTypeAppenderProps) => {
|
|
13
|
+
const addLastDuplicate = () => {
|
|
14
|
+
// Get the current block
|
|
15
|
+
const currentBlock = select('core/block-editor').getSelectedBlock();
|
|
16
|
+
const { clientId, innerBlocks } = currentBlock;
|
|
17
|
+
|
|
18
|
+
// get the last block
|
|
19
|
+
const lastBlock = innerBlocks[innerBlocks.length - 1];
|
|
20
|
+
// duplicate the last block
|
|
21
|
+
const newBlock = createBlock(
|
|
22
|
+
lastBlock.name,
|
|
23
|
+
lastBlock.attributes,
|
|
24
|
+
lastBlock.innerBlocks
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
dispatch('core/block-editor').insertBlocks(newBlock, 0, clientId);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Button variant="primary" onClick={() => addLastDuplicate()}>
|
|
32
|
+
{buttonText}
|
|
33
|
+
</Button>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default SingleBlockTypeAppender;
|
package/src/SlideIn.tsx
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Wrap } from "./Wrap";
|
|
2
|
+
import { useEffect, useState } from "@wordpress/element";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
|
|
5
|
+
type SlideInProps = {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
watch?: any;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const SlideIn = ({ children, watch, className }: SlideInProps) => {
|
|
12
|
+
const [slide, setSlide] = useState(0);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
setTimeout(() => {
|
|
16
|
+
setSlide(1);
|
|
17
|
+
}, 250);
|
|
18
|
+
}, [watch]);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Wrap
|
|
22
|
+
className={classNames(
|
|
23
|
+
className,
|
|
24
|
+
"duration-50 transform transition-all ease-in-out",
|
|
25
|
+
{
|
|
26
|
+
"translate-x-0": slide === 1,
|
|
27
|
+
"-translate-x-full": slide === 0,
|
|
28
|
+
}
|
|
29
|
+
)}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
</Wrap>
|
|
33
|
+
);
|
|
34
|
+
};
|
package/src/Spinner.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Spinner as WordpressSpinner } from '@wordpress/components';
|
|
2
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
|
|
5
|
+
type SpinnerProps = {
|
|
6
|
+
label?: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
defaultValue: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const Spinner = ({ className, defaultValue }: SpinnerProps) => {
|
|
12
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
setTimeout(() => {
|
|
16
|
+
setIsLoading(false);
|
|
17
|
+
}, 500);
|
|
18
|
+
if (defaultValue) {
|
|
19
|
+
setIsLoading(true);
|
|
20
|
+
}
|
|
21
|
+
}, [isLoading, defaultValue]);
|
|
22
|
+
|
|
23
|
+
return isLoading && <WordpressSpinner className={classNames(className)} />;
|
|
24
|
+
};
|
package/src/Tabs.tsx
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Wrap } from "./Wrap";
|
|
2
|
+
import { TabPanel } from "@wordpress/components";
|
|
3
|
+
import { createContext, useContext, useState } from "@wordpress/element";
|
|
4
|
+
import classNames from "classnames";
|
|
5
|
+
|
|
6
|
+
const TabsContext = createContext<{
|
|
7
|
+
activeTab: string | undefined;
|
|
8
|
+
setActiveTab: React.Dispatch<React.SetStateAction<string | undefined>>;
|
|
9
|
+
} | null>(null);
|
|
10
|
+
|
|
11
|
+
type TabsProps = {
|
|
12
|
+
className?: string;
|
|
13
|
+
value?: string;
|
|
14
|
+
defaultValue?: string;
|
|
15
|
+
onValueChange?: (value: string) => void;
|
|
16
|
+
children?: React.ReactNode;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const TabsWrapper = ({
|
|
20
|
+
children,
|
|
21
|
+
defaultValue,
|
|
22
|
+
value,
|
|
23
|
+
onValueChange,
|
|
24
|
+
...rest
|
|
25
|
+
}: TabsProps) => {
|
|
26
|
+
const isControlled = value !== undefined;
|
|
27
|
+
|
|
28
|
+
const [internalValue, setInternalValue] = useState(defaultValue || "");
|
|
29
|
+
|
|
30
|
+
const activeTab = isControlled ? value : internalValue;
|
|
31
|
+
|
|
32
|
+
const handleTabChange = (newValue: string) => {
|
|
33
|
+
if (!isControlled) {
|
|
34
|
+
setInternalValue(newValue);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (onValueChange) {
|
|
38
|
+
onValueChange(newValue);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const providerValue = {
|
|
43
|
+
activeTab,
|
|
44
|
+
setActiveTab: handleTabChange,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<TabsContext.Provider value={providerValue}>
|
|
49
|
+
<Wrap important className={rest.className}>
|
|
50
|
+
{children}
|
|
51
|
+
</Wrap>
|
|
52
|
+
</TabsContext.Provider>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type TabsListProps = {
|
|
57
|
+
options: { name: string; title: string; icon?: any }[];
|
|
58
|
+
children?: React.ReactNode;
|
|
59
|
+
onValueChange?: (value: string) => void;
|
|
60
|
+
className?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const TabsList = ({
|
|
64
|
+
options,
|
|
65
|
+
children,
|
|
66
|
+
className,
|
|
67
|
+
onValueChange,
|
|
68
|
+
}: TabsListProps) => {
|
|
69
|
+
const context = useContext(TabsContext);
|
|
70
|
+
const setActiveTab = onValueChange ? onValueChange : context?.setActiveTab;
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<TabPanel
|
|
74
|
+
className={classNames("tabs-list", className)}
|
|
75
|
+
tabs={options}
|
|
76
|
+
onSelect={(tabName) => {
|
|
77
|
+
setActiveTab(tabName);
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
{() => children}
|
|
81
|
+
</TabPanel>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
type TabsContentProps = {
|
|
86
|
+
value: string;
|
|
87
|
+
children: React.ReactNode;
|
|
88
|
+
className?: string;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const TabsContent = ({
|
|
92
|
+
value,
|
|
93
|
+
children,
|
|
94
|
+
className,
|
|
95
|
+
}: TabsContentProps) => {
|
|
96
|
+
const context = useContext(TabsContext);
|
|
97
|
+
const activeTab = context?.activeTab;
|
|
98
|
+
|
|
99
|
+
return activeTab === value ? (
|
|
100
|
+
<Wrap className={className}>{children}</Wrap>
|
|
101
|
+
) : null;
|
|
102
|
+
};
|
package/src/Tag.tsx
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
|
|
3
|
+
type TagProps = {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
className?: string;
|
|
6
|
+
color:
|
|
7
|
+
| 'blue'
|
|
8
|
+
| 'ruby'
|
|
9
|
+
| 'tomato'
|
|
10
|
+
| 'red'
|
|
11
|
+
| 'crimson'
|
|
12
|
+
| 'pink'
|
|
13
|
+
| 'plum'
|
|
14
|
+
| 'purple'
|
|
15
|
+
| 'violet'
|
|
16
|
+
| 'iris'
|
|
17
|
+
| 'indigo'
|
|
18
|
+
| 'cyan'
|
|
19
|
+
| 'teal'
|
|
20
|
+
| 'jade'
|
|
21
|
+
| 'green'
|
|
22
|
+
| 'grass'
|
|
23
|
+
| 'brown'
|
|
24
|
+
| 'orange'
|
|
25
|
+
| 'sky'
|
|
26
|
+
| 'gray';
|
|
27
|
+
asButton?: boolean;
|
|
28
|
+
onClick?: () => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const Tag = ({
|
|
32
|
+
children,
|
|
33
|
+
onClick,
|
|
34
|
+
color = 'blue',
|
|
35
|
+
className,
|
|
36
|
+
}: TagProps) => {
|
|
37
|
+
return (
|
|
38
|
+
<button onClick={onClick}>
|
|
39
|
+
<div color={color} className={classNames(className)}>
|
|
40
|
+
{children}
|
|
41
|
+
</div>
|
|
42
|
+
</button>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Wrap } from "./Wrap";
|
|
2
|
+
import { forwardRef } from "@wordpress/element";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
|
|
5
|
+
type TextControlProps = {
|
|
6
|
+
className?: string;
|
|
7
|
+
inputClassName?: string;
|
|
8
|
+
defaultValue: any;
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
onChange?: (value: string) => void;
|
|
11
|
+
onClick?: () => void;
|
|
12
|
+
readOnly?: boolean;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
onBlur?: (value) => void;
|
|
15
|
+
type?: string;
|
|
16
|
+
label?: string;
|
|
17
|
+
helper?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const TextControl = forwardRef<HTMLInputElement, TextControlProps>(
|
|
21
|
+
(
|
|
22
|
+
{
|
|
23
|
+
onClick,
|
|
24
|
+
onChange,
|
|
25
|
+
className,
|
|
26
|
+
defaultValue,
|
|
27
|
+
children,
|
|
28
|
+
inputClassName,
|
|
29
|
+
readOnly,
|
|
30
|
+
placeholder,
|
|
31
|
+
onBlur,
|
|
32
|
+
type = "text",
|
|
33
|
+
label,
|
|
34
|
+
helper = "",
|
|
35
|
+
},
|
|
36
|
+
ref
|
|
37
|
+
) => {
|
|
38
|
+
const fieldId = `text-control-${Math.random()
|
|
39
|
+
.toString(36)
|
|
40
|
+
.substring(2, 15)}`;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Wrap className={classNames("flex items-center p-0", className)}>
|
|
44
|
+
{label ? (
|
|
45
|
+
<label htmlFor={fieldId} className="text-primary !m-0 !mb-0 !mr-2">
|
|
46
|
+
{label}
|
|
47
|
+
</label>
|
|
48
|
+
) : null}
|
|
49
|
+
<input
|
|
50
|
+
id={fieldId}
|
|
51
|
+
className={classNames(
|
|
52
|
+
"border-primary !m-0 !mb-0 h-[32px] !rounded-none border border-opacity-70 focus:outline-none focus:ring-0",
|
|
53
|
+
inputClassName
|
|
54
|
+
)}
|
|
55
|
+
type={type}
|
|
56
|
+
value={defaultValue}
|
|
57
|
+
placeholder={placeholder}
|
|
58
|
+
onFocus={() => onClick && onClick()}
|
|
59
|
+
onBlur={(e) => {
|
|
60
|
+
if (onClick) onClick();
|
|
61
|
+
if (onBlur) onBlur(e.target.value);
|
|
62
|
+
}}
|
|
63
|
+
onChange={(e) => onChange && onChange(e.target.value)}
|
|
64
|
+
readOnly={readOnly}
|
|
65
|
+
ref={ref}
|
|
66
|
+
/>
|
|
67
|
+
<span>{children}</span>
|
|
68
|
+
{helper && <p className="text-primary !m-0 !mb-0 text-xs">{helper}</p>}
|
|
69
|
+
</Wrap>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
TextControl.displayName = "TextControl"; // Recommended for debugging
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import Pencil1Icon from "@blockbite/icons/dist/Pencil1";
|
|
2
|
+
import { Wrap } from "./Wrap";
|
|
3
|
+
import { TextControl } from "@wordpress/components";
|
|
4
|
+
|
|
5
|
+
type TextControlLabelProps = {
|
|
6
|
+
className?: string;
|
|
7
|
+
defaultValue: any;
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
onChange?: (value: string) => void;
|
|
10
|
+
onClick?: () => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const TextControlLabel = ({
|
|
14
|
+
onChange,
|
|
15
|
+
defaultValue,
|
|
16
|
+
children,
|
|
17
|
+
}: TextControlLabelProps) => {
|
|
18
|
+
return (
|
|
19
|
+
<Wrap className="blockbite-ui__text-control-label flex items-center">
|
|
20
|
+
<span className="outline-b-2 relative block -outline-offset-2 outline-black">
|
|
21
|
+
<TextControl type="text" value={defaultValue} onChange={onChange} />
|
|
22
|
+
<Pencil1Icon className="absolute right-1 top-2" />
|
|
23
|
+
</span>{" "}
|
|
24
|
+
{children}
|
|
25
|
+
</Wrap>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Wrap } from "./Wrap";
|
|
2
|
+
import { memo, useEffect, useState } from "@wordpress/element";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
__experimentalToggleGroupControlOptionIcon as ToggleGroupControlIcon,
|
|
6
|
+
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
|
|
7
|
+
__experimentalToggleGroupControl as WordpressToggleGroupControl,
|
|
8
|
+
} from "@wordpress/components";
|
|
9
|
+
import classNames from "classnames";
|
|
10
|
+
|
|
11
|
+
type ToggleProps = {
|
|
12
|
+
className?: string;
|
|
13
|
+
options: { value: string; label: string; icon?: React.ReactElement }[];
|
|
14
|
+
defaultPressed: string;
|
|
15
|
+
label?: string | boolean;
|
|
16
|
+
variant?: "primary" | "secondary";
|
|
17
|
+
display?: "icon" | "label";
|
|
18
|
+
size?: "small" | "default" | "compact";
|
|
19
|
+
onPressedChange: (value: string) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const ToggleGroup: React.FC<ToggleProps> = memo(
|
|
23
|
+
({
|
|
24
|
+
className,
|
|
25
|
+
options,
|
|
26
|
+
defaultPressed,
|
|
27
|
+
display = "label",
|
|
28
|
+
label = false,
|
|
29
|
+
onPressedChange,
|
|
30
|
+
}) => {
|
|
31
|
+
const [isPressed, setIsPressed] = useState<string>("");
|
|
32
|
+
|
|
33
|
+
const handlePressChange = (value: string) => {
|
|
34
|
+
onPressedChange(value);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
setIsPressed(defaultPressed);
|
|
39
|
+
}, [defaultPressed]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Wrap className={classNames(className)}>
|
|
43
|
+
<WordpressToggleGroupControl
|
|
44
|
+
isBlock
|
|
45
|
+
value={isPressed}
|
|
46
|
+
label={typeof label === "string" ? label : ""}
|
|
47
|
+
>
|
|
48
|
+
{options.map((option) =>
|
|
49
|
+
display === "icon" ? (
|
|
50
|
+
<ToggleGroupControlIcon
|
|
51
|
+
icon={option.icon}
|
|
52
|
+
label={option.label}
|
|
53
|
+
key={`ToggleGroupControlIcon-${option.value}`}
|
|
54
|
+
value={option.value}
|
|
55
|
+
onClick={() => handlePressChange(option.value)}
|
|
56
|
+
/>
|
|
57
|
+
) : (
|
|
58
|
+
<ToggleGroupControlOption
|
|
59
|
+
key={`ToggleGroupControlOption-${option.value}`}
|
|
60
|
+
value={option.value}
|
|
61
|
+
label={option.label}
|
|
62
|
+
onClick={() => handlePressChange(option.value)}
|
|
63
|
+
/>
|
|
64
|
+
)
|
|
65
|
+
)}
|
|
66
|
+
</WordpressToggleGroupControl>
|
|
67
|
+
</Wrap>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
export { ToggleGroup };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Wrap } from "./Wrap";
|
|
2
|
+
import { ToggleControl as Switch } from "@wordpress/components";
|
|
3
|
+
import { useEffect, useState } from "@wordpress/element";
|
|
4
|
+
import classNames from "classnames";
|
|
5
|
+
|
|
6
|
+
type ToggleSwitchProps = {
|
|
7
|
+
label?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
onChange?: (checked: boolean) => void;
|
|
10
|
+
checked?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const ToggleSwitch = ({
|
|
14
|
+
label,
|
|
15
|
+
className,
|
|
16
|
+
onChange,
|
|
17
|
+
checked = false, // Default to false if undefined, so it's always controlled
|
|
18
|
+
}: ToggleSwitchProps) => {
|
|
19
|
+
const [isChecked, setIsChecked] = useState(checked);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setIsChecked(checked);
|
|
23
|
+
}, [checked]);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Wrap className={classNames(className, "flex items-center gap-2")}>
|
|
27
|
+
<Switch
|
|
28
|
+
checked={isChecked}
|
|
29
|
+
label={label}
|
|
30
|
+
onChange={(e) => {
|
|
31
|
+
setIsChecked(e);
|
|
32
|
+
onChange && onChange(e);
|
|
33
|
+
}}
|
|
34
|
+
></Switch>
|
|
35
|
+
</Wrap>
|
|
36
|
+
);
|
|
37
|
+
};
|
package/src/Wrap.tsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
type WrapProps = {
|
|
2
|
+
children: React.ReactNode;
|
|
3
|
+
className?: string;
|
|
4
|
+
important?: boolean;
|
|
5
|
+
onClick?: (e: React.MouseEvent) => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const Wrap = ({
|
|
9
|
+
children,
|
|
10
|
+
className,
|
|
11
|
+
important = false,
|
|
12
|
+
onClick,
|
|
13
|
+
}: WrapProps) => {
|
|
14
|
+
if (important) {
|
|
15
|
+
return (
|
|
16
|
+
<div className="bb_" onClick={onClick}>
|
|
17
|
+
<div className={className}>{children}</div>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return <div className={className}>{children}</div>;
|
|
23
|
+
};
|