Use space character for delineating code and headers.pull/1/head
@@ -1,6 +1,6 @@ | |||
import * as React from 'react'; | |||
import clsx from 'clsx'; | |||
import {slider} from './style.module.css'; | |||
import styles from './style.module.css'; | |||
import * as SelectControlBase from '@tesseract-design/web-base-selectcontrol'; | |||
type SliderOrientation = 'horizontal' | 'vertical'; | |||
@@ -209,7 +209,7 @@ export const Slider = React.forwardRef<SliderDerivedElement, SliderProps>(({ | |||
type="range" | |||
data-orient={orient} | |||
className={clsx( | |||
slider, | |||
styles.slider, | |||
'w-full h-full bg-inherit block text-primary ring-secondary/50 rounded-full', | |||
'focus:text-secondary focus:outline-0 focus:ring-4', | |||
'active:text-tertiary active:ring-tertiary/50', | |||
@@ -0,0 +1,198 @@ | |||
import * as React from 'react'; | |||
import * as TextControlBase from '@tesseract-design/web-base-textcontrol'; | |||
import clsx from 'clsx'; | |||
type MaskedTextInputDerivedElement = HTMLInputElement; | |||
export interface MaskedTextInputProps extends Omit<React.HTMLProps<MaskedTextInputDerivedElement>, 'size' | 'type' | 'label'> { | |||
/** | |||
* Short textual description indicating the nature of the component's value. | |||
*/ | |||
label?: React.ReactNode, | |||
/** | |||
* Short textual description as guidelines for valid input values. | |||
*/ | |||
hint?: React.ReactNode, | |||
/** | |||
* Size of the component. | |||
*/ | |||
size?: TextControlBase.TextControlSize, | |||
/** | |||
* Additional description, usually graphical, indicating the nature of the component's value. | |||
*/ | |||
indicator?: React.ReactNode, | |||
/** | |||
* Should the component display a border? | |||
*/ | |||
border?: boolean, | |||
/** | |||
* Should the component occupy the whole width of its parent? | |||
*/ | |||
block?: boolean, | |||
/** | |||
* Style of the component. | |||
*/ | |||
variant?: TextControlBase.TextControlVariant, | |||
/** | |||
* Is the label hidden? | |||
*/ | |||
hiddenLabel?: boolean, | |||
} | |||
/** | |||
* Component for inputting textual values. | |||
* | |||
* This component supports multiline input and adjusts its layout accordingly. | |||
*/ | |||
export const MaskedTextInput = React.forwardRef<MaskedTextInputDerivedElement, MaskedTextInputProps>( | |||
( | |||
{ | |||
label, | |||
hint, | |||
indicator, | |||
size = 'medium' as const, | |||
border = false, | |||
block = false, | |||
variant = 'default' as const, | |||
hiddenLabel = false, | |||
className, | |||
...etcProps | |||
}: MaskedTextInputProps, | |||
ref, | |||
) => { | |||
const labelId = React.useId(); | |||
return ( | |||
<div | |||
className={clsx( | |||
'relative rounded ring-secondary/50', | |||
'focus-within:ring-4', | |||
{ | |||
'block': block, | |||
'inline-block align-middle': !block, | |||
}, | |||
className, | |||
)} | |||
> | |||
<input | |||
{...etcProps} | |||
ref={ref} | |||
aria-labelledby={labelId} | |||
type="password" | |||
data-testid="input" | |||
className={clsx( | |||
'bg-negative rounded-inherit w-full peer block', | |||
'focus:outline-0', | |||
'disabled:opacity-50 disabled:cursor-not-allowed', | |||
{ | |||
'text-xxs': size === 'small', | |||
'text-xs': size === 'medium', | |||
}, | |||
{ | |||
'pl-4': variant === 'default', | |||
'pl-1.5': variant === 'alternate', | |||
}, | |||
{ | |||
'pt-4': variant === 'alternate', | |||
}, | |||
{ | |||
'pr-4': variant === 'default' && !indicator, | |||
'pr-1.5': variant === 'alternate' && !indicator, | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
{ | |||
'h-10': size === 'small', | |||
'h-12': size === 'medium', | |||
'h-16': size === 'large', | |||
}, | |||
)} | |||
/> | |||
{ | |||
label && ( | |||
<div | |||
data-testid="label" | |||
id={labelId} | |||
className={clsx( | |||
'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 peer-focus:text-secondary text-primary leading-none bg-negative', | |||
{ | |||
'sr-only': hiddenLabel, | |||
}, | |||
{ | |||
'pr-1': !indicator, | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
)} | |||
> | |||
<div className="w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis"> | |||
{label} | |||
</div> | |||
</div> | |||
) | |||
} | |||
{hint && ( | |||
<div | |||
data-testid="hint" | |||
className={clsx( | |||
'absolute left-0 px-1 pointer-events-none text-xxs peer-disabled:opacity-50 leading-none w-full bg-negative', | |||
{ | |||
'bottom-0 pl-4 pb-1': variant === 'default', | |||
'top-0.5': variant === 'alternate', | |||
}, | |||
{ | |||
'pt-2': variant === 'alternate' && size === 'small', | |||
'pt-3': variant === 'alternate' && size !== 'small', | |||
}, | |||
{ | |||
'pr-4': !indicator && variant === 'default', | |||
'pr-1': !indicator && variant === 'alternate', | |||
}, | |||
{ | |||
'pr-10': indicator && size === 'small', | |||
'pr-12': indicator && size === 'medium', | |||
'pr-16': indicator && size === 'large', | |||
}, | |||
)} | |||
> | |||
<div | |||
className="opacity-50 whitespace-nowrap w-full h-[1.1em] overflow-hidden text-ellipsis" | |||
> | |||
{hint} | |||
</div> | |||
</div> | |||
)} | |||
{indicator && ( | |||
<div | |||
className={clsx( | |||
'text-center flex items-center justify-center peer-disabled:opacity-50 aspect-square absolute bottom-0 right-0 pointer-events-none', | |||
{ | |||
'w-10': size === 'small', | |||
'w-12': size === 'medium', | |||
'w-16': size === 'large', | |||
}, | |||
)} | |||
> | |||
{indicator} | |||
</div> | |||
)} | |||
{ | |||
border && ( | |||
<span | |||
data-testid="border" | |||
className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none peer-focus:border-secondary" | |||
/> | |||
) | |||
} | |||
</div> | |||
); | |||
}, | |||
); | |||
MaskedTextInput.displayName = 'MaskedTextInput'; |
@@ -1,7 +1,7 @@ | |||
import * as React from 'react'; | |||
import clsx from 'clsx'; | |||
import styles from './style.module.css'; | |||
type BinaryDataCanvasDerivedElement = HTMLPreElement; | |||
type BinaryDataCanvasDerivedElement = HTMLDivElement; | |||
export interface BinaryDataCanvasProps extends Omit<React.HTMLProps<BinaryDataCanvasDerivedElement>, 'children' | 'headers'> { | |||
arrayBuffer?: ArrayBuffer; | |||
@@ -43,22 +43,14 @@ export const BinaryDataCanvas = React.forwardRef<BinaryDataCanvasDerivedElement, | |||
return ( | |||
<div | |||
className={clsx( | |||
'react-binary-data-canvas', | |||
className, | |||
)} | |||
style={{ | |||
...(style ?? {}), | |||
display: 'flex', | |||
gap: '0.5rem', | |||
alignItems: 'flex-start', | |||
}} | |||
{...etcProps} | |||
className={`${styles['react-binary-data-canvas']} ${className ?? ''}`.trim()} | |||
style={style} | |||
ref={forwardedRef} | |||
> | |||
{headers && ( | |||
<pre | |||
style={{ | |||
textAlign: 'right', | |||
}} | |||
className={styles['line-numbers']} | |||
> | |||
<code> | |||
{'\u00a0'} | |||
@@ -67,21 +59,17 @@ export const BinaryDataCanvas = React.forwardRef<BinaryDataCanvasDerivedElement, | |||
<React.Fragment key={i}> | |||
{'\n'} | |||
<code | |||
style={{ | |||
whiteSpace: 'nowrap', | |||
}} | |||
className={styles['code']} | |||
> | |||
0x{(BYTES_PER_LINE * i).toString(16).padStart(2, '0')} | |||
</code> | |||
{' '} | |||
</React.Fragment> | |||
))} | |||
</pre> | |||
)} | |||
<div> | |||
<pre | |||
{...etcProps} | |||
ref={forwardedRef} | |||
> | |||
<div className="highlight"> | |||
<pre> | |||
<code> | |||
{headers && new Array(BYTES_PER_LINE).fill(0).map((_, i) => ( | |||
<span | |||
@@ -0,0 +1,20 @@ | |||
.react-binary-data-canvas { | |||
display: flex; | |||
align-items: flex-start; | |||
} | |||
.react-binary-data-canvas pre { | |||
overflow: visible; | |||
padding: 0; | |||
margin: 0; | |||
line-height: 1.2; | |||
} | |||
.line-numbers { | |||
text-align: right; | |||
user-select: none; | |||
} | |||
.code { | |||
white-space: nowrap; | |||
} |
@@ -1,6 +1,8 @@ | |||
import * as React from 'react'; | |||
import RefractorCore from 'react-refractor/all'; | |||
import clsx from 'clsx'; | |||
import styles from './style.module.css'; | |||
import {getFormValues} from '@theoryofnekomata/formxtra'; | |||
import {Marker} from 'react-refractor'; | |||
type PrismDerivedElement = HTMLDivElement; | |||
@@ -11,10 +13,12 @@ export interface PrismProps extends Omit<React.HTMLProps<PrismDerivedElement>, ' | |||
tabSize?: number; | |||
header?: boolean; | |||
scroll?: boolean; | |||
actions?: React.ReactNode; | |||
markers?: Marker[]; | |||
} | |||
export const Refractor = React.forwardRef<PrismDerivedElement, PrismProps>(({ | |||
code = '', | |||
code: codeRaw = '', | |||
language = 'plain', | |||
lineNumbers = false, | |||
tabSize = 2, | |||
@@ -22,58 +26,68 @@ export const Refractor = React.forwardRef<PrismDerivedElement, PrismProps>(({ | |||
style, | |||
header = false, | |||
scroll = false, | |||
actions, | |||
markers = [], | |||
...etcProps | |||
}, forwardedRef) => { | |||
const handleAction: React.FormEventHandler<HTMLFormElement> = React.useCallback((e) => { | |||
e.preventDefault(); | |||
const { currentTarget, nativeEvent } = e; | |||
const { submitter } = nativeEvent as unknown as { submitter: HTMLButtonElement }; | |||
const { action } = getFormValues(currentTarget, { submitter }); | |||
switch (action) { | |||
case 'copyContents': | |||
console.log('copy contents'); | |||
break; | |||
default: | |||
break; | |||
} | |||
}, []); | |||
const code = codeRaw.trim(); | |||
return ( | |||
<div | |||
className={clsx( | |||
'react-refractor', | |||
className, | |||
)} | |||
{...etcProps} | |||
className={`${styles['react-refractor']} ${className ?? ''}`.trim()} | |||
style={style} | |||
ref={forwardedRef} | |||
> | |||
{header && ( | |||
<div className="flex items-center justify-between"> | |||
<div className="uppercase text-sm"> | |||
<div className={styles['header']}> | |||
<div className={styles['language']}> | |||
{language} | |||
</div> | |||
<form> | |||
<button | |||
type="submit" | |||
name="action" | |||
value="copyContents" | |||
className="h-12 uppercase text-primary font-semi-expanded font-bold" | |||
> | |||
Copy | |||
</button> | |||
<form | |||
onSubmit={handleAction} | |||
> | |||
{actions} | |||
</form> | |||
</div> | |||
)} | |||
<div | |||
className="flex gap-2 items-start" | |||
className={styles['content-wrapper']} | |||
> | |||
{lineNumbers && ( | |||
<pre | |||
style={{ | |||
textAlign: 'right', | |||
}} | |||
className={styles['line-numbers']} | |||
> | |||
<code> | |||
{code.split('\n').map((_, i) => ( | |||
<React.Fragment key={i}> | |||
{i > 0 && '\n'} | |||
{i + 1} | |||
{' '} | |||
</React.Fragment> | |||
))} | |||
</code> | |||
</pre> | |||
)} | |||
<div | |||
className={clsx('flex-auto', scroll && 'overflow-auto')} | |||
className={`${styles['code-wrapper']} ${scroll ? styles['code-wrapper-scroll'] : ''}`.trim()} | |||
> | |||
<RefractorCore | |||
{...etcProps} | |||
markers={markers} | |||
language={language} | |||
value={code} | |||
className="highlight" | |||
@@ -0,0 +1,36 @@ | |||
.react-refractor pre { | |||
overflow: visible; | |||
padding: 0; | |||
margin: 0; | |||
line-height: 1.2; | |||
} | |||
.line-numbers { | |||
text-align: right; | |||
user-select: none; | |||
} | |||
.header { | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
} | |||
.language { | |||
text-transform: uppercase; | |||
font-size: 0.875em; | |||
} | |||
.content-wrapper { | |||
display: flex; | |||
align-items: flex-start; | |||
} | |||
.code-wrapper { | |||
flex: auto; | |||
} | |||
.code-wrapper-scroll { | |||
overflow-x: auto; | |||
overflow-y: hidden; | |||
} |
@@ -1,5 +1,6 @@ | |||
import {Refractor} from '@modal-soft/react-refractor'; | |||
import {NextPage} from 'next'; | |||
import * as React from 'react'; | |||
const BlogPostPage: NextPage = () => { | |||
return ( | |||
@@ -19,6 +20,18 @@ const BlogPostPage: NextPage = () => { | |||
header | |||
language="tsx" | |||
scroll | |||
actions={ | |||
<> | |||
<button | |||
type="submit" | |||
name="action" | |||
value="copyContents" | |||
className="h-12 uppercase text-primary font-semi-expanded font-bold" | |||
> | |||
Copy | |||
</button> | |||
</> | |||
} | |||
code={`import {Refractor} from '@modal-soft/react-refractor'; | |||
import {NextPage} from 'next'; | |||
@@ -96,55 +96,41 @@ | |||
} | |||
} | |||
.react-refractor pre { | |||
overflow: visible; | |||
padding: 0; | |||
margin: 0; | |||
line-height: 1.2; | |||
} | |||
.react-refractor .token.number { color: rgb(var(--color-code-number)); } | |||
.react-refractor .token.keyword { color: rgb(var(--color-code-keyword)); } | |||
.react-refractor .token.tag { color: rgb(var(--color-code-keyword)); } | |||
.react-refractor .token.type { color: rgb(var(--color-code-type)); } | |||
.react-refractor .token.instance-attribute { color: rgb(var(--color-code-instance-attribute)); } | |||
.react-refractor .token.maybe-class-name { color: rgb(var(--color-code-function)); font-style: italic; } | |||
.react-refractor .token.function { color: rgb(var(--color-code-function)); font-style: italic; } | |||
.react-refractor .token.parameter { color: rgb(var(--color-code-parameter)); } | |||
.react-refractor .token.property { color: rgb(var(--color-code-property)); } | |||
.react-refractor .token.attr-name { color: rgb(var(--color-code-property)); font-style: italic; } | |||
.react-refractor .token.string { color: rgb(var(--color-code-string)); } | |||
.react-refractor .token.attr-value { color: rgb(var(--color-code-string)); } | |||
.react-refractor .token.attr-value .attr-equals { color: rgb(var(--color-positive)); } | |||
.react-refractor .token.variable { color: rgb(var(--color-code-variable)); } | |||
.react-refractor .token.regexp { color: rgb(var(--color-code-regexp)); } | |||
.react-refractor .token.url { color: rgb(var(--color-code-url)); } | |||
.react-refractor .token.global { color: rgb(var(--color-code-global)); } | |||
.react-refractor .token.comment { opacity: 0.5; } | |||
.react-binary-data-canvas pre { | |||
overflow: visible; | |||
padding: 0; | |||
margin: 0; | |||
line-height: 1.2; | |||
} | |||
.react-binary-data-canvas .x00 { color: rgb(var(--color-code-keyword)); } | |||
.react-binary-data-canvas .x10 { color: rgb(var(--color-code-global)); } | |||
.react-binary-data-canvas .x20 { color: rgb(var(--color-code-string)); } | |||
.react-binary-data-canvas .x30 { color: rgb(var(--color-code-number)); } | |||
.react-binary-data-canvas .x40 { color: rgb(var(--color-code-url)); } | |||
.react-binary-data-canvas .x50 { color: rgb(var(--color-code-type)); } | |||
.react-binary-data-canvas .x60 { color: rgb(var(--color-code-parameter)); } | |||
.react-binary-data-canvas .x70 { color: rgb(var(--color-code-property)); } | |||
.react-binary-data-canvas .x80 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-keyword)); } | |||
.react-binary-data-canvas .x90 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-global)); } | |||
.react-binary-data-canvas .xa0 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-string)); } | |||
.react-binary-data-canvas .xb0 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-number)); } | |||
.react-binary-data-canvas .xc0 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-url)); } | |||
.react-binary-data-canvas .xd0 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-type)); } | |||
.react-binary-data-canvas .xe0 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-parameter)); } | |||
.react-binary-data-canvas .xf0 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-property)); } | |||
.highlight .token.number { color: rgb(var(--color-code-number)); } | |||
.highlight .token.keyword { color: rgb(var(--color-code-keyword)); } | |||
.highlight .token.tag { color: rgb(var(--color-code-keyword)); } | |||
.highlight .token.type { color: rgb(var(--color-code-type)); } | |||
.highlight .token.instance-attribute { color: rgb(var(--color-code-instance-attribute)); } | |||
.highlight .token.maybe-class-name { color: rgb(var(--color-code-function)); font-style: italic; } | |||
.highlight .token.function { color: rgb(var(--color-code-function)); font-style: italic; } | |||
.highlight .token.parameter { color: rgb(var(--color-code-parameter)); } | |||
.highlight .token.property { color: rgb(var(--color-code-property)); } | |||
.highlight .token.attr-name { color: rgb(var(--color-code-property)); font-style: italic; } | |||
.highlight .token.string { color: rgb(var(--color-code-string)); } | |||
.highlight .token.attr-value { color: rgb(var(--color-code-string)); } | |||
.highlight .token.attr-value .attr-equals { color: rgb(var(--color-positive)); } | |||
.highlight .token.variable { color: rgb(var(--color-code-variable)); } | |||
.highlight .token.regexp { color: rgb(var(--color-code-regexp)); } | |||
.highlight .token.url { color: rgb(var(--color-code-url)); } | |||
.highlight .token.global { color: rgb(var(--color-code-global)); } | |||
.highlight .token.comment { opacity: 0.5; } | |||
.highlight .x00 { color: rgb(var(--color-code-keyword)); } | |||
.highlight .x10 { color: rgb(var(--color-code-global)); } | |||
.highlight .x20 { color: rgb(var(--color-code-string)); } | |||
.highlight .x30 { color: rgb(var(--color-code-number)); } | |||
.highlight .x40 { color: rgb(var(--color-code-url)); } | |||
.highlight .x50 { color: rgb(var(--color-code-type)); } | |||
.highlight .x60 { color: rgb(var(--color-code-parameter)); } | |||
.highlight .x70 { color: rgb(var(--color-code-property)); } | |||
.highlight .x80 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-keyword)); } | |||
.highlight .x90 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-global)); } | |||
.highlight .xa0 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-string)); } | |||
.highlight .xb0 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-number)); } | |||
.highlight .xc0 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-url)); } | |||
.highlight .xd0 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-type)); } | |||
.highlight .xe0 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-parameter)); } | |||
.highlight .xf0 { color: rgb(var(--color-negative)); background-color: rgb(var(--color-code-property)); } | |||
.focus\:outline-0:-moz-focusring { | |||
outline: 0; | |||