first commit.
This commit is contained in:
28
src/common/DynamicPortal.tsx
Normal file
28
src/common/DynamicPortal.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
import ReactDOM from 'react-dom';
|
||||
import invariant from 'tiny-invariant';
|
||||
|
||||
interface DynamicPortalProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const DynamicPortal: React.FC<DynamicPortalProps> = (props) => {
|
||||
const { children } = props;
|
||||
const [el] = React.useState(() => document.createElement('div'));
|
||||
React.useEffect(() => {
|
||||
const body = document.querySelector('body');
|
||||
invariant(body, 'The body element is not available. Please ensure this code runs in a browser context.');
|
||||
if (el.parentNode == null) {
|
||||
body.appendChild(el);
|
||||
}
|
||||
return () => {
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
return ReactDOM.createPortal(children, el);
|
||||
};
|
||||
export default DynamicPortal;
|
18
src/common/ErrorFallback.tsx
Normal file
18
src/common/ErrorFallback.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
import KintonePluginAlert from './ui/KintonePluginAlert';
|
||||
|
||||
interface Props {
|
||||
error: Error;
|
||||
}
|
||||
|
||||
const ErrorFallback: React.FC<Props> = (props) => {
|
||||
const { error } = props;
|
||||
return (
|
||||
<KintonePluginAlert>
|
||||
<p>Something went wrong:</p>
|
||||
<pre>{error.message}</pre>
|
||||
</KintonePluginAlert>
|
||||
);
|
||||
};
|
||||
export default ErrorFallback;
|
117
src/common/Loading.module.css
Normal file
117
src/common/Loading.module.css
Normal file
@@ -0,0 +1,117 @@
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
z-index: 9999;
|
||||
}
|
||||
.spinner-container {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10000;
|
||||
}
|
||||
.spinner-container .spinner {
|
||||
font-size: 10px;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
text-indent: -9999em;
|
||||
animation: mulShdSpin 1.1s infinite ease;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
@keyframes mulShdSpin {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em #ffffff,
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
12.5% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(255, 255, 255, 0.7),
|
||||
1.8em -1.8em 0 0em #ffffff,
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
25% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(255, 255, 255, 0.5),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.7),
|
||||
2.5em 0em 0 0em #ffffff,
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
37.5% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.5),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.7),
|
||||
1.75em 1.75em 0 0em #ffffff,
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
50% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.5),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.7),
|
||||
0em 2.5em 0 0em #ffffff,
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
62.5% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.5),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.7),
|
||||
-1.8em 1.8em 0 0em #ffffff,
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
75% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.7),
|
||||
-2.6em 0em 0 0em #ffffff,
|
||||
-1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
87.5% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(255, 255, 255, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(255, 255, 255, 0.2),
|
||||
2.5em 0em 0 0em rgba(255, 255, 255, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(255, 255, 255, 0.2),
|
||||
0em 2.5em 0 0em rgba(255, 255, 255, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(255, 255, 255, 0.5),
|
||||
-2.6em 0em 0 0em rgba(255, 255, 255, 0.7),
|
||||
-1.8em -1.8em 0 0em #ffffff;
|
||||
}
|
||||
}
|
18
src/common/Loading.tsx
Normal file
18
src/common/Loading.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
import DynamicPortal from './DynamicPortal';
|
||||
|
||||
import styles from './Loading.module.css';
|
||||
|
||||
const Loading: React.FC = () => {
|
||||
return (
|
||||
<DynamicPortal>
|
||||
<div className={styles.overlay}>
|
||||
<div className={styles.spinnerContainer}>
|
||||
<div className={styles.spinner}>Loading..</div>
|
||||
</div>
|
||||
</div>
|
||||
</DynamicPortal>
|
||||
);
|
||||
};
|
||||
export default Loading;
|
44
src/common/config.ts
Normal file
44
src/common/config.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { PLUGIN_ID } from './global';
|
||||
|
||||
export interface PluginConfigLookupItem {
|
||||
lookupFieldCode: string;
|
||||
srcAttachmentFieldCode: string;
|
||||
destAttachmentFieldCode: string;
|
||||
}
|
||||
|
||||
export type PluginConfigLookup = PluginConfigLookupItem[];
|
||||
|
||||
export interface PluginConfig {
|
||||
lookup: PluginConfigLookup;
|
||||
}
|
||||
|
||||
const encodeConfigLookup = (rows: PluginConfigLookup): string => {
|
||||
return JSON.stringify(rows);
|
||||
};
|
||||
|
||||
const decodeConfigLookup = (lookup: string): PluginConfigLookup | null => {
|
||||
try {
|
||||
return JSON.parse(lookup) as PluginConfigLookup;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const loadPluginConfigLookup = (): PluginConfigLookup | null => {
|
||||
const config = kintone.plugin.app.getConfig(PLUGIN_ID);
|
||||
return decodeConfigLookup(config.lookup);
|
||||
};
|
||||
|
||||
export const loadPluginConfig = (): PluginConfig | null => {
|
||||
const config = kintone.plugin.app.getConfig(PLUGIN_ID);
|
||||
const lookup = decodeConfigLookup(config.lookup);
|
||||
return lookup ? { lookup } : null;
|
||||
};
|
||||
|
||||
export const savePluginConfigLookup = (lookup: PluginConfigLookup, callback: () => void) => {
|
||||
kintone.plugin.app.setConfig({ lookup: encodeConfigLookup(lookup) }, callback);
|
||||
};
|
||||
|
||||
export const savePluginConfig = (config: PluginConfig, callback: () => void) => {
|
||||
kintone.plugin.app.setConfig({ lookup: encodeConfigLookup(config.lookup) }, callback);
|
||||
};
|
77
src/common/context.ts
Normal file
77
src/common/context.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { KintoneFormFieldProperty, KintoneRestAPIClient } from '@kintone/rest-api-client';
|
||||
import {
|
||||
AppID as KintoneAppId,
|
||||
Properties as KintoneFormFieldProperties,
|
||||
} from '@kintone/rest-api-client/lib/src/client/types';
|
||||
import { PluginConfigLookup } from './config';
|
||||
|
||||
export interface PluginContext {
|
||||
appId: KintoneAppId;
|
||||
formFieldsProperties: Record<KintoneAppId, KintoneFormFieldProperties>;
|
||||
attachmentFields: Record<KintoneAppId, KintoneFormFieldProperty.File[]>;
|
||||
lookupFields: KintoneFormFieldProperty.Lookup[];
|
||||
}
|
||||
|
||||
const filterAttachmentFields = (properties: KintoneFormFieldProperties): KintoneFormFieldProperty.File[] => {
|
||||
return Object.values(properties).filter((property) => property.type === 'FILE');
|
||||
};
|
||||
|
||||
const filterLookupFields = (properties: KintoneFormFieldProperties): KintoneFormFieldProperty.Lookup[] => {
|
||||
return Object.values(properties).filter((property) => 'lookup' in property);
|
||||
};
|
||||
|
||||
const getFormFieldsProperties = async (appId: KintoneAppId): Promise<KintoneFormFieldProperties> => {
|
||||
const client = new KintoneRestAPIClient();
|
||||
const { properties } = await client.app.getFormFields({ app: appId, lang: 'en', preview: false });
|
||||
return properties;
|
||||
};
|
||||
|
||||
export const getPluginContext = async (appId: KintoneAppId): Promise<PluginContext> => {
|
||||
const context: PluginContext = {
|
||||
appId: appId,
|
||||
formFieldsProperties: {},
|
||||
attachmentFields: {},
|
||||
lookupFields: [],
|
||||
};
|
||||
const properties = await getFormFieldsProperties(appId);
|
||||
context.formFieldsProperties[appId] = properties;
|
||||
context.attachmentFields[appId] = filterAttachmentFields(properties);
|
||||
const lookupFields = filterLookupFields(properties);
|
||||
const relatedAppIds = lookupFields
|
||||
.map(({ lookup }) => lookup.relatedApp.app)
|
||||
.filter((value, index, self) => self.indexOf(value) === index);
|
||||
await Promise.all(
|
||||
relatedAppIds.map(async (relatedAppId) => {
|
||||
const relatedProperties = await getFormFieldsProperties(relatedAppId);
|
||||
context.formFieldsProperties[relatedAppId] = relatedProperties;
|
||||
context.attachmentFields[relatedAppId] = filterAttachmentFields(relatedProperties);
|
||||
}),
|
||||
);
|
||||
context.lookupFields = lookupFields.filter((field) => {
|
||||
const relatedKeyField = context.formFieldsProperties[field.lookup.relatedApp.app][field.lookup.relatedKeyField];
|
||||
return (
|
||||
relatedKeyField.type === 'RECORD_NUMBER' ||
|
||||
((relatedKeyField.type === 'SINGLE_LINE_TEXT' || relatedKeyField.type === 'NUMBER') &&
|
||||
'unique' in relatedKeyField &&
|
||||
relatedKeyField.unique)
|
||||
);
|
||||
});
|
||||
return context;
|
||||
};
|
||||
|
||||
export const filterConfigByPluginContext = (
|
||||
config: PluginConfigLookup | null,
|
||||
context: PluginContext,
|
||||
): PluginConfigLookup | null => {
|
||||
const filtered = config?.filter((item) => {
|
||||
const lookupField = context.lookupFields.find((f) => f.code === item.lookupFieldCode);
|
||||
return (
|
||||
lookupField != null &&
|
||||
context.attachmentFields[lookupField.lookup.relatedApp.app]?.find(
|
||||
(f) => f.code === item.srcAttachmentFieldCode,
|
||||
) != null &&
|
||||
context.attachmentFields[context.appId]?.find((f) => f.code === item.destAttachmentFieldCode) != null
|
||||
);
|
||||
});
|
||||
return filtered != null && filtered.length > 0 ? filtered : null;
|
||||
};
|
12
src/common/global.ts
Normal file
12
src/common/global.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import invariant from 'tiny-invariant';
|
||||
|
||||
export const PLUGIN_ID = kintone.$PLUGIN_ID;
|
||||
invariant(PLUGIN_ID, 'The PLUGIN_ID is not available. Please ensure you are on a Kintone plugin page.');
|
||||
|
||||
const KintoneUserLanguages = ['en', 'ja', 'zh', 'zh-TW', 'es', 'pt-BR', 'th'] as const;
|
||||
export type KintoneUserLanguages = (typeof KintoneUserLanguages)[number];
|
||||
export const LANGUAGE = kintone.getLoginUser().language as KintoneUserLanguages;
|
||||
invariant(
|
||||
KintoneUserLanguages.includes(LANGUAGE),
|
||||
`Unsupported language: ${LANGUAGE}. Supported languages are: ${KintoneUserLanguages.join(', ')}`,
|
||||
);
|
669
src/common/ui/51-modern-default.css
Normal file
669
src/common/ui/51-modern-default.css
Normal file
File diff suppressed because one or more lines are too long
17
src/common/ui/KintonePluginAlert.tsx
Normal file
17
src/common/ui/KintonePluginAlert.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type KintonePluginAlertProps = React.PropsWithChildren<{
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
const KintonePluginAlert: React.FC<KintonePluginAlertProps> = (props) => {
|
||||
const { className, children } = props;
|
||||
return (
|
||||
<div className={clsx('kintoneplugin-alert', className)} role="alert">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default KintonePluginAlert;
|
25
src/common/ui/KintonePluginButton.tsx
Normal file
25
src/common/ui/KintonePluginButton.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type KintonePluginButtonProps = React.PropsWithChildren<{
|
||||
className?: string;
|
||||
variant: 'normal' | 'disabled' | 'dialog-ok' | 'dialog-cancel' | 'add-row-image' | 'remove-row-image';
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
}>;
|
||||
|
||||
const KintonePluginButton: React.FC<KintonePluginButtonProps> = (props) => {
|
||||
const { className, variant, type = 'button', onClick, children } = props;
|
||||
return (
|
||||
<button
|
||||
className={clsx(`kintoneplugin-button-${variant}`, className)}
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
disabled={variant === 'disabled'}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
export default KintonePluginButton;
|
13
src/common/ui/KintonePluginDesc.tsx
Normal file
13
src/common/ui/KintonePluginDesc.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type KintonePluginDescProps = React.PropsWithChildren<{
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
const KintonePluginDesc: React.FC<KintonePluginDescProps> = (props) => {
|
||||
const { className, children } = props;
|
||||
return <div className={clsx('kintoneplugin-desc', className)}>{children}</div>;
|
||||
};
|
||||
export default KintonePluginDesc;
|
36
src/common/ui/KintonePluginInputCheckbox.tsx
Normal file
36
src/common/ui/KintonePluginInputCheckbox.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export type KintonePluginInputCheckboxProps = {
|
||||
className?: string;
|
||||
label: string;
|
||||
checked: boolean;
|
||||
disabled?: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
};
|
||||
|
||||
const KintonePluginInputCheckbox: React.FC<KintonePluginInputCheckboxProps> = (props) => {
|
||||
const { className, label, checked, disabled, onChange } = props;
|
||||
const id = nanoid();
|
||||
const handleOnChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
onChange(e.target.checked);
|
||||
};
|
||||
return (
|
||||
<div className={clsx('kintoneplugin-input-checkbox', className)}>
|
||||
<span className="kintoneplugin-input-checkbox-item">
|
||||
<input
|
||||
id={id}
|
||||
className="kintoneplugin-input-text"
|
||||
type="text"
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
<label htmlFor={id}>{label}</label>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default KintonePluginInputCheckbox;
|
47
src/common/ui/KintonePluginInputRadio.tsx
Normal file
47
src/common/ui/KintonePluginInputRadio.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export type KintonePluginInputRadioItem = {
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type KintonePluginInputRadioProps = {
|
||||
className?: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
items: KintonePluginInputRadioItem[];
|
||||
};
|
||||
|
||||
const KintonePluginInputRadio: React.FC<KintonePluginInputRadioProps> = (props) => {
|
||||
const { className, value, onChange, items } = props;
|
||||
if (!items || items.length === 0) {
|
||||
return null; // Return null if no items are provided
|
||||
}
|
||||
const id = nanoid();
|
||||
const handleOnChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
onChange(e.target.value);
|
||||
};
|
||||
return (
|
||||
<div className={clsx('kintoneplugin-input-radio', className)}>
|
||||
{items.map(({ value: itemValue, label, disabled }, idx) => (
|
||||
<span key={`${id}-k${idx}`} className="kintoneplugin-input-radio-item">
|
||||
<input
|
||||
id={`${id}-${idx}`}
|
||||
type="radio"
|
||||
name={id}
|
||||
value={itemValue}
|
||||
checked={value === itemValue}
|
||||
disabled={disabled}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
<label htmlFor={`${id}-${idx}`}>{label}</label>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default KintonePluginInputRadio;
|
29
src/common/ui/KintonePluginInputText.tsx
Normal file
29
src/common/ui/KintonePluginInputText.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type KintonePluginInputTextProps = {
|
||||
className?: string;
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
const KintonePluginInputText: React.FC<KintonePluginInputTextProps> = (props) => {
|
||||
const { className, value, disabled, onChange } = props;
|
||||
const handleOnChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
onChange(e.target.value);
|
||||
};
|
||||
return (
|
||||
<div className={clsx('kintoneplugin-input-outer', className)}>
|
||||
<input
|
||||
className="kintoneplugin-input-text"
|
||||
type="text"
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={handleOnChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default KintonePluginInputText;
|
13
src/common/ui/KintonePluginLabel.tsx
Normal file
13
src/common/ui/KintonePluginLabel.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type KintonePluginLabelProps = React.PropsWithChildren<{
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
const KintonePluginLabel: React.FC<KintonePluginLabelProps> = (props) => {
|
||||
const { className, children } = props;
|
||||
return <div className={clsx('kintoneplugin-label', className)}>{children}</div>;
|
||||
};
|
||||
export default KintonePluginLabel;
|
13
src/common/ui/KintonePluginRequire.tsx
Normal file
13
src/common/ui/KintonePluginRequire.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type KintonePluginRequire = React.PropsWithChildren<{
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
const KintonePluginRequire: React.FC<KintonePluginRequire> = (props) => {
|
||||
const { className, children } = props;
|
||||
return <span className={clsx('kintoneplugin-require', className)}>{children}</span>;
|
||||
};
|
||||
export default KintonePluginRequire;
|
13
src/common/ui/KintonePluginRow.tsx
Normal file
13
src/common/ui/KintonePluginRow.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type KintonePluginRowProps = React.PropsWithChildren<{
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
const KintonePluginRow: React.FC<KintonePluginRowProps> = (props) => {
|
||||
const { className, children } = props;
|
||||
return <div className={clsx('kintoneplugin-row', className)}>{children}</div>;
|
||||
};
|
||||
export default KintonePluginRow;
|
42
src/common/ui/KintonePluginSelect.tsx
Normal file
42
src/common/ui/KintonePluginSelect.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export type KintonePluginSelectOptionData = {
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type KintonePluginSelectProps = {
|
||||
className?: string;
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
options: KintonePluginSelectOptionData[];
|
||||
};
|
||||
|
||||
const KintonePluginSelect: React.FC<KintonePluginSelectProps> = (props) => {
|
||||
const { className, value, onChange, options } = props;
|
||||
if (!options || options.length === 0) {
|
||||
return null; // Return null if no options are provided
|
||||
}
|
||||
const key = nanoid();
|
||||
const handleOnChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
onChange(e.target.value);
|
||||
};
|
||||
return (
|
||||
<div className={clsx('kintoneplugin-select-outer', className)}>
|
||||
<div className="kintoneplugin-select">
|
||||
<select value={value} onChange={handleOnChange}>
|
||||
{options.map(({ value: itemValue, label, disabled }, idx) => (
|
||||
<option key={`${key}-k${idx}`} value={itemValue} disabled={disabled}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default KintonePluginSelect;
|
13
src/common/ui/KintonePluginTable.tsx
Normal file
13
src/common/ui/KintonePluginTable.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type KintonePluginTableProps = React.PropsWithChildren<{
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
const KintonePluginTable: React.FC<KintonePluginTableProps> = (props) => {
|
||||
const { className, children } = props;
|
||||
return <table className={clsx('kintoneplugin-table', className)}>{children}</table>;
|
||||
};
|
||||
export default KintonePluginTable;
|
23
src/common/ui/KintonePluginTableTd.tsx
Normal file
23
src/common/ui/KintonePluginTableTd.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type KintonePluginTableTdProps = React.PropsWithChildren<{
|
||||
className?: string;
|
||||
variant: 'control' | 'operation';
|
||||
}>;
|
||||
|
||||
const KintonePluginTableTd: React.FC<KintonePluginTableTdProps> = (props) => {
|
||||
const { className, variant, children } = props;
|
||||
if (variant === 'control') {
|
||||
return (
|
||||
<td className={className}>
|
||||
<div className="kintoneplugin-table-td-control">
|
||||
<div className="kintoneplugin-table-td-control-value">{children}</div>
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
return <td className={clsx('kintoneplugin-table-td-operation', className)}>{children}</td>;
|
||||
};
|
||||
export default KintonePluginTableTd;
|
21
src/common/ui/KintonePluginTableTh.tsx
Normal file
21
src/common/ui/KintonePluginTableTh.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type KintonePluginTableThProps = React.PropsWithChildren<{
|
||||
className?: string;
|
||||
variant: 'title' | 'blankspace';
|
||||
}>;
|
||||
|
||||
const KintonePluginTableTh: React.FC<KintonePluginTableThProps> = (props) => {
|
||||
const { className, variant, children } = props;
|
||||
if (variant === 'title') {
|
||||
return (
|
||||
<th className={clsx('kintoneplugin-table-th', className)}>
|
||||
<span className="title">{children}</span>
|
||||
</th>
|
||||
);
|
||||
}
|
||||
return <th className={clsx('kintoneplugin-table-th-blankspace', className)}>{children}</th>;
|
||||
};
|
||||
export default KintonePluginTableTh;
|
13
src/common/ui/KintonePluginTitle.tsx
Normal file
13
src/common/ui/KintonePluginTitle.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type KintonePluginTitleProps = React.PropsWithChildren<{
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
const KintonePluginTitle: React.FC<KintonePluginTitleProps> = (props) => {
|
||||
const { className, children } = props;
|
||||
return <div className={clsx('kintoneplugin-title', className)}>{children}</div>;
|
||||
};
|
||||
export default KintonePluginTitle;
|
Reference in New Issue
Block a user