code.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. """A code component."""
  2. from typing import Dict, Optional, Union
  3. from reflex.components.component import Component
  4. from reflex.components.forms import Button
  5. from reflex.components.layout import Box
  6. from reflex.components.libs.chakra import ChakraComponent
  7. from reflex.components.media import Icon
  8. from reflex.event import set_clipboard
  9. from reflex.style import Style
  10. from reflex.utils import imports
  11. from reflex.vars import ImportVar, Var
  12. # Path to the prism styles.
  13. PRISM_STYLES_PATH: str = "/styles/code/prism"
  14. class CodeBlock(Component):
  15. """A code block."""
  16. library = "react-syntax-highlighter@^15.5.0"
  17. tag = "Prism"
  18. # The theme to use ("light" or "dark").
  19. theme: Var[str]
  20. # The language to use.
  21. language: Var[str]
  22. # If this is enabled line numbers will be shown next to the code block.
  23. show_line_numbers: Var[bool]
  24. # The starting line number to use.
  25. starting_line_number: Var[int]
  26. # Whether to wrap long lines.
  27. wrap_long_lines: Var[bool]
  28. # A custom style for the code block.
  29. custom_style: Dict[str, str] = {}
  30. # Props passed down to the code tag.
  31. code_tag_props: Var[Dict[str, str]]
  32. def _get_imports(self) -> imports.ImportDict:
  33. merged_imports = super()._get_imports()
  34. if self.theme is not None:
  35. merged_imports = imports.merge_imports(
  36. merged_imports, {PRISM_STYLES_PATH: {ImportVar(tag=self.theme.name)}}
  37. )
  38. return merged_imports
  39. @classmethod
  40. def create(
  41. cls,
  42. *children,
  43. can_copy: Optional[bool] = False,
  44. copy_button: Optional[Union[bool, Component]] = None,
  45. **props
  46. ):
  47. """Create a text component.
  48. Args:
  49. *children: The children of the component.
  50. can_copy: Whether a copy button should appears.
  51. copy_button: A custom copy button to override the default one.
  52. **props: The props to pass to the component.
  53. Returns:
  54. The text component.
  55. """
  56. # This component handles style in a special prop.
  57. custom_style = props.pop("custom_style", {})
  58. if can_copy:
  59. code = children[0]
  60. copy_button = ( # type: ignore
  61. copy_button
  62. if copy_button is not None
  63. else Button.create(
  64. Icon.create(tag="copy"),
  65. on_click=set_clipboard(code),
  66. style={"position": "absolute", "top": "0.5em", "right": "0"},
  67. )
  68. )
  69. custom_style.update({"padding": "1em 3.2em 1em 1em"})
  70. else:
  71. copy_button = None
  72. # Transfer style props to the custom style prop.
  73. for key, value in props.items():
  74. if key not in cls.get_fields():
  75. custom_style[key] = value
  76. # Create the component.
  77. code_block = super().create(
  78. *children,
  79. **props,
  80. custom_style=Style(custom_style),
  81. )
  82. if copy_button:
  83. return Box.create(code_block, copy_button, position="relative")
  84. else:
  85. return code_block
  86. def _add_style(self, style):
  87. self.custom_style.update(style) # type: ignore
  88. def _render(self):
  89. out = super()._render()
  90. if self.theme is not None:
  91. out.add_props(
  92. style=Var.create(self.theme.name, is_local=False)
  93. ).remove_props("theme")
  94. return out
  95. class Code(ChakraComponent):
  96. """Used to display inline code."""
  97. tag = "Code"