Files
johndoe6345789 c406b8df96 refactor: Reorganize FakeMUI by implementation type
Move FakeMUI content to appropriate root-level folders by implementation:

**React Components → components/fakemui/**
- 537 components (inputs, surfaces, layout, data-display, feedback,
  navigation, utils, atoms, lab, x, email, workflows)
- 416 SVG icons
- Full barrel exports in components/fakemui/index.ts

**QML Components → qml/**
- 104 Material Design 3 components (11 categories)
- 7 hybrid application views
- 8 desktop widgets
- qmldir module registration

**Python Bindings → python/fakemui/**
- 15 PyQt6 modules (120+ components)
- Full Python package structure with pyproject.toml

**SCSS/Styles → fakemui/** (renamed purpose)
- scss/ - Material Design 3 stylesheets
- styles/ - Component SCSS modules
- src/utils/ - Accessibility utilities
- index.ts now re-exports from components/fakemui/

This separation allows:
- React: import { Button } from '@metabuilder/components/fakemui'
- QML: import QmlComponents 1.0
- Python: from fakemui import Button, Card
- Backward compat: import { Button } from '@metabuilder/fakemui'

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 12:59:24 +00:00

931 lines
30 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
FakeMUI X Module for PyQt6
Provides advanced components like DataGrid and DatePicker.
"""
from typing import Any, Callable, Dict, List, Optional, Union
from datetime import date, datetime, time
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QFrame,
QTableWidget, QTableWidgetItem, QHeaderView, QCheckBox,
QComboBox, QPushButton, QLineEdit, QCalendarWidget,
QTimeEdit, QDateEdit, QSpinBox, QScrollArea, QMenu,
QAbstractItemView, QSizePolicy, QGridLayout
)
from PyQt6.QtCore import Qt, pyqtSignal, QDate, QTime
from PyQt6.QtGui import QFont, QColor, QAction
from .base import FakeMUIWidget, StyleMixin
# =============================================================================
# DataGrid
# =============================================================================
class DataGridColumn:
"""Configuration for a DataGrid column."""
def __init__(
self,
field: str,
header_name: Optional[str] = None,
width: Optional[int] = None,
flex: Optional[float] = None,
sortable: bool = True,
filterable: bool = True,
editable: bool = False,
render_cell: Optional[Callable] = None,
value_formatter: Optional[Callable] = None,
):
self.field = field
self.header_name = header_name or field
self.width = width
self.flex = flex
self.sortable = sortable
self.filterable = filterable
self.editable = editable
self.render_cell = render_cell
self.value_formatter = value_formatter
class DataGrid(QFrame, StyleMixin):
"""
A powerful data table component.
Signals:
selection_changed: Emitted when selection changes.
sort_changed: Emitted when sort changes.
row_clicked: Emitted when a row is clicked.
cell_clicked: Emitted when a cell is clicked.
page_changed: Emitted when page changes.
"""
selection_changed = pyqtSignal(list) # List of selected row IDs
sort_changed = pyqtSignal(str, str) # field, direction ('asc'/'desc')
row_clicked = pyqtSignal(dict) # row data
cell_clicked = pyqtSignal(str, object) # field, value
page_changed = pyqtSignal(int) # page number
def __init__(
self,
parent: Optional[QWidget] = None,
rows: Optional[List[Dict]] = None,
columns: Optional[List[DataGridColumn]] = None,
page_size: int = 10,
rows_per_page_options: Optional[List[int]] = None,
pagination: bool = True,
checkbox_selection: bool = False,
disable_selection_on_click: bool = False,
auto_height: bool = False,
density: str = "standard",
loading: bool = False,
get_row_id: Optional[Callable] = None,
):
super().__init__(parent)
StyleMixin.__init__(self)
self._rows = rows or []
self._columns = columns or []
self._page_size = page_size
self._rows_per_page_options = rows_per_page_options or [5, 10, 25, 50]
self._pagination = pagination
self._checkbox_selection = checkbox_selection
self._disable_selection_on_click = disable_selection_on_click
self._auto_height = auto_height
self._density = density
self._loading = loading
self._get_row_id = get_row_id or (lambda row: row.get("id", id(row)))
self._current_page = 0
self._sort_field: Optional[str] = None
self._sort_direction: str = "asc"
self._selected_ids: List[Any] = []
self._setup_ui()
self._apply_styles()
self._refresh_data()
def _setup_ui(self):
"""Set up the UI components."""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# Table
self._table = QTableWidget()
self._table.setAlternatingRowColors(True)
self._table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self._table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self._table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
self._table.verticalHeader().setVisible(False)
self._table.setShowGrid(True)
self._table.horizontalHeader().sectionClicked.connect(self._on_header_clicked)
self._table.cellClicked.connect(self._on_cell_clicked)
layout.addWidget(self._table, 1)
# Footer with pagination
if self._pagination:
self._footer = QFrame()
footer_layout = QHBoxLayout(self._footer)
footer_layout.setContentsMargins(8, 4, 8, 4)
# Rows per page selector
footer_layout.addWidget(QLabel("Rows per page:"))
self._page_size_combo = QComboBox()
for opt in self._rows_per_page_options:
self._page_size_combo.addItem(str(opt), opt)
self._page_size_combo.setCurrentText(str(self._page_size))
self._page_size_combo.currentIndexChanged.connect(self._on_page_size_changed)
footer_layout.addWidget(self._page_size_combo)
footer_layout.addStretch()
# Page info
self._page_info = QLabel()
footer_layout.addWidget(self._page_info)
# Navigation buttons
self._prev_btn = QPushButton("")
self._prev_btn.setFixedWidth(32)
self._prev_btn.clicked.connect(self._prev_page)
footer_layout.addWidget(self._prev_btn)
self._next_btn = QPushButton("")
self._next_btn.setFixedWidth(32)
self._next_btn.clicked.connect(self._next_page)
footer_layout.addWidget(self._next_btn)
layout.addWidget(self._footer)
# Loading overlay
self._loading_label = QLabel("Loading...")
self._loading_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self._loading_label.setStyleSheet("""
QLabel {
background-color: rgba(255, 255, 255, 0.8);
font-size: 14px;
color: #666;
}
""")
self._loading_label.setVisible(False)
def _apply_styles(self):
"""Apply styles based on density."""
row_heights = {
"compact": 36,
"standard": 52,
"comfortable": 64,
}
row_height = row_heights.get(self._density, 52)
self.add_class("fakemui-data-grid")
self.add_class(f"fakemui-data-grid-density-{self._density}")
self.setStyleSheet(f"""
QFrame {{
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
}}
QTableWidget {{
background-color: white;
border: none;
gridline-color: #e0e0e0;
}}
QTableWidget::item {{
padding: 8px;
}}
QHeaderView::section {{
background-color: #fafafa;
padding: 8px;
border: none;
border-bottom: 1px solid #e0e0e0;
font-weight: 500;
}}
QHeaderView::section:hover {{
background-color: #f5f5f5;
}}
QTableWidget::item:selected {{
background-color: rgba(25, 118, 210, 0.08);
color: black;
}}
QTableWidget::item:alternate {{
background-color: #fafafa;
}}
""")
self._table.verticalHeader().setDefaultSectionSize(row_height)
def _refresh_data(self):
"""Refresh the table data."""
# Sort data
sorted_rows = self._rows[:]
if self._sort_field:
reverse = self._sort_direction == "desc"
sorted_rows.sort(
key=lambda r: r.get(self._sort_field, "") or "",
reverse=reverse
)
# Paginate
if self._pagination:
start = self._current_page * self._page_size
end = start + self._page_size
display_rows = sorted_rows[start:end]
else:
display_rows = sorted_rows
# Set up columns
col_count = len(self._columns)
if self._checkbox_selection:
col_count += 1
self._table.setColumnCount(col_count)
headers = []
if self._checkbox_selection:
headers.append("")
for col in self._columns:
header = col.header_name
if col.field == self._sort_field:
header += "" if self._sort_direction == "asc" else ""
headers.append(header)
self._table.setHorizontalHeaderLabels(headers)
# Set up rows
self._table.setRowCount(len(display_rows))
for row_idx, row_data in enumerate(display_rows):
col_offset = 0
row_id = self._get_row_id(row_data)
# Checkbox column
if self._checkbox_selection:
checkbox = QCheckBox()
checkbox.setChecked(row_id in self._selected_ids)
checkbox.stateChanged.connect(
lambda state, rid=row_id: self._on_checkbox_changed(rid, state)
)
self._table.setCellWidget(row_idx, 0, checkbox)
col_offset = 1
# Data columns
for col_idx, col in enumerate(self._columns):
value = row_data.get(col.field, "")
# Apply formatters
if col.render_cell:
display_value = col.render_cell({
"row": row_data,
"value": value,
"field": col.field
})
elif col.value_formatter:
display_value = col.value_formatter({"value": value})
else:
display_value = str(value) if value is not None else ""
item = QTableWidgetItem(str(display_value))
item.setData(Qt.ItemDataRole.UserRole, row_data)
self._table.setItem(row_idx, col_idx + col_offset, item)
# Update pagination info
if self._pagination:
total = len(self._rows)
start = self._current_page * self._page_size + 1
end = min((self._current_page + 1) * self._page_size, total)
self._page_info.setText(f"{start}{end} of {total}")
self._prev_btn.setEnabled(self._current_page > 0)
self._next_btn.setEnabled(end < total)
def _on_header_clicked(self, col_idx: int):
"""Handle header click for sorting."""
if self._checkbox_selection:
if col_idx == 0:
return # Checkbox column
col_idx -= 1
if col_idx < len(self._columns):
col = self._columns[col_idx]
if col.sortable:
if self._sort_field == col.field:
self._sort_direction = "desc" if self._sort_direction == "asc" else "asc"
else:
self._sort_field = col.field
self._sort_direction = "asc"
self.sort_changed.emit(self._sort_field, self._sort_direction)
self._refresh_data()
def _on_cell_clicked(self, row: int, col: int):
"""Handle cell click."""
item = self._table.item(row, col)
if item:
row_data = item.data(Qt.ItemDataRole.UserRole)
if row_data:
self.row_clicked.emit(row_data)
if self._checkbox_selection:
col -= 1
if col >= 0 and col < len(self._columns):
field = self._columns[col].field
value = row_data.get(field)
self.cell_clicked.emit(field, value)
def _on_checkbox_changed(self, row_id: Any, state: int):
"""Handle checkbox state change."""
if state == Qt.CheckState.Checked.value:
if row_id not in self._selected_ids:
self._selected_ids.append(row_id)
else:
if row_id in self._selected_ids:
self._selected_ids.remove(row_id)
self.selection_changed.emit(self._selected_ids)
def _on_page_size_changed(self, index: int):
"""Handle page size change."""
self._page_size = self._page_size_combo.currentData()
self._current_page = 0
self._refresh_data()
def _prev_page(self):
"""Go to previous page."""
if self._current_page > 0:
self._current_page -= 1
self.page_changed.emit(self._current_page)
self._refresh_data()
def _next_page(self):
"""Go to next page."""
max_page = (len(self._rows) - 1) // self._page_size
if self._current_page < max_page:
self._current_page += 1
self.page_changed.emit(self._current_page)
self._refresh_data()
def set_rows(self, rows: List[Dict]):
"""Set the data rows."""
self._rows = rows
self._current_page = 0
self._refresh_data()
def set_columns(self, columns: List[DataGridColumn]):
"""Set the columns."""
self._columns = columns
self._refresh_data()
def get_selected_rows(self) -> List[Dict]:
"""Get selected rows."""
return [
row for row in self._rows
if self._get_row_id(row) in self._selected_ids
]
@property
def loading(self) -> bool:
return self._loading
@loading.setter
def loading(self, value: bool):
self._loading = value
self._loading_label.setVisible(value)
# Aliases for MUI X compatibility
DataGridPro = DataGrid
DataGridPremium = DataGrid
# =============================================================================
# DatePicker
# =============================================================================
class DatePicker(QFrame, StyleMixin):
"""
Date selection component.
Signals:
date_changed: Emitted when date changes.
"""
date_changed = pyqtSignal(object) # date or None
def __init__(
self,
parent: Optional[QWidget] = None,
value: Optional[date] = None,
label: Optional[str] = None,
format_str: str = "MM/dd/yyyy",
min_date: Optional[date] = None,
max_date: Optional[date] = None,
disabled: bool = False,
read_only: bool = False,
error: bool = False,
helper_text: Optional[str] = None,
placeholder: str = "Select date",
clearable: bool = False,
):
super().__init__(parent)
StyleMixin.__init__(self)
self._value = value
self._label_text = label
self._format_str = format_str
self._min_date = min_date
self._max_date = max_date
self._disabled = disabled
self._read_only = read_only
self._error = error
self._helper_text = helper_text
self._placeholder = placeholder
self._clearable = clearable
self._setup_ui()
self._apply_styles()
def _setup_ui(self):
"""Set up the UI components."""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(4)
# Label
if self._label_text:
self._label = QLabel(self._label_text)
self._label.setStyleSheet("font-size: 12px; color: rgba(0,0,0,0.6);")
layout.addWidget(self._label)
# Input container
input_container = QHBoxLayout()
input_container.setSpacing(0)
# Date input
self._date_edit = QDateEdit()
self._date_edit.setCalendarPopup(True)
self._date_edit.setDisplayFormat(self._qt_format())
if self._value:
self._date_edit.setDate(QDate(self._value.year, self._value.month, self._value.day))
if self._min_date:
self._date_edit.setMinimumDate(QDate(
self._min_date.year, self._min_date.month, self._min_date.day
))
if self._max_date:
self._date_edit.setMaximumDate(QDate(
self._max_date.year, self._max_date.month, self._max_date.day
))
self._date_edit.setEnabled(not self._disabled)
self._date_edit.setReadOnly(self._read_only)
self._date_edit.dateChanged.connect(self._on_date_changed)
input_container.addWidget(self._date_edit, 1)
# Clear button
if self._clearable:
self._clear_btn = QPushButton("×")
self._clear_btn.setFixedSize(24, 24)
self._clear_btn.clicked.connect(self._clear_date)
self._clear_btn.setStyleSheet("""
QPushButton {
border: none;
background: transparent;
font-size: 16px;
color: #666;
}
QPushButton:hover {
color: #333;
}
""")
input_container.addWidget(self._clear_btn)
layout.addLayout(input_container)
# Helper text
if self._helper_text:
self._helper = QLabel(self._helper_text)
color = "#d32f2f" if self._error else "rgba(0,0,0,0.6)"
self._helper.setStyleSheet(f"font-size: 12px; color: {color};")
layout.addWidget(self._helper)
def _qt_format(self) -> str:
"""Convert format string to Qt format."""
return self._format_str.replace("MM", "MM").replace("dd", "dd").replace("yyyy", "yyyy")
def _apply_styles(self):
"""Apply styles."""
self.add_class("fakemui-datepicker")
if self._error:
self.add_class("fakemui-datepicker-error")
if self._disabled:
self.add_class("fakemui-datepicker-disabled")
border_color = "#d32f2f" if self._error else "#c4c4c4"
self._date_edit.setStyleSheet(f"""
QDateEdit {{
border: 1px solid {border_color};
border-radius: 4px;
padding: 8px 12px;
background: white;
}}
QDateEdit:focus {{
border-color: #1976d2;
}}
QDateEdit:disabled {{
background: #f5f5f5;
color: #9e9e9e;
}}
""")
def _on_date_changed(self, qdate: QDate):
"""Handle date change."""
self._value = date(qdate.year(), qdate.month(), qdate.day())
self.date_changed.emit(self._value)
def _clear_date(self):
"""Clear the date."""
self._value = None
self.date_changed.emit(None)
@property
def value(self) -> Optional[date]:
return self._value
@value.setter
def value(self, val: Optional[date]):
self._value = val
if val:
self._date_edit.setDate(QDate(val.year, val.month, val.day))
self.date_changed.emit(val)
# =============================================================================
# TimePicker
# =============================================================================
class TimePicker(QFrame, StyleMixin):
"""
Time selection component.
Signals:
time_changed: Emitted when time changes.
"""
time_changed = pyqtSignal(object) # time or None
def __init__(
self,
parent: Optional[QWidget] = None,
value: Optional[time] = None,
label: Optional[str] = None,
ampm: bool = False,
disabled: bool = False,
read_only: bool = False,
error: bool = False,
helper_text: Optional[str] = None,
placeholder: str = "Select time",
):
super().__init__(parent)
StyleMixin.__init__(self)
self._value = value
self._label_text = label
self._ampm = ampm
self._disabled = disabled
self._read_only = read_only
self._error = error
self._helper_text = helper_text
self._placeholder = placeholder
self._setup_ui()
self._apply_styles()
def _setup_ui(self):
"""Set up the UI components."""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(4)
# Label
if self._label_text:
self._label = QLabel(self._label_text)
self._label.setStyleSheet("font-size: 12px; color: rgba(0,0,0,0.6);")
layout.addWidget(self._label)
# Time input
self._time_edit = QTimeEdit()
if self._ampm:
self._time_edit.setDisplayFormat("hh:mm AP")
else:
self._time_edit.setDisplayFormat("HH:mm")
if self._value:
self._time_edit.setTime(QTime(self._value.hour, self._value.minute))
self._time_edit.setEnabled(not self._disabled)
self._time_edit.setReadOnly(self._read_only)
self._time_edit.timeChanged.connect(self._on_time_changed)
layout.addWidget(self._time_edit)
# Helper text
if self._helper_text:
self._helper = QLabel(self._helper_text)
color = "#d32f2f" if self._error else "rgba(0,0,0,0.6)"
self._helper.setStyleSheet(f"font-size: 12px; color: {color};")
layout.addWidget(self._helper)
def _apply_styles(self):
"""Apply styles."""
self.add_class("fakemui-timepicker")
border_color = "#d32f2f" if self._error else "#c4c4c4"
self._time_edit.setStyleSheet(f"""
QTimeEdit {{
border: 1px solid {border_color};
border-radius: 4px;
padding: 8px 12px;
background: white;
}}
QTimeEdit:focus {{
border-color: #1976d2;
}}
QTimeEdit:disabled {{
background: #f5f5f5;
color: #9e9e9e;
}}
""")
def _on_time_changed(self, qtime: QTime):
"""Handle time change."""
self._value = time(qtime.hour(), qtime.minute())
self.time_changed.emit(self._value)
@property
def value(self) -> Optional[time]:
return self._value
@value.setter
def value(self, val: Optional[time]):
self._value = val
if val:
self._time_edit.setTime(QTime(val.hour, val.minute))
self.time_changed.emit(val)
# =============================================================================
# DateTimePicker
# =============================================================================
class DateTimePicker(QFrame, StyleMixin):
"""
Combined date and time picker.
Signals:
datetime_changed: Emitted when datetime changes.
"""
datetime_changed = pyqtSignal(object) # datetime or None
def __init__(
self,
parent: Optional[QWidget] = None,
value: Optional[datetime] = None,
label: Optional[str] = None,
disabled: bool = False,
):
super().__init__(parent)
StyleMixin.__init__(self)
self._value = value
self._label_text = label
self._disabled = disabled
self._setup_ui()
def _setup_ui(self):
"""Set up the UI components."""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(4)
# Label
if self._label_text:
self._label = QLabel(self._label_text)
self._label.setStyleSheet("font-size: 12px; color: rgba(0,0,0,0.6);")
layout.addWidget(self._label)
# Pickers container
pickers = QHBoxLayout()
pickers.setSpacing(8)
# Date picker
self._date_picker = DatePicker(
value=self._value.date() if self._value else None,
disabled=self._disabled
)
self._date_picker.date_changed.connect(self._on_date_changed)
pickers.addWidget(self._date_picker)
# Time picker
self._time_picker = TimePicker(
value=self._value.time() if self._value else None,
disabled=self._disabled
)
self._time_picker.time_changed.connect(self._on_time_changed)
pickers.addWidget(self._time_picker)
layout.addLayout(pickers)
self.add_class("fakemui-datetimepicker")
def _on_date_changed(self, new_date: Optional[date]):
"""Handle date change."""
if new_date:
current_time = self._value.time() if self._value else time(0, 0)
self._value = datetime.combine(new_date, current_time)
else:
self._value = None
self.datetime_changed.emit(self._value)
def _on_time_changed(self, new_time: Optional[time]):
"""Handle time change."""
if new_time:
current_date = self._value.date() if self._value else date.today()
self._value = datetime.combine(current_date, new_time)
self.datetime_changed.emit(self._value)
@property
def value(self) -> Optional[datetime]:
return self._value
@value.setter
def value(self, val: Optional[datetime]):
self._value = val
if val:
self._date_picker.value = val.date()
self._time_picker.value = val.time()
self.datetime_changed.emit(val)
# =============================================================================
# StaticDatePicker (Calendar View)
# =============================================================================
class StaticDatePicker(QFrame, StyleMixin):
"""
A static (always visible) date picker calendar.
Signals:
date_changed: Emitted when date changes.
"""
date_changed = pyqtSignal(object)
def __init__(
self,
parent: Optional[QWidget] = None,
value: Optional[date] = None,
):
super().__init__(parent)
StyleMixin.__init__(self)
self._value = value
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self._calendar = QCalendarWidget()
self._calendar.setGridVisible(True)
if value:
self._calendar.setSelectedDate(QDate(value.year, value.month, value.day))
self._calendar.selectionChanged.connect(self._on_selection_changed)
layout.addWidget(self._calendar)
self.add_class("fakemui-static-datepicker")
self._calendar.setStyleSheet("""
QCalendarWidget {
background: white;
}
QCalendarWidget QToolButton {
color: #333;
background: transparent;
border: none;
padding: 4px 8px;
}
QCalendarWidget QToolButton:hover {
background: #f5f5f5;
border-radius: 4px;
}
QCalendarWidget QWidget#qt_calendar_navigationbar {
background: #fafafa;
}
QCalendarWidget QAbstractItemView:enabled {
selection-background-color: #1976d2;
selection-color: white;
}
""")
def _on_selection_changed(self):
"""Handle selection change."""
qdate = self._calendar.selectedDate()
self._value = date(qdate.year(), qdate.month(), qdate.day())
self.date_changed.emit(self._value)
@property
def value(self) -> Optional[date]:
return self._value
@value.setter
def value(self, val: Optional[date]):
self._value = val
if val:
self._calendar.setSelectedDate(QDate(val.year, val.month, val.day))
self.date_changed.emit(val)
# Aliases
DesktopDatePicker = DatePicker
MobileDatePicker = DatePicker
CalendarPicker = StaticDatePicker
class ClockPicker(QFrame, StyleMixin):
"""
A clock picker widget.
Signals:
time_changed: Emitted when time changes.
"""
time_changed = pyqtSignal(object)
def __init__(
self,
parent: Optional[QWidget] = None,
value: Optional[time] = None,
):
super().__init__(parent)
StyleMixin.__init__(self)
self._value = value
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self._time_picker = TimePicker(value=value)
self._time_picker.time_changed.connect(self._on_time_changed)
layout.addWidget(self._time_picker)
self.add_class("fakemui-clock-picker")
def _on_time_changed(self, new_time: Optional[time]):
"""Handle time change."""
self._value = new_time
self.time_changed.emit(new_time)
@property
def value(self) -> Optional[time]:
return self._value
@value.setter
def value(self, val: Optional[time]):
self._value = val
self._time_picker.value = val
self.time_changed.emit(val)
# =============================================================================
# Exports
# =============================================================================
__all__ = [
# DataGrid
"DataGrid",
"DataGridPro",
"DataGridPremium",
"DataGridColumn",
# Date/Time Pickers
"DatePicker",
"TimePicker",
"DateTimePicker",
"DesktopDatePicker",
"MobileDatePicker",
"StaticDatePicker",
"CalendarPicker",
"ClockPicker",
]