AItool's picture
Upload 81 files
d467ea7 verified
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_franken.ipynb.
# %% auto 0
__all__ = ['franken_class_map', 'TextT', 'TextPresets', 'CodeSpan', 'CodeBlock', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'Subtitle',
'Q', 'Em', 'Strong', 'I', 'Small', 'Mark', 'Del', 'Ins', 'Sub', 'Sup', 'Blockquote', 'Caption', 'Cite',
'Time', 'Address', 'Abbr', 'Dfn', 'Kbd', 'Samp', 'Var', 'Figure', 'Details', 'Summary', 'Data', 'Meter', 'S',
'U', 'Output', 'PicSumImg', 'AccordionItem', 'Accordion', 'ButtonT', 'Button', 'ContainerT', 'BackgroundT',
'Container', 'Titled', 'DividerT', 'Divider', 'DividerSplit', 'DividerLine', 'Article', 'ArticleTitle',
'ArticleMeta', 'SectionT', 'Section', 'Form', 'Fieldset', 'Legend', 'Input', 'Radio', 'CheckboxX', 'Range',
'TextArea', 'Switch', 'Upload', 'UploadZone', 'FormLabel', 'LabelT', 'Label', 'UkFormSection',
'GenericLabelInput', 'LabelInput', 'LabelTextArea', 'LabelSwitch', 'LabelRadio', 'LabelCheckboxX', 'Options',
'Select', 'LabelSelect', 'LabelRange', 'AT', 'ListT', 'ModalContainer', 'ModalDialog', 'ModalHeader',
'ModalBody', 'ModalFooter', 'ModalTitle', 'ModalCloseButton', 'Modal', 'Placeholder', 'Progress', 'UkIcon',
'UkIconLink', 'DiceBearAvatar', 'Center', 'FlexT', 'Grid', 'DivFullySpaced', 'DivCentered', 'DivLAligned',
'DivRAligned', 'DivVStacked', 'DivHStacked', 'NavT', 'NavContainer', 'NavParentLi', 'NavDividerLi',
'NavHeaderLi', 'NavSubtitle', 'NavCloseLi', 'ScrollspyT', 'NavBar', 'SliderContainer', 'SliderItems',
'SliderNav', 'Slider', 'DropDownNavContainer', 'TabContainer', 'CardT', 'CardTitle', 'CardHeader',
'CardBody', 'CardFooter', 'CardContainer', 'Card', 'TableT', 'Table', 'Td', 'Th', 'Tbody', 'TableFromLists',
'TableFromDicts', 'apply_classes', 'FrankenRenderer', 'render_md', 'ThemePicker', 'LightboxContainer',
'LightboxItem', 'ApexChart']
# %% ../nbs/02_franken.ipynb
import fasthtml.common as fh
from .foundations import *
from fasthtml.common import Div, P, Span, FT
from enum import Enum, auto
from fasthtml.components import Uk_select,Uk_input_tag,Uk_icon,Uk_input_range, Uk_chart
from functools import partial
from itertools import zip_longest
from typing import Union, Tuple, Optional, Sequence
from fastcore.all import *
import copy, re, httpx, os
import pathlib
from mistletoe.html_renderer import HTMLRenderer
from mistletoe.span_token import Image
import mistletoe
from lxml import html, etree
import fasthtml.components as fh_comp
import json
# %% ../nbs/02_franken.ipynb
class TextT(VEnum):
'Text Styles from https://franken-ui.dev/docs/text'
def _generate_next_value_(name, start, count, last_values):
return str2ukcls('text', name)
paragraph = "uk-paragraph"
# Text Style
lead,meta, gray, italic= auto(), auto(), 'text-gray-500 dark:text-gray-200', 'italic'
# Text Size
xs, sm, lg, xl = 'text-xs', 'text-sm', 'text-lg', 'text-xl'
# Text Weight
light, normal, medium, bold, extrabold = 'font-light','font-normal','font-medium','font-bold','font-extrabold'
# Text Color
muted,primary,secondary = 'text-gray-500 dark:text-gray-200', 'text-primary', 'text-secondary'
success,warning, error, info = 'text-success', 'text-warning', 'text-error', 'text-info'
# Text Alignment
left, right,center = "text-left","text-right","text-center"
justify, start, end = "text-justify","text-start","text-end"
# Vertical Alignment
top,middle,bottom = 'align-top','align-middle','align-bottom'
# Text Wrapping
truncate,break_,nowrap = 'uk-text-truncate','uk-text-break', 'uk-text-nowrap'
# other
underline = 'underline'
highlight = 'bg-yellow-200 dark:bg-yellow-800 text-black'
class TextPresets(VEnum):
'Common Typography Presets'
muted_sm = TextT.muted+TextT.sm
muted_lg = TextT.muted+TextT.lg
bold_sm = TextT.bold+TextT.sm
bold_lg = TextT.bold+TextT.lg
md_weight_sm = stringify((TextT.sm, TextT.medium))
md_weight_muted = stringify((TextT.medium, TextT.muted))
# %% ../nbs/02_franken.ipynb
def CodeSpan(*c, # Contents of CodeSpan tag (inline text code snippets)
cls=(), # Classes in addition to CodeSpan styling
**kwargs # Additional args for CodeSpan tag
)->FT: # Code(..., cls='uk-codespan')
"A CodeSpan with Styling"
return fh.Code(*c, cls=('uk-codespan', stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def CodeBlock(*c: str, # Contents of Code tag (often text)
cls: Enum | str | tuple = (), # Classes for the outer container
code_cls: Enum | str | tuple = (), # Classes for the code tag
**kwargs # Additional args for Code tag
) -> FT: # Div(Pre(Code(..., cls='uk-codeblock), cls='multiple tailwind styles'), cls='uk-block')
"CodeBlock with Styling"
return Div(
Pre(Code(*c, cls=('uk-codeblock', stringify(code_cls)), **kwargs),
cls=(f'bg-gray-100 dark:bg-gray-800 {TextT.gray} p-0.4 rounded text-sm font-mono')),
# cls=('bg-gray-100 dark:bg-gray-800 dark:text-gray-200 p-0.4 rounded text-sm font-mono')),
cls=('uk-block', stringify(cls)))
# %% ../nbs/02_franken.ipynb
def H1(*c:FT|str, # Contents of H1 tag (often text)
cls:Enum|str|tuple=(), # Classes in addition to H1 styling
**kwargs # Additional args for H1 tag
)->FT: # H1(..., cls='uk-h1')
"H1 with styling and appropriate size"
return fh.H1(*c, cls=('uk-h1',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def H2(*c:FT|str, # Contents of H2 tag (often text)
cls:Enum|str|tuple=(), # Classes in addition to H2 styling
**kwargs # Additional args for H2 tag
)->FT: # H2(..., cls='uk-h2')
"H2 with styling and appropriate size"
return fh.H2(*c, cls=('uk-h2',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def H3(*c:FT|str, # Contents of H3 tag (often text)
cls:Enum|str|tuple=(), # Classes in addition to H3 styling
**kwargs # Additional args for H3 tag
)->FT: # H3(..., cls='uk-h3')
"H3 with styling and appropriate size"
return fh.H3(*c, cls=('uk-h3',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def H4(*c:FT|str, # Contents of H4 tag (often text)
cls:Enum|str|tuple=(), # Classes in addition to H4 styling
**kwargs # Additional args for H4 tag
)->FT: # H4(..., cls='uk-h4')
"H4 with styling and appropriate size"
return fh.H4(*c, cls=('uk-h4',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def H5(*c:FT|str, # Contents of H5 tag (often text)
cls:Enum|str|tuple=(), # Classes in addition to H5 styling
**kwargs # Additional args for H5 tag
)->FT: # H5(..., cls='text-lg font-semibold')
"H5 with styling and appropriate size"
return fh.H5(*c, cls=('text-lg font-semibold',stringify(cls)), **kwargs)
def H6(*c:FT|str, # Contents of H6 tag (often text)
cls:Enum|str|tuple=(), # Classes in addition to H6 styling
**kwargs # Additional args for H6 tag
)->FT: # H6(..., cls='text-base font-semibold')
"H6 with styling and appropriate size"
return fh.H6(*c, cls=('text-base font-semibold',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Subtitle(*c:FT|str, # Contents of P tag (often text)
cls:Enum|str|tuple='mt-1.5', # Additional classes
**kwargs # Additional args for P tag
)->FT:
"Styled muted_sm text designed to go under Headings and Titles"
return fh.P(*c, cls=(TextPresets.muted_sm, stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Q(*c:FT|str, # Contents of Q tag (quote)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Q tag
)->FT:
"Styled quotation mark"
return fh.Q(*c, cls=(TextT.italic,TextT.lg, stringify(cls)), **kwargs)
def Em(*c:FT|str, # Contents of Em tag (emphasis)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Em tag
)->FT:
"Styled emphasis text"
return fh.Em(*c, cls=(TextT.medium, stringify(cls)), **kwargs)
def Strong(*c:FT|str, # Contents of Strong tag
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Strong tag
)->FT:
"Styled strong text"
return fh.Strong(*c, cls=(TextT.bold, stringify(cls)), **kwargs)
def I(*c:FT|str, # Contents of I tag (italics)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for I tag
)->FT:
"Styled italic text"
return fh.I(*c, cls=(TextT.italic, stringify(cls)), **kwargs)
def Small(*c:FT|str, # Contents of Small tag
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Small tag
)->FT:
"Styled small text"
return fh.Small(*c, cls=(TextT.sm, stringify(cls)), **kwargs)
def Mark(*c:FT|str, # Contents of Mark tag (highlighted text)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Mark tag
)->FT:
"Styled highlighted text"
return fh.Mark(*c, cls=(TextT.highlight, stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Del(*c:FT|str, # Contents of Del tag (deleted text)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Del tag
)->FT:
"Styled deleted text"
return fh.Del(*c, cls=('line-through', TextT.gray, stringify(cls)), **kwargs)
def Ins(*c:FT|str, # Contents of Ins tag (inserted text)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Ins tag
)->FT:
"Styled inserted text"
return fh.Ins(*c, cls=(TextT.underline+' text-green-600', stringify(cls)), **kwargs)
def Sub(*c:FT|str, # Contents of Sub tag (subscript)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Sub tag
)->FT:
"Styled subscript text"
return fh.Sub(*c, cls=(TextT.sm+' -bottom-1 relative', stringify(cls)), **kwargs)
def Sup(*c:FT|str, # Contents of Sup tag (superscript)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Sup tag
)->FT:
"Styled superscript text"
return fh.Sup(*c, cls=(TextT.sm+' -top-1 relative', stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Blockquote(*c:FT|str, # Contents of Blockquote tag (often text)
cls:Enum|str|tuple=(), # Classes in addition to Blockquote styling
**kwargs # Additional args for Blockquote tag
)->FT: # Blockquote(..., cls='uk-blockquote')
"Blockquote with Styling"
return fh.Blockquote(*c, cls=('uk-blockquote',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Caption(*c:FT|str, cls:Enum|str|tuple=(), **kwargs)->FT:
"Styled caption text"
return fh.Caption(
Span(*c, cls=(TextT.gray, TextT.sm, stringify(cls))),
**kwargs)
# %% ../nbs/02_franken.ipynb
def Cite(*c:FT|str, # Contents of Cite tag
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Cite tag
)->FT:
"Styled citation text"
return fh.Cite(*c, cls=(TextT.italic, TextT.gray, stringify(cls)), **kwargs)
def Time(*c:FT|str, # Contents of Time tag
cls:Enum|str|tuple=(), # Additional classes
datetime:str=None, # datetime attribute
**kwargs # Additional args for Time tag
)->FT:
"Styled time element"
if datetime: kwargs['datetime'] = datetime
return fh.Time(*c, cls=(TextT.gray, stringify(cls)), **kwargs)
def Address(*c:FT|str, # Contents of Address tag
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Address tag
)->FT:
"Styled address element"
return fh.Address(*c, cls=(TextT.italic, stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Abbr(*c:FT|str, # Contents of Abbr tag
cls:Enum|str|tuple=(), # Additional classes
title:str=None, # Title attribute for abbreviation
**kwargs # Additional args for Abbr tag
)->FT:
"Styled abbreviation with dotted underline"
if title: kwargs['title'] = title
return fh.Abbr(*c, cls=('border-b border-dotted border-secondary hover:cursor-help', stringify(cls)), **kwargs)
def Dfn(*c:FT|str, # Contents of Dfn tag (definition)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Dfn tag
)->FT:
"Styled definition term with italic and medium weight"
return fh.Dfn(*c, cls=(TextT.medium + TextT.italic + TextT.gray, stringify(cls)), **kwargs)
def Kbd(*c:FT|str, # Contents of Kbd tag (keyboard input)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Kbd tag
)->FT:
"Styled keyboard input with subtle background"
return fh.Kbd(*c, cls=('font-mono px-1.5 py-0.5 text-sm bg-secondary border border-gray-300 dark:border-gray-600 rounded shadow-sm', stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Samp(*c:FT|str, # Contents of Samp tag (sample output)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Samp tag
)->FT:
"Styled sample output with subtle background"
return fh.Samp(*c, cls=('font-mono bg-secondary px-1 rounded', TextT.gray, stringify(cls)), **kwargs)
def Var(*c:FT|str, # Contents of Var tag (variable)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Var tag
)->FT:
"Styled variable with italic monospace"
return fh.Var(*c, cls=('font-mono',TextT.italic + TextT.gray, stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Figure(*c:FT|str, # Contents of Figure tag
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Figure tag
)->FT:
"Styled figure container with card-like appearance"
return fh.Figure(*c, cls=('p-4 my-4 border border-gray-200 dark:border-gray-800 rounded-lg shadow-sm bg-card', stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Details(*c:FT|str, # Contents of Details tag
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Details tag
)->FT:
"Styled details element"
return fh.Details(*c, cls=('border border-secondary rounded-lg', stringify(cls)), **kwargs)
def Summary(*c:FT|str, # Contents of Summary tag
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Summary tag
)->FT:
"Styled summary element"
return fh.Summary(*c, cls=(TextT.medium + ' p-3 hover:bg-secondary cursor-pointer', stringify(cls)), **kwargs)
def Data(*c:FT|str, # Contents of Data tag
value:str=None, # Value attribute
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Data tag
)->FT:
"Styled data element"
if value: kwargs['value'] = value
return fh.Data(*c, cls=('font-mono text-sm bg-secondary px-1 rounded', stringify(cls)), **kwargs)
def Meter(*c:FT|str, # Contents of Meter tag
value:float=None, # Current value
min:float=None, # Minimum value
max:float=None, # Maximum value
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Meter tag
)->FT:
"Styled meter element"
if value is not None: kwargs['value'] = value
if min is not None: kwargs['min'] = min
if max is not None: kwargs['max'] = max
return fh.Meter(*c, cls=('w-full h-2 bg-secondary rounded', stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def S(*c:FT|str, # Contents of S tag (strikethrough)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for S tag
)->FT:
"Styled strikethrough text (different semantic meaning from Del)"
return fh.S(*c, cls=('line-through', TextT.gray, stringify(cls)), **kwargs)
def U(*c:FT|str, # Contents of U tag (unarticulated annotation)
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for U tag
)->FT:
"Styled underline (for proper names in Chinese, proper spelling etc)"
return fh.U(*c, cls=(TextT.underline, stringify(cls)), **kwargs)
def Output(*c:FT|str, # Contents of Output tag
form:str=None, # ID of form this output belongs to
for_:str=None, # IDs of elements this output is for
cls:Enum|str|tuple=(), # Additional classes
**kwargs # Additional args for Output tag
)->FT:
"Styled output element for form results"
if form: kwargs['form'] = form
if for_: kwargs['for'] = for_ # Note: 'for' is reserved in Python
return fh.Output(*c, cls=('font-mono bg-secondary px-2 py-1 rounded',
stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def PicSumImg(h:int=200, # Height in pixels
w:int=200, # Width in pixels
id:int=None, # Optional specific image ID to use
grayscale:bool=False, # Whether to return grayscale version
blur:int=None, # Optional blur amount (1-10)
**kwargs # Additional args for Img tag
)->FT: # Img tag with picsum image
"Creates a placeholder image using https://picsum.photos/"
url = f"https://picsum.photos"
if id is not None: url = f"{url}/id/{id}"
url = f"{url}/{w}/{h}"
if grayscale: url = f"{url}?grayscale"
if blur is not None:
url = f"{url}{'?' if not grayscale else '&'}blur={max(1,min(10,blur))}"
return fh.Img(src=url, loading="lazy", **kwargs)
# %% ../nbs/02_franken.ipynb
def AccordionItem(title: Union[str, FT], # Content for the accordion item title
*c: FT, # Content to display when the item is open
cls: Union[str, Enum, tuple] = (), # Additional classes for the outer `Li` container
title_cls: Union[str, Enum, tuple] = ('flex justify-between items-center w-full',), # Additional classes for the title `A` tag
content_cls: Union[str, Enum, tuple] = (), # Additional classes for the content `Div`
open: bool = False, # Whether this item should be open by default
li_kwargs: Optional[Dict] = None, # Additional attributes for the outer `Li` tag
a_kwargs: Optional[Dict] = None, # Additional attributes for the title `A` tag
div_kwargs: Optional[Dict] = None # Additional attributes for the content `Div` tag
) -> FT: # Li(A(title, Span(Icon, Icon)), Div(content))
"Creates a single item for use within an Accordion component, handling title, content, and open state."
li_attrs, a_attrs, div_attrs = li_kwargs or {}, a_kwargs or {}, div_kwargs or {}
li_classes = ['group', stringify(cls)]
if open: li_classes.append('uk-open')
final_li_cls = stringify(li_classes)
combined_title_cls = stringify(('uk-accordion-title', stringify(title_cls)))
content_classes = stringify(('uk-accordion-content', stringify(content_cls)))
if 'href' not in a_attrs: a_attrs['href'] = '#'
icon_container = Span(
UkIcon("chevron-down", cls="block group-[.uk-open]:hidden h-5 w-5"),
UkIcon("chevron-up", cls="hidden group-[.uk-open]:block h-5 w-5")
)
return fh.Li(
fh.A(title, icon_container, cls=combined_title_cls, **a_attrs),
fh.Div(*c, cls=content_classes, **div_attrs),
cls=final_li_cls,
**li_attrs
)
# %% ../nbs/02_franken.ipynb
def Accordion(*c: 'AccordionItem', # One or more `AccordionItem` components
cls: Union[str, Enum, tuple] = (), # Additional classes for the container (`Ul` or `Div`)
multiple: Optional[bool] = None, # Allow multiple items to be open simultaneously (UIkit option)
collapsible: Optional[bool] = None, # Allow all items to be closed (UIkit option, default True)
animation: Optional[bool] = None, # Enable/disable animation (UIkit option, default True)
duration: Optional[int] = None, # Animation duration in ms (UIkit option, default 200)
active: Optional[int] = None, # Index (0-based) of the item to be open by default (UIkit option)
transition: Optional[str] = None, # Animation transition timing function (UIkit option, e.g., 'ease-out')
tag: str = 'ul', # HTML tag for the container ('ul' or 'div')
**kwargs # Additional attributes for the container tag (e.g., id)
) -> FT: # Ul(*items...) or Div(*items...)
"""
Creates a styled Accordion container using accordion component.
"""
opts = []
if multiple is not None: opts.append(f"multiple: {str(multiple).lower()}")
if collapsible is not None: opts.append(f"collapsible: {str(collapsible).lower()}")
if animation is not None: opts.append(f"animation: {str(animation).lower()}")
if duration is not None: opts.append(f"duration: {duration}")
if active is not None: opts.append(f"active: {active}")
if transition is not None: opts.append(f"transition: {transition}")
uk_attr_val = "; ".join(opts) if opts else ""
kwargs['uk-accordion'] = uk_attr_val
container_tag = fh.Ul if tag.lower() == 'ul' else fh.Div
final_cls = stringify(cls)
return container_tag(*c, cls=final_cls, **kwargs)
# %% ../nbs/02_franken.ipynb
class ButtonT(VEnum):
"Options for styling Buttons"
def _generate_next_value_(name, start, count, last_values): return str2ukcls('btn', name)
default, ghost, primary = auto(),auto(),auto()
secondary, destructive = auto(), auto()
text, link = auto(), auto()
xs, sm, lg, xl = auto(), auto(), auto(), auto()
icon = auto()
# %% ../nbs/02_franken.ipynb
def Button(*c: Union[str, FT], # Contents of `Button` tag (often text)
cls: Union[str, Enum]=ButtonT.default, # Classes in addition to `Button` styling (use `ButtonT` for built in styles)
submit=True, # Whether the button should submit a form
**kwargs # Additional args for `Button` tag
) -> FT: # Button(..., cls='uk-btn')
"Button with Styling (defaults to `submit` for form submission)"
if 'type' not in kwargs: kwargs['type'] = 'submit' if submit else 'button'
return fh.Button(*c, cls=('uk-btn', stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
class ContainerT(VEnum):
'Max width container sizes from https://franken-ui.dev/docs/container'
def _generate_next_value_(name, start, count, last_values): return str2ukcls('container', name)
xs = auto()
sm = auto()
lg = auto()
xl = auto()
expand = auto()
# %% ../nbs/02_franken.ipynb
class BackgroundT(VEnum):
def _generate_next_value_(name, start, count, last_values): return str2ukcls('background', name)
muted = auto()
primary = auto()
secondary = auto()
default = auto()
# %% ../nbs/02_franken.ipynb
def Container(*c, # Contents of Container tag (often other FT Components)
cls=('mt-5', ContainerT.xl), # Classes in addition to Container styling
**kwargs # Additional args for Container (`Div` tag)
)->FT: # Container(..., cls='uk-container')
"Div to be used as a container that often wraps large sections or a page of content"
return Div(*c, cls=('uk-container',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Titled(title:str="FastHTML app", # Title of the page
*c, # Contents of the page (often other tags)
cls=ContainerT.xl, # Classes in addition to Container styling
**kwargs # Additional args for Container (`Div` tag)
)->FT: # Title, Main(Container(H1(title), content))
"Creates a standard page structure for titled page. Main(Container(title, content))"
return fh.Title(title), fh.Main(Container(H1(title), *c, cls=cls, **kwargs))
# %% ../nbs/02_franken.ipynb
class DividerT(VEnum):
"Divider Styles from https://franken-ui.dev/docs/divider"
def _generate_next_value_(name, start, count, last_values): return str2ukcls('divider', name)
icon=auto()
sm=auto()
vertical=auto()
# %% ../nbs/02_franken.ipynb
def Divider(*c, # contents of Divider tag (often nothing)
cls=('my-4', DividerT.icon), # Classes in addition to Divider styling
**kwargs # Additional args for Divider tag
)->FT: # Hr(..., cls='uk-divider-icon') or Div(..., cls='uk-divider-vertical')
"Divider with default styling and margin"
cls = stringify(cls)
container = Div if 'uk-divider-vertical' in cls else Hr
return container(*c, cls=cls, **kwargs)
# %% ../nbs/02_franken.ipynb
def DividerSplit(*c, cls=(), line_cls=(), text_cls=()):
"Creates a simple horizontal line divider with configurable thickness and vertical spacing"
cls, line_cls, text_cls = map(stringify,(cls, line_cls, text_cls))
return Div(cls='relative ' + cls)(
Div(cls="absolute inset-0 flex items-center " + line_cls)(Span(cls="w-full border-t border-border")),
Div(cls="relative flex justify-center " + text_cls)(Span(cls="bg-background px-2 ")(*c)))
# %% ../nbs/02_franken.ipynb
def DividerLine(lwidth=2, y_space=4): return Hr(cls=f"my-{y_space} h-[{lwidth}px] w-full bg-secondary")
# %% ../nbs/02_franken.ipynb
def Article(*c, # contents of Article tag (often other tags)
cls=(), # Classes in addition to Article styling
**kwargs # Additional args for Article tag
)->FT: # Article(..., cls='uk-article')
"A styled article container for blog posts or similar content"
return fh.Article(*c, cls=('uk-article',stringify(cls)), **kwargs)
def ArticleTitle(*c, # contents of ArticleTitle tag (often other tags)
cls=(), # Classes in addition to ArticleTitle styling
**kwargs # Additional args for ArticleTitle tag
)->FT: # H1(..., cls='uk-article-title')
"A title component for use within an Article"
return H1(*c, cls=('uk-article-title',stringify(cls)), **kwargs)
def ArticleMeta(*c, # contents of ArticleMeta tag (often other tags)
cls=(), # Classes in addition to ArticleMeta styling
**kwargs # Additional args for ArticleMeta tag
)->FT: # P(..., cls='uk-article-meta')
"A metadata component for use within an Article showing things like date, author etc"
return P(*c, cls=('uk-article-meta',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
class SectionT(VEnum):
'Section styles from https://franken-ui.dev/docs/section'
def _generate_next_value_(name, start, count, last_values): return str2ukcls('section', name)
default = auto()
muted = auto()
primary = auto()
secondary = auto()
xs = 'uk-section-xsmall'
sm = 'uk-section-small'
lg = 'uk-section-large'
xl = 'uk-section-xlarge'
remove_vertical = auto()
# %% ../nbs/02_franken.ipynb
def Section(*c, # contents of Section tag (often other tags)
cls=(), # Classes in addition to Section styling
**kwargs # Additional args for Section tag
)->FT: # Div(..., cls='uk-section')
"Section with styling and margins"
return fh.Div(*c, cls=('uk-section',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Form(*c, # contents of Form tag (often Buttons, FormLabels, and LabelInputs)
cls='space-y-3', # Classes in addition to Form styling (default is 'space-y-3' to prevent scrunched up form elements)
**kwargs # Additional args for Form tag
)->FT: # Form(..., cls='space-y-3')
"A Form with default spacing between form elements"
return fh.Form(*c, cls=stringify(cls), **kwargs)
# %% ../nbs/02_franken.ipynb
def Fieldset(*c, # contents of Fieldset tag (often other tags)
cls='flex', # Classes in addition to Fieldset styling
**kwargs # Additional args for Fieldset tag
)->FT: # Fieldset(..., cls='uk-fieldset')
"A Fieldset with default styling"
return fh.Fieldset(*c, cls=('uk-fieldset',stringify(cls)), **kwargs)
def Legend(*c, # contents of Legend tag (often other tags)
cls=(), # Classes in addition to Legend styling
**kwargs # Additional args for Legend tag
)->FT: # Legend(..., cls='uk-legend')
"A Legend with default styling"
return fh.Legend(*c, cls=('uk-legend',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Input(*c, # contents of Input tag (often nothing)
cls=(), # Classes in addition to Input styling
**kwargs # Additional args for Input tag
)->FT: # Input(..., cls='uk-input')
"An Input with default styling"
return fh.Input(*c, cls=('uk-input',stringify(cls)), **kwargs)
def Radio(*c, # contents of Radio tag (often nothing)
cls=(), # Classes in addition to Radio styling
**kwargs # Additional args for Radio tag
)->FT: # Input(..., cls='uk-radio', type='radio')
"A Radio with default styling"
return fh.Input(*c, cls=('uk-radio',stringify(cls)), type='radio', **kwargs)
def CheckboxX(*c, # contents of CheckboxX tag (often nothing)
cls=(), # Classes in addition to CheckboxX styling
**kwargs # Additional args for CheckboxX tag
)->FT: # Input(..., cls='uk-checkbox', type='checkbox')
"A Checkbox with default styling"
return fh.Input(*c, cls=('uk-checkbox',stringify(cls)), type='checkbox', **kwargs)
# %% ../nbs/02_franken.ipynb
def Range(*c, # contents of Range tag (often nothing)
value='',
label=True,
min=None,
max=None,
step=None,
cls=(), # Classes in addition to Range styling
**kwargs # Additional args for Range tag
)->FT: # Input(..., cls='uk-range', type='range')
"A Range with default styling"
return Uk_input_range(*c, min=min, label=label, max=max, value=value, step=step, multiple=len(value.split(','))>1, cls=('uk-range',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def TextArea(*c, # contents of TextArea tag (often text)
cls=(), # Classes in addition to TextArea styling
**kwargs # Additional args for TextArea tag
)->FT: # TextArea(..., cls='uk-textarea')
"A Textarea with default styling"
return fh.Textarea(*c, cls=('uk-textarea',stringify(cls)), **kwargs)
def Switch(*c, # contents of Switch tag (often nothing)
cls=(), # Classes in addition to Switch styling
**kwargs # Additional args for Switch tag
)->FT: # Input(..., cls='uk-toggle-switch uk-toggle-switch-primary min-w-9', type='checkbox')
"A Switch with default styling"
return fh.Input(*c, cls=('uk-toggle-switch uk-toggle-switch-primary min-w-9',stringify(cls)), type='checkbox', **kwargs)
# %% ../nbs/02_franken.ipynb
def Upload(*c, # Contents of Upload tag button (often text)
cls=(), # Classes in addition to Upload styling
multiple=False, # Whether to allow multiple file selection
accept=None, # File types to accept (e.g. 'image/*')
button_cls=ButtonT.default, # Classes for the button
id=None, # ID for the file input
name=None, # Name for the file input
**kwargs # Additional args for the outer div
)->FT: # Div(Input(type='file'), Button(...))
"A file upload component with default styling"
input_kwargs = {'type': 'file', 'multiple': multiple}
if accept: input_kwargs['accept'] = accept
if id: input_kwargs['id'] = id
if name: input_kwargs['name'] = name
return Div(
fh.Input(**input_kwargs),
Button(*c, cls=button_cls, submit=False, tabindex="-1"),
cls=('w-full js-upload', stringify(cls)),
uk_form_custom=True)
def UploadZone(*c, # Contents of UploadZone tag (often text or other tags)
cls=(), # Classes in addition to UploadZone styling
multiple=False, # Whether to allow multiple file selection
accept=None, # File types to accept (e.g. 'image/*')
id=None, # ID for the file input
name=None, # Name for the file input
**kwargs # Additional args for the outer div
)->FT:
"A file drop zone component with default styling"
input_kwargs = {'type': 'file', 'multiple': multiple}
if accept: input_kwargs['accept'] = accept
if id: input_kwargs['id'] = id
if name: input_kwargs['name'] = name
return Div(
Div(fh.Input(**input_kwargs),
Span(*c),
uk_form_custom=True,
cls='w-full'),
cls=('js-upload uk-placeholder uk-text-center', stringify(cls)),
**kwargs)
# %% ../nbs/02_franken.ipynb
def FormLabel(*c, # contents of FormLabel tag (often text)
cls=(), # Classes in addition to FormLabel styling
**kwargs # Additional args for FormLabel tag
)->FT: # Label(..., cls='uk-form-label')
"A Label with default styling"
return fh.Label(*c, cls=('uk-form-label',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
class LabelT(VEnum):
def _generate_next_value_(name, start, count, last_values): return str2ukcls('label', name)
primary = auto()
secondary = auto()
destructive = auto()
# %% ../nbs/02_franken.ipynb
def Label(*c, # contents of Label tag (often text)
cls=(), # Classes in addition to Label styling
**kwargs # Additional args for Label tag
)->FT: # Label(..., cls='uk-label')
"FrankenUI labels, which look like pills"
return fh.Label(*c, cls=('uk-label',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def UkFormSection(title, description, *c, button_txt='Update', outer_margin=6, inner_margin=6):
"A form section with a title, description and optional button"
return Div(cls=f'space-y-{inner_margin} py-{outer_margin}')(
Div(H3(title), P(description, cls=TextPresets.muted_sm)),
DividerSplit(), *c,
Div(Button(button_txt, cls=ButtonT.primary)) if button_txt else None)
# %% ../nbs/02_franken.ipynb
def GenericLabelInput(
label:str|FT, # FormLabel content (often text)
lbl_cls='', # Additional classes for FormLabel
input_cls='', # Additional classes for user input (Input, Select, etc)
container=Div, # Container to wrap label and input in (default is Div)
cls='', # Classes on container (default is '')
id=None, # id for label and input (`id`, `name` and `for` attributes are set to this value). If `label` is str, this defaults to `label.lower()`
input_fn=noop, # User input FT component
**kwargs # Additional args for user input
):
"`Div(Label,Input)` component with Uk styling injected appropriately. Generally you should higher level API, such as `LabelInput` which is created for you in this library"
if not id and isinstance(label, str): id = label.lower()
if isinstance(label, str) or label.tag != 'label':
label = FormLabel(cls=stringify(lbl_cls), fr=id)(label)
inp = input_fn(id=id, cls=stringify(input_cls), **kwargs)
if container: return container(label, inp, cls=stringify(cls))
return label, inp
# %% ../nbs/02_franken.ipynb
def LabelInput(label:str|FT, # FormLabel content (often text)
lbl_cls='', # Additional classes for `FormLabel`
input_cls='', # Additional classes for `Input`
cls='space-y-2', # Classes on container (default is `'space-y-2'` to prevent scrunched up form elements)
id='', # id for `FormLabel` and `Input` (`id`, `name` and `for` attributes are set to this value)
**kwargs # Additional args for `Input`
)->FT: # Div(cls='space-y-2')(`FormLabel`, `Input`)
"A `FormLabel` and `Input` pair that provides default spacing and links/names them based on id"
return GenericLabelInput(label=label, lbl_cls=lbl_cls, input_cls=input_cls,
container=Div, cls=cls, id=id, input_fn=Input, **kwargs)
# %% ../nbs/02_franken.ipynb
def LabelTextArea(label:str|FT, # FormLabel content (often text)
value='', # Value for the textarea
lbl_cls='', # Additional classes for `FormLabel`
input_cls='', # Additional classes for `TextArea`
cls='space-y-2', # Classes on container (default is `'space-y-2'` to prevent scrunched up form elements)
id='', # id for `FormLabel` and `TextArea` (`id`, `name` and `for` attributes are set to this value)
**kwargs # Additional args for `TextArea`
)->FT: # Div(cls='space-y-2')(`FormLabel`, `TextArea`)
def text_area_with_value(**kw): return TextArea(value, **kw)
return GenericLabelInput(label=label, lbl_cls=lbl_cls, input_cls=input_cls,
container=Div, cls=cls, id=id, input_fn=text_area_with_value, **kwargs)
# %% ../nbs/02_franken.ipynb
@delegates(GenericLabelInput, but=['input_fn','cls'])
def LabelSwitch(label:str|FT, # FormLabel content (often text)
lbl_cls='', # Additional classes for `FormLabel`
input_cls='', # Additional classes for `Switch`
cls='space-x-2', # Classes on container (default is `'space-x-2'` to prevent scrunched up form elements)
id='', # id for `FormLabel` and `Switch` (`id`, `name` and `for` attributes are set to this value)
**kwargs # Additional args for `Switch`
)->FT: # Div(cls='space-y-2')(`FormLabel`, `Switch`)
return GenericLabelInput(label=label, lbl_cls=lbl_cls, input_cls=input_cls,
container=Div, cls=cls, id=id, input_fn=Switch, **kwargs)
# %% ../nbs/02_franken.ipynb
def LabelRadio(label:str|FT, # FormLabel content (often text)
lbl_cls='', # Additional classes for `FormLabel`
input_cls='', # Additional classes for `Radio`
container=Div, # Container to wrap label and input in (default is Div)
cls='flex items-center space-x-2', # Classes on container (default is 'flex items-center space-x-2')
id='', # id for `FormLabel` and `Radio` (`id`, `name` and `for` attributes are set to this value)
**kwargs # Additional args for `Radio`
)->FT: # Div(cls='flex items-center space-x-2')(`FormLabel`, `Radio`)
"A FormLabel and Radio pair that provides default spacing and links/names them based on id"
if isinstance(label, str) or label.tag != 'label':
label = FormLabel(cls=stringify(lbl_cls), fr=id)(label)
inp = Radio(id=id, cls=stringify(input_cls), **kwargs)
if container: return container(inp, label, cls=stringify(cls))
return inp, label
# %% ../nbs/02_franken.ipynb
def LabelCheckboxX(label:str|FT, # FormLabel content (often text)
lbl_cls='', # Additional classes for `FormLabel`
input_cls='', # Additional classes for `CheckboxX`
container=Div, # Container to wrap label and input in (default is Div)
cls='flex items-center space-x-2', # Classes on container (default is 'flex items-center space-x-2')
id='', # id for `FormLabel` and `CheckboxX` (`id`, `name` and `for` attributes are set to this value)
**kwargs # Additional args for `CheckboxX`
)->FT: # Div(cls='flex items-center space-x-2')(`FormLabel`, `CheckboxX`)
"A FormLabel and CheckboxX pair that provides default spacing and links/names them based on id"
if not id: id = fh.unqid()
if isinstance(label, str) or label.tag != 'label':
label = FormLabel(cls=stringify(lbl_cls), fr=id)(label)
inp = CheckboxX(id=id, cls=stringify(input_cls), **kwargs)
if container: return container(inp, label, cls=stringify(cls))
return inp, label
# %% ../nbs/02_franken.ipynb
def Options(*c, # Content for an `Option`
selected_idx:int=None, # Index location of selected `Option`
disabled_idxs:set=None # Idex locations of disabled `Options`
):
"Helper function to wrap things into `Option`s for use in `Select`"
return [fh.Option(o,selected=i==selected_idx, disabled=disabled_idxs and i in disabled_idxs) for i,o in enumerate(c)]
# %% ../nbs/02_franken.ipynb
def Select(*option, # Options for the select dropdown (can use `Options` helper function to create)
inp_cls=(), # Additional classes for the select input
cls=('h-10',), # Classes for the outer div (default h-10 for consistent height)
cls_custom='button: uk-input-fake; dropdown: w-full', # Classes for the Uk_Select web component
id="", # ID for the select input
name="", # Name attribute for the select input
placeholder="", # Placeholder text for the select input
searchable=False, # Whether the select should be searchable
insertable=False, # Whether to allow user-defined options to be added
select_kwargs=None, # Additional Arguments passed to Select
**kwargs # Additional arguments passed to Uk_select
):
"Creates a select dropdown with uk styling and option for adding a search box"
inp_cls, cls, cls_custom= map(stringify, (inp_cls, cls, cls_custom))
select_kwargs = ifnone(select_kwargs, {})
if 'hx_trigger' not in kwargs: kwargs['hx_trigger']=''
if 'change' in kwargs['hx_trigger']:
if not id: id = fh.unqid()
kwargs['hx_trigger'] = kwargs['hx_trigger'].replace('changed', f'uk-select:input from:#{id}')
kwargs['hx_trigger'] = kwargs['hx_trigger'].replace('change', f'uk-select:input from:#{id}')
if 'delay' not in kwargs['hx_trigger']:
kwargs['hx_trigger'] += ' delay:100ms'
if 'hx_include' not in kwargs: kwargs['hx_include']=''
kwargs['hx_include'] += ' this'
kwargs['hx_include'] = kwargs['hx_include'].strip()
if id and not name: name = id
uk_select = Uk_select(fh.Select(*option, hidden=True,
**select_kwargs,
),
cls_custom=cls_custom,
searchable=searchable,
placeholder=placeholder,
insertable=insertable,
cls=inp_cls,
id=id,
name=name,
**kwargs
)
return Div(cls=cls)(uk_select)
# %% ../nbs/02_franken.ipynb
def LabelSelect(*option, # Options for the select dropdown (can use `Options` helper function to create)
label=(), # String or FT component for the label
lbl_cls=(), # Additional classes for the label
inp_cls=(), # Additional classes for the select input
cls=('space-y-2',), # Classes for the outer div
id="", # ID for the select input
name="", # Name attribute for the select input
placeholder="", # Placeholder text for the select input
searchable=False, # Whether the select should be searchable
select_kwargs=None, # Additional Arguments passed to Select
**kwargs): # Additional arguments passed to Select
"A FormLabel and Select pair that provides default spacing and links/names them based on id"
lbl_cls, inp_cls, cls = map(stringify, (lbl_cls, inp_cls, cls))
select_kwargs = ifnone(select_kwargs, {})
if label:
lbl = FormLabel(cls=f'{lbl_cls}', fr=id)(label)
select = Select(*option, inp_cls=inp_cls, id=id, name=name if name else id,
placeholder=placeholder, searchable=searchable, select_kwargs=select_kwargs, **kwargs)
return Div(cls=cls)(lbl, select) if label else Div(cls=cls)(select)
# %% ../nbs/02_franken.ipynb
@delegates(GenericLabelInput, but=['input_fn','cls'])
def LabelRange(label:str|FT, # FormLabel content (often text)
lbl_cls='', # Additional classes for `FormLabel`
input_cls='', # Additional classes for `Range`
cls='space-y-6', # Classes on container (default is `'space-y-2'` to prevent scrunched up form elements)
id='', # id for `FormLabel` and `Range` (`id`, `name` and `for` attributes are set to this value)
value='', # Value for the range input
min=None, # Minimum value
max=None, # Maximum value
step=None, # Step size
label_range=True, # Whether to show the range value label (label for the `Range` component)
**kwargs # Additional args for `Range`
)->FT: # Div(cls='space-y-2')(`FormLabel`, `Range`)
"A FormLabel and Range pair that provides default spacing and links/names them based on id"
def range_with_value(**kw):
return Div(Range(value=value, min=min, max=max, step=step, label=label_range, **kw))
return GenericLabelInput(label=label, lbl_cls=lbl_cls, input_cls=input_cls,
container=Div, cls=cls, id=id, input_fn=range_with_value, **kwargs)
# %% ../nbs/02_franken.ipynb
class AT(VEnum):
'Link styles from https://franken-ui.dev/docs/link'
def _generate_next_value_(name, start, count, last_values): return str2ukcls('link', name)
muted = auto()
text = auto()
reset = auto()
primary = 'uk-link text-primary hover:text-primary-focus underline'
classic = 'text-blue-600 hover:text-blue-800 underline'
# %% ../nbs/02_franken.ipynb
class ListT(VEnum):
'List styles using Tailwind CSS'
disc = 'list-disc list-inside'
circle = 'list-[circle] list-inside'
square = 'list-[square] list-inside'
decimal = 'uk-list uk-list-decimal'
hyphen = 'uk-list uk-list-hyphen'
bullet = 'uk-list uk-list-bullet'
divider = 'uk-list uk-list-divider'
striped = 'uk-list uk-list-striped'
# %% ../nbs/02_franken.ipynb
def ModalContainer(*c, # Components to put in the modal (often `ModalDialog`)
cls=(), # Additional classes on the `ModalContainer`
**kwargs # Additional args for `Div` tag
)->FT: # Div(..., cls='uk-modal uk-modal-container')
"Creates a modal container that components go in"
return fh.Div(*c, cls=('uk-modal uk-modal-container',stringify(cls)), data_uk_modal=True, **kwargs)
def ModalDialog(*c, # Components to put in the `ModalDialog` (often `ModalBody`, `ModalHeader`, etc)
cls=(), # Additional classes on the `ModalDialog`
**kwargs # Additional args for `Div` tag
)->FT: # Div(..., cls='uk-modal-dialog')
"Creates a modal dialog"
return fh.Div(*c, cls=('uk-modal-dialog', stringify(cls)), **kwargs)
def ModalHeader(*c, # Components to put in the `ModalHeader`
cls=(), # Additional classes on the `ModalHeader`
**kwargs # Additional args for `Div` tag
)->FT: # Div(..., cls='uk-modal-header')
"Creates a modal header"
return fh.Div(*c, cls=('uk-modal-header', stringify(cls)), **kwargs)
def ModalBody(*c, # Components to put in the `ModalBody` (often forms, sign in buttons, images, etc.)
cls=(), # Additional classes on the `ModalBody`
**kwargs # Additional args for `Div` tag
)->FT: # Div(..., cls='uk-modal-body')
"Creates a modal body"
return fh.Div(*c, cls=('uk-modal-body', stringify(cls)), **kwargs)
def ModalFooter(*c, # Components to put in the `ModalFooter` (often buttons)
cls=(), # Additional classes on the `ModalFooter`
**kwargs # Additional args for `Div` tag
)->FT: # Div(..., cls='uk-modal-footer')
"Creates a modal footer"
return fh.Div(*c, cls=('uk-modal-footer', stringify(cls)), **kwargs)
def ModalTitle(*c, # Components to put in the `ModalTitle` (often text)
cls=(), # Additional classes on the `ModalTitle`
**kwargs # Additional args for `H2` tag
)->FT: # H2(..., cls='uk-modal-title')
"Creates a modal title"
return fh.H2(*c, cls=('uk-modal-title', stringify(cls)), **kwargs)
def ModalCloseButton(*c, # Components to put in the button (often text and/or an icon)
cls="absolute top-3 right-3", # Additional classes on the button
**kwargs # Additional args for `Button` tag
)->FT: # Button(..., cls='uk-modal-close')
"Creates a button that closes a modal with js"
cls = (stringify(cls), 'uk-modal-close')
kwargs['data-uk-close'] = True
return Button(*c, cls=(stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Modal(*c, # Components to put in the `ModalBody` (often forms, sign in buttons, images, etc.)
header=None, # Components that go in the `ModalHeader` (often a `ModalTitle`)
footer=None, # Components that go in the `ModalFooter` (often a `ModalCloseButton`)
cls=(), # Additional classes on the outermost `ModalContainer`
dialog_cls=(), # Additional classes on the `ModalDialog`
header_cls='p-6', # Additional classes on the `ModalHeader`
body_cls='space-y-6', # Additional classes on the `ModalBody`
footer_cls=(), # Additional classes on the `ModalFooter`
id='', # id for the outermost container
hx_init=False, # Initialize modal with UIKit on load (used for modals added to the DOM by HTMX)
hx_open=False, # Open modal on load (used for modals added to the DOM by HTMX)
**kwargs # Additional args for the outermost `Div` tag
)->FT: # Fully styled modal FT Component
"Creates a modal with the appropriate classes to put the boilerplate in the appropriate places for you"
if not id: id = fh.unqid()
if hx_open: kwargs["hx_on__load"] = f"UIkit.modal('#{id}').show()"
if hx_init and not hx_open: kwargs["hx_on__load"] = f"UIkit.modal('#{id}')"
if hx_open or hx_init: kwargs["hx-on:hidden"] = "this.remove()"
cls, dialog_cls, header_cls, body_cls, footer_cls = map(stringify, (cls, dialog_cls, header_cls, body_cls, footer_cls))
res = []
if header: res.append(ModalHeader(cls=header_cls)(header))
res.append(ModalBody(cls=body_cls)(*c))
if footer: res.append(ModalFooter(cls=footer_cls)(footer))
return ModalContainer(ModalDialog(*res, cls=dialog_cls), cls=cls, id=id, **kwargs)
# %% ../nbs/02_franken.ipynb
def Placeholder(*c, # Components to put in the placeholder
cls=(), # Additional classes on the placeholder
**kwargs # Additional args for `Div` tag
)->FT: # Div(..., cls='uk-placeholder')
"Creates a placeholder"
return fh.Div(*c, cls=('uk-placeholder',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Progress(*c, # Components to put in the progress bar (often nothing)
cls=(), # Additional classes on the progress bar
value="", # Value of the progress bar
max="100", # Max value of the progress bar (defaults to 100 for percentage)
**kwargs # Additional args for `Progress` tag
)->FT: # Progress(..., cls='uk-progress')
"Creates a progress bar"
return fh.Progress(*c, value=value, max=max, cls=('uk-progress',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def UkIcon(icon:str, # Icon name from [lucide icons](https://lucide.dev/icons/)
height:int=None,
width:int=None,
stroke_width:int=None, # Thickness of lines
cls=(), # Additional classes on the `Uk_icon` tag
**kwargs # Additional args for `Uk_icon` tag
)->FT: # a lucide icon of the specified size
"Creates an icon using lucide icons"
return Uk_icon(icon=icon, height=height, width=width, stroke_width=stroke_width, cls=cls, **kwargs)
# %% ../nbs/02_franken.ipynb
def UkIconLink(icon:str, # Icon name from [lucide icons](https://lucide.dev/icons/)
height:int=None,
width:int=None,
stroke_width:int=None, # Thickness of lines
cls=(), # Additional classes on the icon
button:bool=False, # Whether to use a button (defaults to a link)
**kwargs # Additional args for `A` or `Button` tag
)->FT: # a lucide icon button or link of the specified size
"Creates an icon link using lucide icons"
fn = fh.Button if button else fh.A
return fn(cls=(f"uk-icon-{'button' if button else 'link'}", stringify(cls)), **kwargs)(
UkIcon(icon=icon, height=height, width=width, stroke_width=stroke_width))
# %% ../nbs/02_franken.ipynb
def DiceBearAvatar(seed_name:str, # Seed name (ie 'Isaac Flath')
h:int=20, # Height
w:int=20, # Width
): # Span with Avatar
"Creates an Avatar using https://dicebear.com/"
url = 'https://api.dicebear.com/8.x/lorelei/svg?seed='
return Span(cls=f"relative flex h-{h} w-{w} shrink-0 overflow-hidden rounded-full bg-secondary")(
fh.Img(cls=f"aspect-square h-{h} w-{w}", alt="Avatar", loading="lazy", src=f"{url}{seed_name}"))
# %% ../nbs/02_franken.ipynb
def Center(*c, # Components to center
vertical:bool=True, # Whether to center vertically
horizontal:bool=True, # Whether to center horizontally
cls=(), # Additional classes
**kwargs # Additional args for container div
)->FT: # Div with centered contents
"Centers contents both vertically and horizontally by default"
classes = ['flex']
if vertical: classes.append('items-center min-h-full')
if horizontal: classes.append('justify-center min-w-full')
return fh_comp.Center(*c, cls=(stringify(classes), stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
class FlexT(VEnum):
'Flexbox modifiers using Tailwind CSS'
def _generate_next_value_(name, start, count, last_values): return name
# Display
block = 'flex'
inline = 'inline-flex'
# Horizontal Alignment
left = 'justify-start'
center = 'justify-center'
right = 'justify-end'
between = 'justify-between'
around = 'justify-around'
# Vertical Alignment
stretch = 'items-stretch'
top = 'items-start'
middle = 'items-center'
bottom = 'items-end'
# Direction
row = 'flex-row'
row_reverse = 'flex-row-reverse'
column = 'flex-col'
column_reverse = 'flex-col-reverse'
# Wrap
nowrap = 'flex-nowrap'
wrap = 'flex-wrap'
wrap_reverse = 'flex-wrap-reverse'
# %% ../nbs/02_franken.ipynb
def Grid(*div, # `Div` components to put in the grid
cols_min:int=1, # Minimum number of columns at any screen size
cols_max:int=4, # Maximum number of columns allowed at any screen size
cols_sm:int=None, # Number of columns on small screens
cols_md:int=None, # Number of columns on medium screens
cols_lg:int=None, # Number of columns on large screens
cols_xl:int=None, # Number of columns on extra large screens
cols:int=None, # Number of columns on all screens
cls='gap-4', # Additional classes on the grid (tip: `gap` provides spacing for grids)
**kwargs # Additional args for `Div` tag
)->FT: # Responsive grid component
"Creates a responsive grid layout with smart defaults based on content"
if cols: cols_min = cols_sm = cols_md = cols_lg = cols_xl = cols
else:
n = len(div)
cols_max = min(n, cols_max)
cols_sm = cols_sm or min(n, cols_min, cols_max)
cols_md = cols_md or min(n, cols_min+1, cols_max)
cols_lg = cols_lg or min(n, cols_min+2, cols_max)
cols_xl = cols_xl or cols_max
return Div(cls=(f'grid grid-cols-{cols_min} sm:grid-cols-{cols_sm} md:grid-cols-{cols_md} lg:grid-cols-{cols_lg} xl:grid-cols-{cols_xl}', stringify(cls)), **kwargs)(*div)
# %% ../nbs/02_franken.ipynb
def DivFullySpaced(*c, # Components
cls='w-full',# Classes for outer div (`w-full` makes it use all available width)
**kwargs # Additional args for outer div
): # Div with spaced components via flex classes
"Creates a flex div with it's components having as much space between them as possible"
cls = stringify(cls)
return Div(cls=(FlexT.block,FlexT.between,FlexT.middle,cls), **kwargs)(*c)
# %% ../nbs/02_franken.ipynb
def DivCentered(*c, # Components
cls='space-y-4', # Classes for outer div (`space-y-4` provides spacing between components)
vstack=True, # Whether to stack the components vertically
**kwargs # Additional args for outer div
)->FT: # Div with components centered in it
"Creates a flex div with it's components centered in it"
cls=stringify(cls)
return Div(cls=(FlexT.block,(FlexT.column if vstack else FlexT.row),FlexT.middle,FlexT.center,cls),**kwargs)(*c)
# %% ../nbs/02_franken.ipynb
def DivLAligned(*c, # Components
cls='space-x-4', # Classes for outer div
**kwargs # Additional args for outer div
)->FT: # Div with components aligned to the left
"Creates a flex div with it's components aligned to the left"
cls=stringify(cls)
return Div(cls=(FlexT.block,FlexT.left,FlexT.middle,cls), **kwargs)(*c)
# %% ../nbs/02_franken.ipynb
def DivRAligned(*c, # Components
cls='space-x-4', # Classes for outer div
**kwargs # Additional args for outer div
)->FT: # Div with components aligned to the right
"Creates a flex div with it's components aligned to the right"
cls=stringify(cls)
return Div(cls=(FlexT.block,FlexT.right,FlexT.middle,cls), **kwargs)(*c)
# %% ../nbs/02_franken.ipynb
def DivVStacked(*c, # Components
cls='space-y-4', # Additional classes on the div (tip: `space-y-4` provides spacing between components)
**kwargs # Additional args for the div
)->FT: # Div with components stacked vertically
"Creates a flex div with it's components stacked vertically"
cls=stringify(cls)
return Div(cls=(FlexT.block,FlexT.column,FlexT.middle,cls), **kwargs)(*c)
# %% ../nbs/02_franken.ipynb
def DivHStacked(*c, # Components
cls='space-x-4', # Additional classes on the div (`space-x-4` provides spacing between components)
**kwargs # Additional args for the div
)->FT: # Div with components stacked horizontally
"Creates a flex div with it's components stacked horizontally"
cls=stringify(cls)
return Div(cls=(FlexT.block,FlexT.row,FlexT.middle,cls), **kwargs)(*c)
# %% ../nbs/02_franken.ipynb
class NavT(VEnum):
def _generate_next_value_(name, start, count, last_values): return str2ukcls('nav', name)
default = auto()
primary = auto()
secondary = auto()
# %% ../nbs/02_franken.ipynb
def NavContainer(*li, # List items are navigation elements (Special `Li` such as `NavParentLi`, `NavDividerLi`, `NavHeaderLi`, `NavSubtitle`, `NavCloseLi` can also be used)
cls=NavT.primary, # Additional classes on the nav
parent=True, # Whether this nav is a *parent* or *sub* nav
uk_nav=False, #True for default collapsible behavior, see [frankenui docs](https://franken-ui.dev/docs/nav#component-options) for more advanced options
uk_scrollspy_nav=False, # Activates scrollspy linking each item `A` tags `href` to content's `id` attribute
sticky=False, # Whether to stick to the top of the page while scrolling
**kwargs # Additional args
)->FT: # FT Component that is a list of `Li` styled for a sidebar navigation menu
"Creates a navigation container (useful for creating a sidebar navigation). A Nav is a list (NavBar is something different)"
_uk_scrollspy_nav = False
if uk_scrollspy_nav:
if isinstance(uk_scrollspy_nav, bool): _uk_scrollspy_nav = 'closest: li; scroll: true' if uk_scrollspy_nav else False
else: _uk_scrollspy_nav = uk_scrollspy_nav
_sticky = 'float-left sticky top-4 hidden md:block' if sticky else ''
return fh.Ul(*li, uk_nav=uk_nav, cls=(f"uk-nav{'' if parent else '-sub'}", stringify(cls), _sticky), uk_scrollspy_nav=_uk_scrollspy_nav, **kwargs)
# %% ../nbs/02_franken.ipynb
def NavParentLi(*nav_container, # `NavContainer` container for a nested nav with `parent=False`)
cls=(), # Additional classes on the li
**kwargs # Additional args for the li
)->FT: # Navigation list item
"Creates a navigation list item with a parent nav for nesting"
return fh.Li(*nav_container, cls=('uk-parent', stringify(cls)),**kwargs)
def NavDividerLi(*c, # Components
cls=(), # Additional classes on the li
**kwargs # Additional args for the li
)->FT: # Navigation list item with a divider
"Creates a navigation list item with a divider"
return fh.Li(*c, cls=('uk-nav-divider', stringify(cls)),**kwargs)
def NavHeaderLi(*c, # Components
cls=(), # Additional classes on the li
**kwargs # Additional args for the li
)->FT: # Navigation list item with a header
"Creates a navigation list item with a header"
return fh.Li(*c, cls=('uk-nav-header', stringify(cls)),**kwargs)
def NavSubtitle(*c, # Components
cls=TextPresets.muted_sm, # Additional classes on the div
**kwargs # Additional args for the div
)->FT: # Navigation subtitle
"Creates a navigation subtitle"
return fh.Div(*c, cls=('uk-nav-subtitle', stringify(cls)),**kwargs)
def NavCloseLi(*c, # Components
cls=(), # Additional classes on the li
**kwargs # Additional args for the li
)->FT: # Navigation list item with a close button
"Creates a navigation list item with a close button"
return fh.Li(*c, cls=('uk-drop-close', stringify(cls)),**kwargs)
# %% ../nbs/02_franken.ipynb
class ScrollspyT(VEnum):
underline = 'navbar-underline'
bold = 'navbar-bold'
# %% ../nbs/02_franken.ipynb
def NavBar(*c, # Component for right side of navbar (Often A tag links)
brand=H3("Title"), # Brand/logo component for left side
right_cls='items-center space-x-4', # Spacing for desktop links
mobile_cls='space-y-4', # Spacing for mobile links
sticky:bool=False, # Whether to stick to the top of the page while scrolling
uk_scrollspy_nav:bool|str=False, # Whether to use scrollspy for navigation
cls='p-4', # Classes for navbar
scrollspy_cls=ScrollspyT.underline, # Scrollspy class (usually ScrollspyT.*)
menu_id=None, # ID for menu container (used for mobile toggle)
)->FT: # Responsive NavBar
"Creates a responsive navigation bar with mobile menu support"
if menu_id is None: menu_id = fh.unqid()
sticky_cls = 'sticky top-4 bg-base-100/80 backdrop-blur-sm z-50' if sticky else ''
if uk_scrollspy_nav == True: uk_scrollspy_nav = 'closest: a; scroll: true'
mobile_icon = A(UkIcon("menu", width=30, height=30), cls="md:hidden", data_uk_toggle=f"target: #{menu_id}; cls: hidden")
return Div(
Div(
DivFullySpaced(
brand, # Brand/logo component for left side
mobile_icon, # Hamburger menu icon
Div(*c,cls=(stringify(right_cls),'hidden md:flex'), uk_scrollspy_nav=uk_scrollspy_nav)),# Desktop Navbar (right side)
cls=('monster-navbar', stringify(cls), stringify(scrollspy_cls))
),
DivCentered(*c,
cls=(stringify(mobile_cls),stringify(cls), stringify(scrollspy_cls),
'hidden md:hidden monster-navbar'),
id=menu_id, uk_scrollspy_nav=uk_scrollspy_nav),
cls=sticky_cls)
# %% ../nbs/02_franken.ipynb
def SliderContainer(
*c, # Components
cls='', # Additional classes on the container
uk_slider=True, # See FrankenUI Slider docs for more options
**kwargs # Additional args for the container
) -> FT: # Div(..., cls='relative', uk_slider=True, ...)
"Creates a slider container"
return Div(*c, cls=('relative', stringify(cls)), uk_slider=uk_slider, **kwargs)
# %% ../nbs/02_franken.ipynb
def SliderItems(
*c, # Components
cls='', # Additional classes for the items
**kwargs # Additional args for the items
) -> FT: # Div(..., cls='uk-slider-items uk-grid', ...)
"Creates a slider items container"
return Div(*c, cls=('uk-slider-items uk-grid', stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def SliderNav(
cls='uk-position-small uk-hidden-hover', # Additional classes for the navigation
prev_cls='absolute left-0 top-1/2 -translate-y-1/2', # Additional classes for the previous navigation
next_cls='absolute right-0 top-1/2 -translate-y-1/2', # Additional classes for the next navigation
**kwargs # Additional args for the navigation
) -> FT: # Left and right navigation arrows for Slider component
"Navigation arrows for Slider component"
return (
fh.A(cls=(prev_cls, stringify(cls)), href='',
uk_slidenav_previous=True, uk_slider_item='previous', **kwargs),
fh.A(cls=(next_cls, stringify(cls)), href='',
uk_slidenav_next=True, uk_slider_item='next', **kwargs)
)
# %% ../nbs/02_franken.ipynb
def Slider(*c, # Items to show in slider
cls='', # Classes for slider container
items_cls='gap-4', # Classes for items container
nav=True, # Whether to show navigation arrows
nav_cls='', # Classes for navigation arrows
**kwargs # Additional args for slider container
) -> FT: # SliderContainer(SliderItems(..., cls='gap-4'), SliderNav?)
"Creates a slider with optional navigation arrows"
nav_comp = SliderNav(cls=nav_cls) if nav else ()
return SliderContainer(
SliderItems(*c, cls=items_cls),
*nav_comp,
cls=cls,
**kwargs
)
# %% ../nbs/02_franken.ipynb
def DropDownNavContainer(*li, # Components
cls=NavT.primary, # Additional classes on the nav
parent=True, # Whether to use a parent nav
uk_nav=False, #True for default collapsible behavior, see https://franken-ui.dev/docs/nav#component-options for more advanced options
uk_dropdown=True, # Whether to use a dropdown
**kwargs # Additional args for the nav
)->FT: # DropDown nav container
"A Nav that is part of a DropDown"
return Div(cls = 'uk-drop uk-dropdown',uk_dropdown=uk_dropdown)(NavContainer(*li, cls=('uk-dropdown-nav',stringify(cls)), uk_nav=uk_nav, parent=parent, **kwargs))
# %% ../nbs/02_franken.ipynb
def TabContainer(*li, # Components
cls='', # Additional classes on the `Ul`
alt=False, # Whether to use an alternative tab style
**kwargs # Additional args for the `Ul`
)->FT: # Tab container
"A TabContainer where children will be different tabs"
cls = stringify(cls)
return Ul(cls=(f"uk-tab{'-alt' if alt else ''}",stringify(cls)),**kwargs)(*li)
# %% ../nbs/02_franken.ipynb
class CardT(VEnum):
'Card styles from UIkit'
def _generate_next_value_(name, start, count, last_values): return str2ukcls('card', name)
default = auto()
primary = auto()
secondary = auto()
destructive = auto()
hover = 'uk-card hover:shadow-lg hover:-translate-y-1 transition-all duration-200'
# %% ../nbs/02_franken.ipynb
def CardTitle(*c, # Components (often a string)
cls=(), # Additional classes on the div
**kwargs # Additional args for the div
):
"Creates a card title"
return fh.Div(*c, cls=('uk-card-title',stringify(cls)), **kwargs)
def CardHeader(*c, # Components that goes in the header (often a `ModalTitle` and description)
cls=(), # Additional classes on the div
**kwargs # Additional args for the div
)->FT: # Container for the header of a card
"Creates a card header"
return fh.Div(*c, cls=('uk-card-header',stringify(cls)), **kwargs)
def CardBody(*c, # Components that go in the body (Main content of the card such as a form, and image, a signin form, etc.)
cls=(), # Additional classes on the div
**kwargs # Additional args for the div
)->FT: # Container for the body of a card
"Creates a card body"
return fh.Div(*c, cls=('uk-card-body',stringify(cls)), **kwargs)
def CardFooter(*c, # Components that go in the footer (often a `ModalCloseButton`)
cls=(), # Additional classes on the div
**kwargs # Additional args for the div
)->FT: # Container for the footer of a card
"Creates a card footer"
return fh.Div(*c, cls=('uk-card-footer',stringify(cls)), **kwargs)
def CardContainer(*c, # Components (typically `CardHeader`, `CardBody`, `CardFooter`)
cls=CardT.default, # Additional classes on the div
**kwargs # Additional args for the div
)->FT: # Container for a card
"Creates a card container"
return fh.Div(*c, cls=('uk-card',stringify(cls)), **kwargs)
# %% ../nbs/02_franken.ipynb
def Card(*c, # Components that go in the body (Main content of the card such as a form, and image, a signin form, etc.)
header:FT|Iterable[FT]=None, # Component(s) that goes in the header (often a `ModalTitle` and a subtitle)
footer:FT|Iterable[FT]=None, # Component(s) that goes in the footer (often a `ModalCloseButton`)
body_cls='space-y-6', # classes for the body
header_cls=(), # classes for the header
footer_cls=(), # classes for the footer
cls=(), #class for outermost component
**kwargs # additional arguments for the `CardContainer`
)->FT: # Card component
"Creates a Card with a header, body, and footer"
header_cls, footer_cls, body_cls, cls = map(stringify, (header_cls, footer_cls, body_cls, cls))
res = []
if header: res.append(CardHeader(cls=header_cls)(header))
res.append(CardBody(cls=body_cls)(*c))
if footer: res.append(CardFooter(cls=footer_cls)(footer))
return CardContainer(cls=cls, **kwargs)(*res)
# %% ../nbs/02_franken.ipynb
class TableT(VEnum):
def _generate_next_value_(name, start, count, last_values): return str2ukcls('table', name)
divider = auto()
striped = auto()
hover = auto()
sm = auto()
lg = auto()
justify = auto()
middle = auto()
responsive = auto()
# %% ../nbs/02_franken.ipynb
def Table(*c, # Components (typically `Thead`, `Tbody`, `Tfoot`)
cls=(TableT.middle, TableT.divider, TableT.hover, TableT.sm), # Additional classes on the table
**kwargs # Additional args for the table
)->FT: # Table component
"Creates a table"
return fh.Table(cls=('uk-table', stringify(cls)), *c, **kwargs)
# %% ../nbs/02_franken.ipynb
def _TableCell(Component,
*c, # Components that go in the cell
cls=(), # Additional classes on the cell container
shrink=False, # Whether to shrink the cell
expand=False, # Whether to expand the cell
small=False, # Whether to use a small table
**kwargs # Additional args for the cell
)->FT: # Table cell
"Creates a table cell"
cls = stringify(cls)
if shrink: cls += ' uk-table-shrink'
if expand: cls += ' uk-table-expand'
if small: cls += ' uk-table-small'
return Component(*c,cls=cls, **kwargs)
@delegates(_TableCell, but=['Component'])
def Td(*c,**kwargs): return _TableCell(fh.Td, *c, **kwargs)
@delegates(_TableCell, but=['Component'])
def Th(*c,**kwargs): return _TableCell(fh.Th, *c, **kwargs)
def Tbody(*rows, cls=(), sortable=False, **kwargs): return fh.Tbody(*rows, cls=stringify(cls), uk_sortable=sortable, **kwargs)
# %% ../nbs/02_franken.ipynb
def TableFromLists(header_data:Sequence, # List of header data
body_data:Sequence[Sequence], # List of lists of body data
footer_data=None, # List of footer data
header_cell_render=Th, # Function(content) -> FT that renders header cells
body_cell_render=Td, # Function(key, content) -> FT that renders body cells
footer_cell_render=Td, # Function(key, content) -> FT that renders footer cells
cls=(TableT.middle, TableT.divider, TableT.hover, TableT.sm), # Additional classes on the table
sortable=False, # Whether to use sortable table
**kwargs # Additional args for the table
)->FT: # Table from lists
"Creates a Table from a list of header data and a list of lists of body data"
return Table(
Thead(Tr(*map(header_cell_render, header_data))),
Tbody(*[Tr(*map(body_cell_render, r)) for r in body_data], sortable=sortable),
Tfoot(Tr(*map(footer_cell_render, footer_data))) if footer_data else '',
cls=stringify(cls),
**kwargs)
# %% ../nbs/02_franken.ipynb
def TableFromDicts(header_data:Sequence, # List of header data
body_data:Sequence[dict], # List of dicts of body data
footer_data=None, # List of footer data
header_cell_render=Th, # Function(content) -> FT that renders header cells
body_cell_render=lambda k,v : Td(v), # Function(key, content) -> FT that renders body cells
footer_cell_render=lambda k,v : Td(v), # Function(key, content) -> FT that renders footer cells
cls=(TableT.middle, TableT.divider, TableT.hover, TableT.sm), # Additional classes on the table
sortable=False, # Whether to use sortable table
**kwargs # Additional args for the table
)->FT: # Styled Table
"Creates a Table from a list of header data and a list of dicts of body data"
return Table(
Thead(Tr(*[header_cell_render(h) for h in header_data])),
Tbody(*[Tr(*[body_cell_render(k, r.get(k, '')) for k in header_data]) for r in body_data], sortable=sortable),
Tfoot(Tr(*[footer_cell_render(k, footer_data.get(k.lower(), '')) for k in header_data])) if footer_data else '',
cls=stringify(cls),
**kwargs
)
# %% ../nbs/02_franken.ipynb
franken_class_map = {
'h1': 'uk-h1 text-4xl font-bold mt-12 mb-6',
'h2': 'uk-h2 text-3xl font-bold mt-10 mb-5',
'h3': 'uk-h3 text-2xl font-semibold mt-8 mb-4',
'h4': 'uk-h4 text-xl font-semibold mt-6 mb-3',
# Body text and links
'p': 'text-lg leading-relaxed mb-6',
'a': 'uk-link text-primary hover:text-primary-focus underline',
# Lists with proper spacing
'ul': 'uk-list uk-list-bullet space-y-2 mb-6 ml-6 text-lg',
'ol': 'uk-list uk-list-decimal space-y-2 mb-6 ml-6 text-lg',
'li': 'leading-relaxed',
# Code and quotes
'pre': 'bg-base-200 rounded-lg p-4 mb-6',
'code': 'uk-codespan px-1',
'pre code': 'uk-codespan px-1 block overflow-x-auto',
'blockquote': 'uk-blockquote pl-4 border-l-4 border-primary italic mb-6',
# Tables
'table': 'uk-table uk-table-divider uk-table-hover uk-table-small w-full mb-6',
'th': '!text-left p-2 font-semibold',
'td': 'p-2',
# Other elements
'hr': 'uk-divider-icon my-8',
'img': 'max-w-full h-auto rounded-lg mb-6'
}
# %% ../nbs/02_franken.ipynb
def apply_classes(html_str:str, # Html string
class_map=None, # Class map
class_map_mods=None # Class map that will modify the class map map (for small changes to base map)
)->str: # Html string with classes applied
"Apply classes to html string"
if not html_str: return html_str
# Handles "Unicode strings with encoding declaration are not supported":
if html_str[:100].lstrip().startswith('<?xml'): html_str = html_str.split('?>', 1)[1].strip()
class_map = ifnone(class_map, franken_class_map)
if class_map_mods: class_map = {**class_map, **class_map_mods}
try:
html_str = html.fragment_fromstring(html_str, create_parent=True)
for selector, classes in class_map.items():
# Handle descendant selectors (e.g., 'pre code')
xpath = '//' + '/descendant::'.join(selector.split())
for element in html_str.xpath(xpath):
existing_class = element.get('class', '')
new_class = f"{existing_class} {classes}".strip()
element.set('class', new_class)
return ''.join(etree.tostring(c, encoding='unicode', method='html') for c in html_str)
except (etree.ParserError,ValueError): return html_str
# %% ../nbs/02_franken.ipynb
class FrankenRenderer(HTMLRenderer):
"Custom renderer for Franken UI that handles image paths"
def __init__(self, *args, img_dir=None, **kwargs):
super().__init__(*args, **kwargs)
self.img_dir = img_dir
def render_image(self, token):
"Modify image paths if they're relative and self.img_dir is specified"
template = '<img src="{}" alt="{}"{} class="max-w-full h-auto rounded-lg mb-6">'
title = f' title="{token.title}"' if hasattr(token, 'title') else ''
src = token.src
if self.img_dir and not src.startswith(('http://', 'https://', '/', 'attachment:', 'blob:', 'data:')):
src = f'{pathlib.Path(self.img_dir)}/{src}'
return template.format(src, token.children[0].content if token.children else '', title)
# %% ../nbs/02_franken.ipynb
def render_md(md_content:str, # Markdown content
class_map=None, # Class map
class_map_mods=None, # Additional class map
img_dir:str=None, # Directory containing images
renderer=FrankenRenderer # custom renderer
)->FT: # Rendered markdown
"Renders markdown using mistletoe and lxml with custom image handling"
if md_content=='': return md_content
html_content = mistletoe.markdown(md_content, partial(renderer, img_dir=img_dir))
return NotStr(apply_classes(html_content, class_map, class_map_mods))
# %% ../nbs/02_franken.ipynb
def ThemePicker(color=True, radii=True, shadows=True, font=True, mode=True, cls='p-4', custom_themes=[]):
"Theme picker component with configurable sections"
def _opt(val, txt, **kwargs): return Option(txt, value=val, **kwargs)
def _optgrp(key, lbl, opts): return fh.Optgroup(data_key=key, label=lbl)(*opts)
groups = []
if color: groups.append(_optgrp('theme', 'Theme', [
_opt('uk-theme-zinc', 'Zinc', data_hex='#52525b', selected=True),
*[_opt(f'uk-theme-{c.lower()}', c, data_hex=h) for c,h in
[('Slate','#64748b'),('Stone','#78716c'),('Gray','#6b7280'),
('Neutral','#737373'),('Red','#dc2626'),('Rose','#e11d48'),
('Orange','#f97316'),('Green','#16a34a'),('Blue','#2563eb'),
('Yellow','#facc15'),('Violet','#7c3aed'),*custom_themes]]]))
if radii: groups.append(_optgrp('radii', 'Radii', [
_opt('uk-radii-none','None'), _opt('uk-radii-sm','Small'),
_opt('uk-radii-md','Medium',selected=True), _opt('uk-radii-lg','Large')]))
if shadows: groups.append(_optgrp('shadows', 'Shadows', [
_opt('uk-shadows-none','None'), _opt('uk-shadows-sm','Small',selected=True),
_opt('uk-shadows-md','Medium'), _opt('uk-shadows-lg','Large')]))
if font: groups.append(_optgrp('font', 'Font', [
_opt('uk-font-sm','Small',selected=True), _opt('uk-font-base','Default')]))
if mode: groups.append(_optgrp('mode', 'Mode', [
_opt('light','Light',data_icon='sun'), _opt('dark','Dark',data_icon='moon')]))
from fasthtml.components import Uk_theme_switcher
return Div(Uk_theme_switcher(fh.Select(*groups, hidden=True), id="theme-switcher"), cls=stringify(cls))
# %% ../nbs/02_franken.ipynb
def LightboxContainer(*lightboxitem, # `LightBoxItem`s that will be inside lightbox
data_uk_lightbox='counter: true', # See https://franken-ui.dev/docs/2.0/lightbox for advanced options
**kwargs # Additional options for outer container
)->FT: # Lightbox
"Lightbox container that will hold `LightboxItems`"
return fh.Div(*lightboxitem, data_uk_lightbox=data_uk_lightbox, **kwargs)
# %% ../nbs/02_franken.ipynb
def LightboxItem(*c, # Component that when clicked will open the lightbox (often a button)
href, # Href to image, youtube video, vimeo, google maps, etc.
data_alt=None, # Alt text for the lightbox item/image
data_caption=None, # Caption for the item that shows below it
cls='', # Class for the A tag (often nothing or `uk-btn`)
**kwargs # Additional args for the `A` tag
)->FT: # A(... href, data_alt, cls., ...)
"Anchor tag with appropriate structure to go inside a `LightBoxContainer`"
return fh.A(*c, href=href, data_alt=data_alt, cls=stringify(cls), **kwargs)
# %% ../nbs/02_franken.ipynb
def ApexChart(*,
opts:Dict, # ApexChart options used to render your chart (e.g. {"chart":{"type":"line"}, ...})
cls: Enum | str | tuple = (), # Classes for the outer container
**kws, # Additional args for the outer container
)->FT: # Div(Uk_chart(Script(...)))
"Apex chart component"
js=NotStr(f"<script type='application/json'>{json.dumps(opts)}</script>")
return Div(Uk_chart(js), cls=stringify(cls), **kws)