diff --git a/demo/components_list.py b/demo/components_list.py index 5b76e1b7..529b2d95 100644 --- a/demo/components_list.py +++ b/demo/components_list.py @@ -28,6 +28,22 @@ def components_view() -> list[AnyComponent]: c.Text(text='This is a text component.'), ] ), + c.Div( + components=[ + c.Heading(text='FormattedText', level=2), + c.Markdown( + text="""`FormattedText` can be used to change the style of the text, this is particularly + useful to avoid the overhead of loading the markdown renderer, or when you're including user + generated content that you don't want to escape styling."""), + c.FormattedText( + text='This is a FormattedText component.', + text_color='red', + background_color='blue', + text_format='italic' + ), + ], + class_name='border-top mt-3 pt-1', + ), c.Div( components=[ c.Heading(text='Paragraph', level=2), diff --git a/demo/main.py b/demo/main.py index afbddd65..00b5f9b5 100644 --- a/demo/main.py +++ b/demo/main.py @@ -20,6 +20,7 @@ def api_index() -> list[AnyComponent]: * `Markdown` — that's me :-) * `Text`— example [here](/components#text) +* `FormattedText`— example [here](/components#FormattedText) * `Paragraph` — example [here](/components#paragraph) * `PageTitle` — you'll see the title in the browser tab change when you navigate through the site * `Heading` — example [here](/components#heading) diff --git a/src/npm-fastui/src/components/FormattedText.tsx b/src/npm-fastui/src/components/FormattedText.tsx new file mode 100644 index 00000000..ed0dda39 --- /dev/null +++ b/src/npm-fastui/src/components/FormattedText.tsx @@ -0,0 +1,27 @@ +import { FC } from 'react' + +import { ClassName } from '../hooks/className' + +export interface FormattedTextProps { + type: 'FormattedText' + text: string + textFormat?: 'bold' | 'italic' | 'underline' | 'strikethrough' + color?: string + backgroundColor?: string + className?: ClassName +} + +export const FormattedTextComp: FC = (props) => { + const { text, textFormat, color, backgroundColor } = props + + const style = { + backgroundColor, + color, + fontWeight: textFormat, + fontStyle: textFormat, + textDecoration: + textFormat === 'underline' ? 'underline' : textFormat === 'strikethrough' ? 'line-through' : 'none', + } + + return {text} +} diff --git a/src/npm-fastui/src/components/index.tsx b/src/npm-fastui/src/components/index.tsx index f500bc53..7dda43e5 100644 --- a/src/npm-fastui/src/components/index.tsx +++ b/src/npm-fastui/src/components/index.tsx @@ -40,6 +40,7 @@ import { JsonComp, JsonProps } from './Json' import { ServerLoadComp, ServerLoadProps } from './ServerLoad' import { ImageComp, ImageProps } from './image' import { IframeComp, IframeProps } from './Iframe' +import { FormattedTextComp, FormattedTextProps } from './FormattedText' export type { TextProps, @@ -67,6 +68,7 @@ export type { ServerLoadProps, ImageProps, IframeProps, + FormattedTextProps, } // TODO some better way to export components @@ -97,6 +99,7 @@ export type FastProps = | ServerLoadProps | ImageProps | IframeProps + | FormattedTextProps export type FastClassNameProps = Exclude @@ -179,6 +182,8 @@ export const AnyComp: FC = (props) => { return case 'Iframe': return + case 'FormattedText': + return default: unreachable('Unexpected component type', type, props) return diff --git a/src/python-fastui/fastui/components/__init__.py b/src/python-fastui/fastui/components/__init__.py index 5cdcb223..a650a67d 100644 --- a/src/python-fastui/fastui/components/__init__.py +++ b/src/python-fastui/fastui/components/__init__.py @@ -51,12 +51,12 @@ class Text(pydantic.BaseModel, extra='forbid'): - text: str + text: '_t.Union[str, _t.List[_t.Union[str, FormattedText, Link]]]' type: _t.Literal['Text'] = 'Text' class Paragraph(pydantic.BaseModel, extra='forbid'): - text: str + text: '_t.Union[str, _t.List[_t.Union[str, FormattedText, Link]]]' type: _t.Literal['Paragraph'] = 'Paragraph' @@ -114,7 +114,7 @@ class Code(pydantic.BaseModel, extra='forbid'): class Button(pydantic.BaseModel, extra='forbid'): - text: str + text: '_t.Union[str, _t.List[_t.Union[str, FormattedText, Link]]]' on_click: _t.Union[events.AnyEvent, None] = pydantic.Field(default=None, serialization_alias='onClick') html_type: _t.Union[_t.Literal['button', 'submit', 'reset'], None] = pydantic.Field( default=None, serialization_alias='htmlType' @@ -202,6 +202,17 @@ class Iframe(pydantic.BaseModel, extra='forbid'): type: _t.Literal['Iframe'] = 'Iframe' +class FormattedText(pydantic.BaseModel, extra='forbid'): + text: str + text_format: _t.Union[_t.Literal['bold', 'italic', 'underline', 'strikethrough'], None] = pydantic.Field( + None, serialization_alias='textFormat' + ) + # TODO, use pydantic-extra-types Color? + text_color: _t.Union[str, None] = pydantic.Field(None, serialization_alias='color') + background_color: _t.Union[str, None] = pydantic.Field(None, serialization_alias='backgroundColor') + type: _t.Literal['FormattedText'] = 'FormattedText' + + AnyComponent = _te.Annotated[ _t.Union[ Text,