Add input elements to ContentKit (#2882)

This commit is contained in:
Nolann B.
2025-02-28 18:50:20 +01:00
committed by GitHub
parent 53f5dbea26
commit 8beb5d6bcf
8 changed files with 139 additions and 23 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@gitbook/react-contentkit': patch
'gitbook': patch
---
Add input elements to ContentKit
+7 -6
View File
@@ -38,7 +38,7 @@
},
"packages/gitbook": {
"name": "gitbook",
"version": "0.6.3",
"version": "0.6.4",
"dependencies": {
"@gitbook/api": "0.96.1",
"@gitbook/cache-do": "workspace:*",
@@ -118,7 +118,7 @@
},
"packages/gitbook-v2": {
"name": "gitbook-v2",
"version": "0.1.0",
"version": "0.1.1",
"dependencies": {
"@gitbook/api": "0.96.1",
"@sindresorhus/fnv1a": "^3.1.0",
@@ -138,7 +138,7 @@
"name": "@gitbook/icons",
"version": "0.1.0",
"bin": {
"gitbook-icons": "./bin/gitbook-icons.js",
"gitbook-icons": "./bin/gitbook-icons.js"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.6.0",
@@ -156,7 +156,7 @@
},
"packages/openapi-parser": {
"name": "@gitbook/openapi-parser",
"version": "2.0.0",
"version": "2.0.1",
"dependencies": {
"@scalar/openapi-parser": "^0.10.4",
"@scalar/openapi-types": "^0.1.6",
@@ -181,6 +181,7 @@
"version": "0.6.0",
"dependencies": {
"@gitbook/api": "0.96.1",
"@gitbook/icons": "workspace:*",
"assert-never": "^1.2.1",
"classnames": "^2.5.1",
},
@@ -195,7 +196,7 @@
"name": "@gitbook/react-math",
"version": "0.6.0",
"bin": {
"gitbook-math": "./bin/gitbook-math.js",
"gitbook-math": "./bin/gitbook-math.js"
},
"dependencies": {
"object-hash": "^3.0.0",
@@ -210,7 +211,7 @@
},
"packages/react-openapi": {
"name": "@gitbook/react-openapi",
"version": "1.0.3",
"version": "1.0.4",
"dependencies": {
"@gitbook/openapi-parser": "workspace:*",
"@scalar/api-client-react": "1.0.87",
@@ -3,8 +3,13 @@
@apply flex gap-4;
}
/** Add margin bottom to all stacks that are not the last child and have a form */
.contentkit-stack:not(:last-child):has(.contentkit-form) {
@apply mb-4;
}
.contentkit-hstack {
@apply flex-row gap-4 items-center;
@apply flex-row gap-3 items-center;
}
.contentkit-hstack.contentkit-stack-align-center {
@apply justify-center;
@@ -88,17 +93,23 @@
/** Buttons */
.contentkit-button {
@apply flex gap-2 items-center justify-center px-2 py-1 rounded
border border-tint hover:border-tint-hover
text-tint hover:text-tint-strong;
@apply text-sm px-3 h-8 text-center inline-block py-1.5 rounded-md straight-corners:rounded-none place-self-start ring-1 ring-tint hover:ring-tint-hover shadow-sm shadow-tint dark:shadow-tint-1 hover:shadow-md active:shadow-none contrast-more:ring-tint-12 contrast-more:hover:ring-2 contrast-more:hover:ring-tint-12 hover:scale-105 active:scale-100 transition-all grow-0 shrink-0 truncate w-full;
}
.contentkit-button-style-primary {
@apply bg-primary-solid text-contrast-primary-solid hover:bg-primary-solid-hover hover:text-contrast-primary-solid-hover ring-0 contrast-more:ring-1;
}
.contentkit-button-style-secondary {
@apply bg-tint text-tint hover:bg-tint-hover hover:text-primary contrast-more:bg-tint-subtle;
}
.contentkit-button .contentkit-icon {
@apply w-4 h-4;
}
.contentkit-button .contentkit-button-label {
@apply text-sm font-medium text-tint-strong;
.contentkit-button-loading {
@apply size-5 flex mx-auto animate-spin;
}
/** Modals */
@@ -143,7 +154,21 @@
/** Text input */
.contentkit-textinput {
@apply w-full px-2 py-1 rounded border border-tint text-sm text-tint;
@apply w-full rounded border border-tint text-tint-strong placeholder:text-tint flex resize-none flex-1 px-2 py-1.5 text-sm bg-transparent whitespace-pre-line;
@apply focus:outline-primary focus:border-primary;
}
/** Form */
.contentkit-form {
@apply flex flex-col gap-1;
}
.contentkit-form:not(:first-of-type) {
@apply mt-4;
}
.contentkit-label {
@apply text-sm font-medium text-tint-strong;
}
/** Dividers */
+2 -1
View File
@@ -11,7 +11,8 @@
"dependencies": {
"classnames": "^2.5.1",
"@gitbook/api": "0.96.1",
"assert-never": "^1.2.1"
"assert-never": "^1.2.1",
"@gitbook/icons": "workspace:*"
},
"peerDependencies": {
"react": "*"
@@ -13,6 +13,7 @@ import { ElementCodeBlock } from './ElementCodeBlock';
import { ElementDivider } from './ElementDivider';
import { ElementIcon } from './ElementIcon';
import { ElementImage } from './ElementImage';
import { ElementInput } from './ElementInput';
import { ElementMarkdown } from './ElementMarkdown';
import { ElementModal } from './ElementModal';
import { ElementStack } from './ElementStack';
@@ -152,6 +153,8 @@ export function Element(props: {
return <ElementCodeBlock element={element} context={context} state={state} />;
case 'webframe':
return <ElementWebframe element={element} />;
case 'input':
return <ElementInput element={element} context={context} state={state} />;
default:
return (
<pre
@@ -1,6 +1,7 @@
'use client';
import type { ContentKitButton } from '@gitbook/api';
import { Icon } from '@gitbook/icons';
import React from 'react';
import classNames from 'classnames';
@@ -19,16 +20,14 @@ export function ElementButton(
const [loading, setLoading] = React.useState(false);
// TODO:
// - loading
// - confirm
// - confirm dialog
return (
<button
title={element.tooltip}
className={classNames(
'contentkit-button',
`contentkit-button-style-${element.style ?? 'secondary'}`,
loading ? 'contentkit-button-loading' : null
`contentkit-button-style-${element.style ?? 'secondary'}`
)}
onClick={(event) => {
if (element.disabled || loading) {
@@ -43,11 +42,17 @@ export function ElementButton(
});
}}
>
{icon}
{element.label ? (
<span className="contentkit-button-label">{element.label}</span>
) : null}
{trailingIcon}
{loading ? (
<Icon icon="spinner" className="contentkit-button-loading" />
) : (
<>
{icon}
{element.label ? (
<span className="contentkit-button-label">{element.label}</span>
) : null}
{trailingIcon}
</>
)}
</button>
);
}
@@ -0,0 +1,74 @@
import type {
ContentKitButton,
ContentKitCheckbox,
ContentKitCodeBlock,
ContentKitInput,
ContentKitRadio,
ContentKitSelect,
ContentKitSwitch,
ContentKitTextInput,
} from '@gitbook/api';
import classNames from 'classnames';
import { ElementButton } from './ElementButton';
import { ElementCodeBlock } from './ElementCodeBlock';
import { ElementIcon } from './ElementIcon';
import { ElementTextInput } from './ElementTextInput';
import type { ContentKitServerContext } from './types';
export function ElementInput(props: {
element: ContentKitInput;
context: ContentKitServerContext;
state: object;
}) {
const { element, context, state } = props;
const { label, element: childElement } = element;
return (
<div className={classNames('contentkit-form')}>
{label ? <label className="contentkit-label">{label}</label> : null}
{getInputElement({ element: childElement, context, state })}
</div>
);
}
function getInputElement(props: {
element:
| ContentKitTextInput
| ContentKitSelect
| ContentKitSwitch
| ContentKitRadio
| ContentKitCheckbox
| ContentKitButton
| ContentKitCodeBlock;
context: ContentKitServerContext;
state: object;
}) {
const { element, context, state } = props;
//!!TODO add support for:
// - select
// - switch
// - radio
// - checkbox
switch (element.type) {
case 'textinput':
return <ElementTextInput element={element} />;
case 'button':
return (
<ElementButton
element={element}
icon={
element.icon ? <ElementIcon icon={element.icon} context={context} /> : null
}
trailingIcon={
element.trailingIcon ? (
<ElementIcon icon={element.trailingIcon} context={context} />
) : null
}
/>
);
case 'codeblock':
return <ElementCodeBlock element={element} context={context} state={state} />;
}
}
@@ -29,6 +29,7 @@ export function ElementTextInput(props: ContentKitClientElementProps<ContentKitT
value={value}
placeholder={element.placeholder}
onChange={onChange}
rows={4}
/>
);
}