tinyrack_ui 0.59.0 copy "tinyrack_ui: ^0.59.0" to clipboard
tinyrack_ui: ^0.59.0 copied to clipboard

Cross-platform Flutter components and design tokens for Tinyrack applications.

example/lib/main.dart

import 'dart:math' as math;

import 'package:material_ui/material_ui.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:lucide_flutter/lucide_flutter.dart';
// The preview reads package rendering metadata through internal helpers; it is
// not a published consumer.
// ignore: implementation_imports
import 'package:tinyrack_ui/src/generated/tokens.g.dart';
// ignore: implementation_imports
import 'package:tinyrack_ui/src/internal/layer.dart';
// ignore: implementation_imports
import 'package:tinyrack_ui/src/internal/motion_boundary.dart';
import 'package:tinyrack_ui/tinyrack_ui.dart';

import 'code_highlighter.dart';
import 'preview_bridge.dart';
import 'preview_examples.dart';
import 'preview_registry.g.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  final query = Uri.base.queryParameters;
  timeDilation = query['motion'] == 'true' ? 100 : 1;
  runApp(
    TRCodeHighlighterProvider(
      highlighter: previewCodeHighlighter,
      child: PreviewApp(
        component: supportedPreviewComponents.contains(query['component'])
            ? query['component']!
            : 'button',
        example: query['example'],
        initialTheme: query['theme'] == 'dark'
            ? ThemeMode.dark
            : ThemeMode.light,
        locale: switch (query['locale']) {
          'ko' => const Locale('ko'),
          'ja' => const Locale('ja'),
          _ => const Locale('en'),
        },
      ),
    ),
  );
}

class PreviewApp extends StatefulWidget {
  const PreviewApp({
    required this.component,
    required this.initialTheme,
    required this.locale,
    this.example,
    super.key,
  });

  final String component;

  /// When set, the app renders the named docs example composition instead of
  /// the single playground widget.
  final String? example;
  final ThemeMode initialTheme;
  final Locale locale;

  @override
  State<PreviewApp> createState() => _PreviewAppState();
}

class _PreviewAppState extends State<PreviewApp> {
  late final PreviewBridge _bridge;
  late final TextEditingController _textFieldController;
  late final TROtpFieldController _otpFieldController;
  final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
  late GlobalKey _previewKey;
  final Map<String, GlobalKey> _partKeys = {};
  Map<String, Object?> _args = const {};
  late String _component;
  late Locale _locale;
  late ThemeMode _themeMode;
  bool _focused = false;
  bool _focusVisible = false;
  bool _hovered = false;
  bool _pressed = false;
  int _activations = 0;
  int _contentRevision = 0;
  final int _generation = 0;

  @override
  void initState() {
    super.initState();
    _component = widget.component;
    _locale = widget.locale;
    _previewKey = GlobalKey();
    _themeMode = widget.initialTheme;
    _textFieldController = TextEditingController();
    _otpFieldController = TROtpFieldController();
    _bridge = PreviewBridge(_handleMessage);
    HardwareKeyboard.instance.addHandler(_handleHardwareKey);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _bridge.send('ready', _component, {
        'generation': _generation,
        'supportedArgs': _supportedArgs(_component),
      });
      final example = widget.example;
      if (example != null && !previewExampleScenarios.containsKey(example)) {
        _sendSchemaError('example');
      }
      _sendMetrics();
    });
  }

  void _sendMetrics({Object? requestId}) {
    final layerKind = _args['open'] == true
        ? switch (_component) {
            'alert-dialog' => TRLayerBoundaryKind.alertDialog,
            'autocomplete' => TRLayerBoundaryKind.autocomplete,
            'combobox' => TRLayerBoundaryKind.combobox,
            'inline-suggestions' => TRLayerBoundaryKind.inlineSuggestions,
            'context-menu' => TRLayerBoundaryKind.contextMenu,
            'dialog' => TRLayerBoundaryKind.dialog,
            'drawer' => TRLayerBoundaryKind.drawer,
            'menu' => TRLayerBoundaryKind.menu,
            'menubar' => TRLayerBoundaryKind.menubar,
            'navigation-menu' => TRLayerBoundaryKind.navigationMenu,
            'popover' => TRLayerBoundaryKind.popover,
            'preview-card' => TRLayerBoundaryKind.previewCard,
            'select' => TRLayerBoundaryKind.select,
            'toast' => TRLayerBoundaryKind.toast,
            'tooltip' => TRLayerBoundaryKind.tooltip,
            _ => null,
          }
        : null;
    final popupRenderObject = layerKind == null
        ? null
        : _layerBoundary(layerKind);
    final renderObject =
        popupRenderObject ?? _previewKey.currentContext?.findRenderObject();
    if (renderObject is! RenderBox || !renderObject.hasSize) return;
    final origin = renderObject.localToGlobal(Offset.zero);
    double? motionOpacity;
    final globalTransform = renderObject.getTransformTo(null).storage;
    final motionScaleX = globalTransform[0].abs();
    final motionScaleY = globalTransform[5].abs();
    double? motionTranslateX;
    double? motionTranslateY;
    double? motionProgress;
    void findMotionProgress(RenderObject node) {
      if (motionProgress != null) return;
      if (node is RenderTRMotionBoundary) {
        motionProgress = node.progress;
        return;
      }
      node.visitChildren(findMotionProgress);
    }

    findMotionProgress(renderObject);
    RenderObject? ancestor = renderObject.parent;
    while (ancestor != null) {
      if (motionOpacity == null && ancestor is RenderOpacity) {
        motionOpacity = ancestor.opacity;
      } else if (motionOpacity == null && ancestor is RenderAnimatedOpacity) {
        motionOpacity = ancestor.opacity.value;
      }
      if (_component == 'drawer' && ancestor is RenderFractionalTranslation) {
        motionTranslateX = ancestor.translation.dx * renderObject.size.width;
        motionTranslateY = ancestor.translation.dy * renderObject.size.height;
      } else if (_component == 'toast' && ancestor is RenderTransform) {
        final transform = ancestor.getTransformTo(ancestor.parent).storage;
        motionTranslateX ??= transform[12];
        motionTranslateY ??= transform[13];
      }
      ancestor = ancestor.parent;
    }
    if (_component == 'toast' && motionOpacity != null) {
      motionTranslateX = 0;
      motionTranslateY = TRGeneratedSpacing.sm * (1 - motionOpacity);
    }
    double? baseline;
    try {
      baseline = renderObject.getDistanceToBaseline(TextBaseline.alphabetic);
    } catch (_) {
      baseline = null;
    }
    final activePartNames = _activeLayerPartNames();
    final layerParts = _layerParts(renderObject);
    final parts = {
      for (final MapEntry(:key, :value) in _partKeys.entries)
        if (key != 'popup' &&
            (activePartNames == null || activePartNames.contains(key)))
          key: _measure(value),
      for (final MapEntry(:key, :value) in layerParts.entries)
        if (activePartNames == null || activePartNames.contains(key))
          key: value,
      // The copy button's labels live inside TRButton's pressed transform;
      // report the first label paragraph so the harness can anchor the
      // press translation like it does for plain buttons.
      if (_component == 'copy-button')
        'label': _measureBox(_firstParagraph(renderObject)),
    }..removeWhere((_, value) => value == null);
    _bridge.send('metrics', _component, {
      'args': _args,
      'bounds': {
        'x': origin.dx,
        'y': origin.dy,
        'width': renderObject.size.width,
        'height': renderObject.size.height,
      },
      'baseline': baseline,
      'devicePixelRatio': View.of(context).devicePixelRatio,
      'interaction': {
        'activations': _activations,
        'enabled': _args['disabled'] != true && _args['loading'] != true,
        // Text fields used to be exempted here, transcribing the DOM rule that
        // `:focus-visible` always matches them. Both platforms now gate focus
        // emphasis on the input modality instead, so the exemption would report
        // a ring that neither of them paints.
        'focusVisible': _focused && _focusVisible,
        'focused': _focused,
        'hovered': _hovered,
        'invalid': _args['errorText'] != null,
        'loading': _args['loading'] == true,
        'pressed': _pressed,
        'readonly': _args['readOnly'] == true,
      },
      if (motionOpacity != null || layerKind != null)
        'motion': {
          'opacity': ?motionOpacity,
          'scaleX': motionScaleX,
          'scaleY': motionScaleY,
          'translateX': ?motionTranslateX,
          'translateY': ?motionTranslateY,
        },
      'motionProgress': ?motionProgress,
      if (renderObject is RenderParagraph)
        'textStyle': {
          'fontFamily': renderObject.text.style?.fontFamily,
          'fontSize': renderObject.text.style?.fontSize,
          'fontWeight': renderObject.text.style?.fontWeight?.value,
          'letterSpacing': renderObject.text.style?.letterSpacing,
        },
      'parts': parts,
      'generation': _generation,
      'theme': _themeMode.name,
      if (requestId is num) 'requestId': requestId,
    });
  }

  RenderParagraph? _firstParagraph(RenderObject root) {
    RenderParagraph? paragraph;
    void visit(RenderObject node) {
      if (paragraph != null) return;
      if (node is RenderParagraph) {
        paragraph = node;
        return;
      }
      node.visitChildren(visit);
    }

    visit(root);
    return paragraph;
  }

  Set<String>? _activeLayerPartNames() {
    final open = _args['open'] == true;
    return switch ((_component, open)) {
      ('menu', false) => const {'triggerLabel'},
      ('menu', true) => const {
        'checkboxIndicator',
        'checkboxLabel',
        'groupLabel',
        'radioIndicator',
        'radioLabel',
      },
      ('select', false) => const {'triggerIcon', 'triggerLabel'},
      ('select', true) => const {'item0Indicator', 'item0Label', 'item1Label'},
      ('dialog', false) => const {'triggerLabel'},
      ('dialog', true) => const {
        'actionLabel',
        'body',
        'cancelLabel',
        'description',
        'title',
      },
      _ => null,
    };
  }

  Map<String, Map<String, Object?>> _layerParts(RenderObject root) {
    final parts = <String, Map<String, Object?>>{};
    final rootBounds = root is RenderBox && root.hasSize
        ? root.localToGlobal(Offset.zero) & root.size
        : null;
    void visit(RenderObject node) {
      if (node is RenderTRLayerPartBoundary) {
        final nodeBounds = node.hasSize
            ? node.localToGlobal(Offset.zero) & node.size
            : null;
        final measurement = _measureBox(node);
        if (measurement != null &&
            (rootBounds == null ||
                nodeBounds == null ||
                rootBounds.overlaps(nodeBounds) ||
                rootBounds.contains(nodeBounds.center))) {
          parts.putIfAbsent(node.name, () => measurement);
        }
      }
      node.visitChildren(visit);
    }

    visit(root);
    for (final view in RendererBinding.instance.renderViews) {
      visit(view);
    }
    return parts;
  }

  Map<String, Object?>? _measure(GlobalKey key) =>
      _measureBox(key.currentContext?.findRenderObject());

  RenderTRLayerBoundary? _layerBoundary(TRLayerBoundaryKind expectedKind) {
    RenderTRLayerBoundary? result;
    void visit(RenderObject node) {
      if (result != null) return;
      if (node is RenderTRLayerBoundary && node.kind == expectedKind) {
        result = node;
        return;
      }
      node.visitChildren(visit);
    }

    for (final view in RendererBinding.instance.renderViews) {
      visit(view);
      if (result != null) break;
    }
    return result;
  }

  Map<String, Object?>? _measureBox(RenderObject? renderObject) {
    if (renderObject is! RenderBox || !renderObject.hasSize) return null;
    final paragraph = switch (renderObject) {
      RenderParagraph paragraph => paragraph,
      RenderTRLayerPartBoundary part
          when !part.name.endsWith('Indicator') &&
              !part.name.endsWith('Icon') &&
              !part.name.endsWith('Surface') =>
        _firstParagraph(part),
      _ => null,
    };
    final textBounds = paragraph == null ? null : _paragraphBounds(paragraph);
    final origin =
        textBounds?.topLeft ?? renderObject.localToGlobal(Offset.zero);
    final size = textBounds?.size ?? renderObject.size;
    double? baseline;
    try {
      final paragraphOrigin = paragraph?.localToGlobal(Offset.zero);
      final paragraphBaseline = paragraph?.getDistanceToBaseline(
        TextBaseline.alphabetic,
      );
      baseline = paragraphOrigin != null && paragraphBaseline != null
          ? paragraphOrigin.dy + paragraphBaseline - origin.dy
          : renderObject.getDistanceToBaseline(TextBaseline.alphabetic);
    } catch (_) {
      baseline = null;
    }
    return {
      'bounds': {
        'x': origin.dx,
        'y': origin.dy,
        'width': size.width,
        'height': size.height,
      },
      'baseline': baseline,
      if (paragraph != null) 'text': paragraph.text.toPlainText(),
    };
  }

  Rect? _paragraphBounds(RenderParagraph paragraph) {
    final text = paragraph.text.toPlainText();
    if (text.isEmpty) return null;
    final boxes = paragraph.getBoxesForSelection(
      TextSelection(baseOffset: 0, extentOffset: text.length),
    );
    if (boxes.isEmpty) return null;
    var bounds = boxes.first.toRect();
    for (final box in boxes.skip(1)) {
      bounds = bounds.expandToInclude(box.toRect());
    }
    final paragraphOrigin = paragraph.localToGlobal(Offset.zero);
    final glyphOrigin = paragraph.localToGlobal(Offset(bounds.left, 0));
    return Rect.fromLTWH(
      glyphOrigin.dx,
      paragraphOrigin.dy,
      bounds.width,
      paragraph.size.height,
    );
  }

  void _handleMessage(Map<String, Object?> message) {
    if (message['channel'] != 'tinyrack.flutter-preview.v1') {
      return;
    }
    final type = message['type'];
    final payload = message['payload'];
    final requestId = message['requestId'];
    if (message['component'] != _component) return;
    if (type == 'ready' ||
        type == 'configured' ||
        type == 'environmentConfigured' ||
        type == 'scenarioRendered' ||
        type == 'stateChanged' ||
        type == 'metrics' ||
        type == 'error') {
      // The standalone preview posts responses to its own window. Ignore
      // outbound protocol messages when parent and child are the same realm.
      return;
    }
    if (type == 'reset') {
      FocusManager.instance.primaryFocus?.unfocus();
      _textFieldController.clear();
      _otpFieldController.clear();
      setState(() {
        _activations = 0;
        _args = const {};
        _contentRevision += 1;
        _focusVisible = false;
        _focused = false;
        _pressed = false;
      });
    } else if (type == 'setTheme' && payload is Map) {
      if (payload['theme'] case final String theme
          when theme == 'light' || theme == 'dark') {
        setState(() {
          _themeMode = theme == 'dark' ? ThemeMode.dark : ThemeMode.light;
        });
      } else {
        _sendSchemaError(type);
        return;
      }
    } else if (type == 'updateArgs' && payload is Map) {
      final nextArgs = _validateArgs(_component, payload);
      if (nextArgs == null) {
        _sendSchemaError(type);
        return;
      }
      if (_component == 'text-field') {
        final value = nextArgs['value'];
        if (value is String && _textFieldController.text != value) {
          _textFieldController.value = TextEditingValue(
            text: value,
            selection: TextSelection.collapsed(offset: value.length),
          );
        }
      }
      if (_component == 'otp-field') {
        final value = nextArgs['value'];
        if (value is String) _otpFieldController.value = value;
      }
      setState(() {
        _args = {..._args, ...nextArgs};
      });
    } else if (type == 'measure') {
      if (payload is Map && payload['afterFrame'] == false) {
        _sendMetrics(requestId: requestId);
        return;
      }
      // Report after the next frame so layout, paint transforms and
      // interaction state all belong to the same request generation.
      WidgetsBinding.instance.addPostFrameCallback(
        (_) => _sendMetrics(requestId: requestId),
      );
      WidgetsBinding.instance.ensureVisualUpdate();
      return;
    } else {
      _sendSchemaError(type);
      return;
    }
    _bridge.send('stateChanged', _component, {
      'args': _args,
      'generation': _generation,
      'theme': _themeMode.name,
      if (requestId is num) 'requestId': requestId,
    });
    WidgetsBinding.instance.addPostFrameCallback(
      (_) => _sendMetrics(requestId: requestId),
    );
  }

  void _sendSchemaError(Object? type) {
    _bridge.send('error', _component, {
      'code': 'invalid-message',
      'messageType': type is String ? type : 'unknown',
    });
  }

  void _updateInteraction({bool? focused, bool? hovered, bool? pressed}) {
    _focused = focused ?? _focused;
    _hovered = hovered ?? _hovered;
    _pressed = pressed ?? _pressed;
    WidgetsBinding.instance.addPostFrameCallback((_) => _sendMetrics());
  }

  bool _handleHardwareKey(KeyEvent event) {
    if (event is KeyDownEvent) _focusVisible = true;
    return false;
  }

  Widget _previewContent(PreviewExampleBuilder? exampleBuilder) {
    if (exampleBuilder != null) {
      return Builder(builder: (context) => exampleBuilder(context, _locale));
    }
    return MouseRegion(
      onEnter: (_) => _updateInteraction(hovered: true),
      onExit: (_) => _updateInteraction(hovered: false),
      child: Listener(
        onPointerCancel: (_) => _updateInteraction(pressed: false),
        onPointerDown: (_) {
          _focusVisible = false;
          _updateInteraction(pressed: true);
        },
        onPointerUp: (_) => _updateInteraction(pressed: false),
        child: Focus(
          canRequestFocus: false,
          onFocusChange: (focused) => _updateInteraction(focused: focused),
          child: PreviewComponent(
            key: ValueKey('$_component-$_contentRevision'),
            args: _args,
            component: _component,
            contentRevision: _contentRevision,
            locale: _locale.languageCode,
            measureKey: _previewKey,
            partKeys: _partKeys,
            textFieldController: _textFieldController,
            otpFieldController: _otpFieldController,
            onStateChanged: (payload) {
              if (payload['pressed'] == true) _activations += 1;
              _bridge.send('stateChanged', _component, {
                ...payload,
                'generation': _generation,
              });
              WidgetsBinding.instance.addPostFrameCallback(
                (_) => _sendMetrics(),
              );
            },
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    HardwareKeyboard.instance.removeHandler(_handleHardwareKey);
    _bridge.dispose();
    _textFieldController.dispose();
    _otpFieldController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final exampleBuilder = widget.example == null
        ? null
        : previewExampleScenarios[widget.example];
    final preview = _previewContent(exampleBuilder);
    _bridge.syncPageBackground(
      _cssColor(
        (_themeMode == ThemeMode.dark
                ? TRGeneratedColors.dark
                : TRGeneratedColors.light)
            .surface,
      ),
    );
    return MaterialApp(
      navigatorKey: _navigatorKey,
      debugShowCheckedModeBanner: false,
      locale: _locale,
      localizationsDelegates: GlobalMaterialLocalizations.delegates,
      supportedLocales: const [Locale('en'), Locale('ko'), Locale('ja')],
      theme: TinyrackTheme.light(),
      darkTheme: TinyrackTheme.dark(),
      themeAnimationDuration: Duration.zero,
      themeMode: _themeMode,
      home: Scaffold(
        body: SafeArea(
          child: Center(
            child: _component == 'app-shell'
                ? preview
                : SingleChildScrollView(
                    padding: const EdgeInsets.all(32),
                    child: preview,
                  ),
          ),
        ),
      ),
    );
  }
}

/// Reads the field appearance a preview scenario asks for.
TRFieldAppearance _fieldAppearance(Map<String, Object?> args) =>
    switch (args['appearance']) {
      'ghost' => TRFieldAppearance.ghost,
      'plain' => TRFieldAppearance.plain,
      _ => TRFieldAppearance.solid,
    };

List<String> _supportedArgs(String component) => switch (component) {
  'button' => [
    'appearance',
    'children',
    'disabled',
    'intent',
    'loading',
    'loadingLabel',
    'uiSize',
  ],
  'alert' => ['showActions', 'showDescription', 'showIcon', 'variant'],
  'badge' => ['uiSize', 'variant'],
  'card' => ['focused', 'padding', 'variant'],
  'drop-overlay' => ['visible'],
  'focus-ring' => ['focused'],
  'code-block' => ['code', 'language', 'wrap'],
  'dialog' => ['open', 'placement'],
  'autocomplete' => [
    'appearance',
    'completionMode',
    'disabled',
    'errorText',
    'open',
    'placeholder',
    'readOnly',
    'uiSize',
  ],
  'alert-dialog' => ['disabled', 'label', 'open'],
  'combobox' => [
    'appearance',
    'autoHighlight',
    'clearable',
    'disabled',
    'disabledOption',
    'filterMode',
    'layout',
    'open',
    'placeholder',
    'readOnly',
    'uiSize',
  ],
  'inline-suggestions' => ['disabledOption', 'open', 'status'],
  'context-menu' ||
  'navigation-menu' ||
  'popover' ||
  'preview-card' ||
  'toast' ||
  'tooltip' => ['open'],
  'app-shell' => [
    'breakpoint',
    'controlAppearance',
    'layout',
    'mobileSidebar',
    'open',
    'sidebarCollapsed',
    'sidebarMode',
  ],
  'drawer' => ['open', 'swipeDirection'],
  'slider' => ['disabled', 'label', 'orientation', 'uiSize'],
  'menu' => ['disabled', 'open'],
  'menubar' => ['open'],
  'select' => [
    'appearance',
    'disabled',
    'errorText',
    'open',
    'readOnly',
    'searchable',
    'uiSize',
    'value',
  ],
  'icon-button' => [
    'appearance',
    'disabled',
    'intent',
    'loading',
    'loadingLabel',
    'uiSize',
  ],
  'spinner' => ['uiSize', 'variant'],
  'text' => ['align', 'color', 'truncate', 'variant', 'weight'],
  'separator' => ['orientation', 'variant'],
  'skeleton' => ['animate', 'shape'],
  'avatar' => ['shape', 'uiSize'],
  'fieldset' => ['disabled', 'legend'],
  'field' => ['disabled', 'errorText', 'helper'],
  'form' => ['label', 'required', 'submitLabel'],
  'meter' => ['value', 'variant'],
  'progress' => ['uiSize', 'value', 'variant'],
  'qr-code' => ['data', 'uiSize'],
  'radial-meter' => ['uiSize', 'value', 'variant'],
  'link' => ['disabled', 'underline', 'variant'],
  'toggle' => ['disabled', 'pressed', 'uiSize'],
  'checkbox' => ['checked', 'disabled', 'indeterminate', 'mark', 'uiSize'],
  'radio' => ['checked', 'disabled', 'readOnly', 'uiSize'],
  'switch' => ['checked', 'disabled', 'invalid', 'readOnly'],
  'toggle-group' => [
    'disabled',
    'disabledItem',
    'loopFocus',
    'multiple',
    'orientation',
    'selectedValues',
  ],
  'collapsible' => ['disabled', 'open'],
  'chat' => ['open', 'status'],
  'accordion' => ['disabledItem', 'multiple', 'open'],
  'animated-number' => [
    'animation',
    'duration',
    'formatPreset',
    'locale',
    'rollDirection',
    'value',
  ],
  'code' => ['data'],
  'copy-button' => [
    'appearance',
    'copiedLabel',
    'idleLabel',
    'intent',
    'resetDelay',
    'uiSize',
    'value',
  ],
  'checkbox-group' => ['disabled', 'label', 'readOnly', 'selectedValues'],
  'radio-group' => ['disabled', 'readOnly', 'selectedValue'],
  'textarea' => [
    'appearance',
    'disabled',
    'placeholder',
    'readOnly',
    'uiSize',
    'value',
  ],
  'tabs' => ['uiSize'],
  'pagination' => [
    'boundaryCount',
    'currentPage',
    'siblingCount',
    'totalPages',
  ],
  'scroll-area' => ['autoHide'],
  'table' => ['density', 'striped'],
  'tree-nav' => ['collapsed', 'selected'],
  'window-frame' => ['padding', 'variant'],
  'text-field' => [
    'appearance',
    'disabled',
    'errorText',
    'placeholder',
    'readOnly',
    'uiSize',
    'value',
  ],
  'otp-field' => [
    'appearance',
    'disabled',
    'errorText',
    'helperText',
    'length',
    'obscureText',
    'readOnly',
    'uiSize',
    'value',
  ],
  'number-field' => ['appearance'],
  _ => const <String>[],
};

String _cssColor(Color color) =>
    '#${(color.toARGB32() & 0xffffff).toRadixString(16).padLeft(6, '0')}';

Map<String, Object?>? _validateArgs(
  String component,
  Map<Object?, Object?> raw,
) {
  final allowed = _supportedArgs(component).toSet();
  final result = <String, Object?>{};
  for (final MapEntry(:key, :value) in raw.entries) {
    if (key is! String || !allowed.contains(key)) return null;
    final valid = switch (key) {
      'boundaryCount' || 'currentPage' || 'siblingCount' || 'totalPages' =>
        value is num || (value is String && int.tryParse(value) != null),
      'density' =>
        value is String &&
            const {'compact', 'comfortable', 'spacious'}.contains(value),
      'striped' => value is bool,
      // A field has no outline step, so it is checked before the shared action
      // rule that allows one.
      'appearance'
          when const {
            'autocomplete',
            'combobox',
            'number-field',
            'otp-field',
            'select',
            'text-field',
            'textarea',
          }.contains(component) =>
        value is String && const {'solid', 'ghost', 'plain'}.contains(value),
      'appearance' =>
        value is String && const {'solid', 'outline', 'ghost'}.contains(value),
      'filterMode' when component == 'combobox' =>
        value is String &&
            const {'contains', 'startsWith', 'none'}.contains(value),
      'layout' when component == 'combobox' =>
        value is String && const {'list', 'grid'}.contains(value),
      'status' when component == 'inline-suggestions' =>
        value is String && const {'ready', 'loading', 'error'}.contains(value),
      'required' when component == 'form' => value is bool,
      'submitLabel' when component == 'form' => value is String,
      'copiedLabel' when component == 'copy-button' => value is String,
      'idleLabel' when component == 'copy-button' => value is String,
      'resetDelay' when component == 'copy-button' =>
        value is num && value >= 0 && value <= 5000,
      'value' when component == 'animated-number' || component == 'meter' =>
        value is num,
      'value' when component == 'progress' =>
        value is num || value == 'indeterminate',
      'duration' when component == 'animated-number' =>
        value is num && value >= 0 && value <= 1500,
      'animation' when component == 'animated-number' =>
        value is String && const {'roll', 'count'}.contains(value),
      'formatPreset' when component == 'animated-number' =>
        value is String &&
            const {'decimal', 'currency', 'percent', 'unit'}.contains(value),
      'locale' when component == 'animated-number' =>
        value is String && const {'en-US', 'ko-KR', 'ja-JP'}.contains(value),
      'rollDirection' when component == 'animated-number' =>
        value is String && const {'auto', 'up', 'down'}.contains(value),
      'controlAppearance' =>
        value is String && const {'solid', 'outline', 'ghost'}.contains(value),
      'breakpoint' => value is String && const {'sm', 'lg'}.contains(value),
      'layout' =>
        value is String &&
            const {'header-first', 'sidebar-first'}.contains(value),
      'mobileSidebar' =>
        value is String && const {'drawer', 'rail'}.contains(value),
      'sidebarMode' =>
        value is String && const {'expanded', 'rail'}.contains(value),
      'children' ||
      'code' ||
      'data' ||
      'errorText' ||
      'helperText' ||
      'label' ||
      'legend' ||
      'loadingLabel' ||
      'placeholder' ||
      'selectedValue' ||
      'value' => value is String,
      'animate' ||
      'autoHide' ||
      'autoHighlight' ||
      'checked' ||
      'clearable' ||
      'collapsed' ||
      'disabled' ||
      'disabledItem' ||
      'disabledOption' ||
      'focused' ||
      'indeterminate' ||
      'invalid' ||
      'open' ||
      'loading' ||
      'loopFocus' ||
      'multiple' ||
      'obscureText' ||
      'pressed' ||
      'readOnly' ||
      'showActions' ||
      'showDescription' ||
      'showIcon' ||
      'sidebarCollapsed' ||
      'searchable' ||
      'selected' ||
      'truncate' ||
      'visible' => value is bool,
      'wrap' => value is bool,
      'language' =>
        value is String && const {'plain', 'dart', 'json'}.contains(value),
      'underline' =>
        value is String && const {'always', 'hover', 'none'}.contains(value),
      'variant' when component == 'link' =>
        value is String && const {'default', 'muted', 'danger'}.contains(value),
      'mark' =>
        value is String &&
            const {'unchecked', 'checked', 'indeterminate'}.contains(value),
      'selectedValues' =>
        value is List && value.every((entry) => entry is String),
      'orientation' =>
        value is String && const {'horizontal', 'vertical'}.contains(value),
      'swipeDirection' =>
        value is String &&
            const {'down', 'up', 'left', 'right'}.contains(value),
      'placement' =>
        value is String &&
            const {'middle', 'top', 'bottom', 'start', 'end'}.contains(value),
      'shape' when component == 'skeleton' =>
        value is String &&
            const {'text', 'rectangle', 'circle'}.contains(value),
      'shape' when component == 'avatar' =>
        value is String && const {'circle', 'square'}.contains(value),
      'intent' =>
        value is String &&
            const {
              'neutral',
              'primary',
              'info',
              'success',
              'warning',
              'danger',
            }.contains(value),
      'variant'
          when component == 'alert' ||
              component == 'badge' ||
              component == 'meter' ||
              component == 'progress' =>
        value is String &&
            const {
              'neutral',
              'info',
              'success',
              'warning',
              'danger',
            }.contains(value),
      'helper' =>
        value is String && const {'none', 'description'}.contains(value),
      'variant' when component == 'card' =>
        value is String &&
            const {'default', 'outlined', 'elevated'}.contains(value),
      'variant' when component == 'window-frame' =>
        value is String && const {'macos', 'browser'}.contains(value),
      'padding' =>
        value is String && const {'none', 'sm', 'md', 'lg'}.contains(value),
      'completionMode' =>
        value is String &&
            const {'manual', 'list', 'inline', 'both'}.contains(value),
      'variant' when component == 'spinner' =>
        value is String &&
            const {'current', 'muted', 'primary', 'danger'}.contains(value),
      'variant' when component == 'separator' =>
        value is String && const {'defaultVariant', 'muted'}.contains(value),
      'variant' when component == 'text' =>
        value is String &&
            const {
              'caption',
              'label',
              'body',
              'bodySm',
              'code',
              'headingSm',
              'headingMd',
              'headingLg',
              'display',
              'displayLg',
            }.contains(value),
      'color' =>
        value is String &&
            const {
              'default',
              'muted',
              'placeholder',
              'inverse',
              'primary',
              'info',
              'success',
              'warning',
              'danger',
            }.contains(value),
      'align' =>
        value is String && const {'start', 'center', 'end'}.contains(value),
      'weight' =>
        value is String &&
            const {
              'regular',
              'medium',
              'heading',
              'bold',
              'strong',
            }.contains(value),
      'uiSize' => value is String && const {'sm', 'md', 'lg'}.contains(value),
      'length' when component == 'otp-field' =>
        value is num && value >= 3 && value <= 8,
      _ => false,
    };
    if (!valid) return null;
    result[key] = value;
  }
  return result;
}

class _PreviewAnimatedNumber extends StatelessWidget {
  const _PreviewAnimatedNumber({required this.args, required this.measureKey});

  final Map<String, Object?> args;
  final Key measureKey;

  @override
  Widget build(BuildContext context) {
    final locale = (args['locale'] as String? ?? 'en-US').replaceAll('-', '_');
    final preset = args['formatPreset'] as String? ?? 'decimal';
    final hasFormatPreset = args.containsKey('formatPreset');
    final numberFormat = switch (preset) {
      'currency' => NumberFormat.simpleCurrency(locale: locale, name: 'USD'),
      'percent' => NumberFormat.percentPattern(
        locale,
      )..maximumFractionDigits = 1,
      _ => NumberFormat.decimalPattern(locale),
    };
    final unitFormatter = hasFormatPreset && preset == 'unit'
        ? (double value) => '${numberFormat.format(value)} GB'
        : null;
    return TRAnimatedNumber(
      animation: TRAnimatedNumberAnimation.values.byName(
        args['animation'] as String? ?? 'roll',
      ),
      duration: Duration(
        milliseconds: (args['duration'] as num? ?? 600).round(),
      ),
      formatter: unitFormatter,
      key: measureKey,
      numberFormat: hasFormatPreset && unitFormatter == null
          ? numberFormat
          : null,
      rollDirection: TRAnimatedNumberRollDirection.values.byName(
        args['rollDirection'] as String? ?? 'auto',
      ),
      value: (args['value'] as num? ?? 12345).toDouble(),
    );
  }
}

class PreviewComponent extends StatelessWidget {
  const PreviewComponent({
    required this.args,
    required this.component,
    this.contentRevision = 0,
    required this.locale,
    required this.measureKey,
    required this.partKeys,
    required this.textFieldController,
    required this.otpFieldController,
    required this.onStateChanged,
    super.key,
  });

  final Map<String, Object?> args;
  final String component;
  final int contentRevision;
  final String locale;
  final Key measureKey;
  final Map<String, GlobalKey> partKeys;
  final TextEditingController textFieldController;
  final TROtpFieldController otpFieldController;
  final ValueChanged<Map<String, Object?>> onStateChanged;

  String get _label => switch (locale) {
    'ko' => '배포',
    'ja' => 'デプロイ',
    _ => 'Deploy',
  };

  String get _otpLabel => switch (locale) {
    'ko' => '인증 코드',
    'ja' => '認証コード',
    _ => 'Verification code',
  };

  /// Splits the slot row in half with a rule, and keeps the plain gaps on the
  /// remaining seams aligned with the resolved [TRUiSize].
  TROtpSeparatorBuilder _otpSeparatorBuilder(TRUiSize uiSize, int length) {
    final slotHeight = TRControlMetrics.heightOf(uiSize);
    final gap = TRControlMetrics.gapOf(uiSize);
    final middle = length ~/ 2 - 1;
    return (context, index) => index == middle
        ? SizedBox(
            width: gap * 2,
            child: Center(
              child: Container(
                width: TRGeneratedBorders.defaultWidth,
                height: slotHeight,
                color: context.tinyrackTheme.border,
              ),
            ),
          )
        : SizedBox(width: gap);
  }

  GlobalKey _partKey(String name) => partKeys.putIfAbsent(name, GlobalKey.new);

  int _intArg(String name, int fallback) => switch (args[name]) {
    final num value => value.round(),
    final String value => int.tryParse(value) ?? fallback,
    _ => fallback,
  };

  @override
  Widget build(BuildContext context) {
    final intent = TRIntent.values.byName(
      args['intent'] is String ? args['intent']! as String : 'primary',
    );
    final size = TRUiSize.values.byName(
      args['uiSize'] is String ? args['uiSize']! as String : 'md',
    );
    final statusVariant = switch (component) {
      'alert' ||
      'badge' ||
      'meter' ||
      'progress' => TRStatusVariant.values.byName(
        args['variant'] is String ? args['variant']! as String : 'neutral',
      ),
      _ => TRStatusVariant.neutral,
    };
    return switch (component) {
      'button' => TRButton(
        appearance: TRAppearance.values.byName(
          args['appearance'] is String
              ? args['appearance']! as String
              : 'solid',
        ),
        intent: intent,
        loading: args['loading'] == true,
        loadingLabel: args['loadingLabel'] is String
            ? args['loadingLabel']! as String
            : switch (locale) {
                'ko' => '배포 중',
                'ja' => 'デプロイ中',
                _ => 'Deploying',
              },
        onPressed: args['disabled'] == true
            ? null
            : () => onStateChanged({'pressed': true}),
        uiSize: size,
        key: measureKey,
        child: Text(
          key: _partKey('label'),
          args['children'] is String ? args['children']! as String : _label,
        ),
      ),
      'icon-button' => TRIconButton(
        appearance: TRAppearance.values.byName(
          args['appearance'] is String
              ? args['appearance']! as String
              : 'solid',
        ),
        icon: _PreviewPlusIcon(key: _partKey('icon')),
        intent: intent,
        label: switch (locale) {
          'ko' => '랙 추가',
          'ja' => 'ラックを追加',
          _ => 'Add rack',
        },
        loading: args['loading'] == true,
        loadingLabel: args['loadingLabel'] is String
            ? args['loadingLabel']! as String
            : null,
        onPressed: args['disabled'] == true
            ? null
            : () => onStateChanged({'pressed': true}),
        uiSize: size,
        key: measureKey,
      ),
      'text-field' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRTextField(
          controller: textFieldController,
          enabled: args['disabled'] != true,
          errorText: args['errorText'] is String
              ? args['errorText']! as String
              : null,
          label: switch (locale) {
            'ko' => '랙 이름',
            'ja' => 'ラック名',
            _ => 'Rack name',
          },
          placeholder: args['placeholder'] is String
              ? args['placeholder']! as String
              : 'Rack alpha',
          readOnly: args['readOnly'] == true,
          onChanged: (value) => onStateChanged({
            'args': {'value': value},
          }),
          uiSize: size,
          appearance: TRFieldAppearance.values.byName(
            args['appearance'] is String
                ? args['appearance']! as String
                : 'solid',
          ),
        ),
      ),
      'card' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRCard(
          focused: args['focused'] == true,
          padding: TRCardPadding.values.byName(
            args['padding'] is String ? args['padding']! as String : 'md',
          ),
          variant: switch (args['variant']) {
            'outlined' => TRCardVariant.outlined,
            'elevated' => TRCardVariant.elevated,
            _ => TRCardVariant.defaultVariant,
          },
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            spacing: TRSpacing.medium,
            children: [
              TRCardHeader(
                key: _partKey('header'),
                children: [
                  TRCardTitle(
                    key: _partKey('title'),
                    child: const Text('Rack alpha'),
                  ),
                  TRCardDescription(
                    key: _partKey('description'),
                    child: const Text('4 services are healthy.'),
                  ),
                ],
              ),
              TRCardContent(
                key: _partKey('content'),
                child: const TRText(
                  'Latency 18 ms',
                  variant: TRTextVariant.bodySm,
                ),
              ),
              TRCardFooter(
                key: _partKey('footer'),
                children: [
                  const TRText(
                    'Updated now',
                    color: TRTextColor.muted,
                    variant: TRTextVariant.bodySm,
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
      'focus-ring' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRFocusRing(
          focused: args['focused'] == true,
          child: const SizedBox(
            height: 88,
            child: Center(child: Text('Composite control')),
          ),
        ),
      ),
      'drop-overlay' => SizedBox(
        key: measureKey,
        width: 320,
        height: 200,
        child: TRDropOverlay(
          visible: args['visible'] != false,
          label: switch (locale) {
            'ko' => '여기에 파일을 놓으세요',
            'ja' => 'ここにファイルをドロップ',
            _ => 'Drop files here',
          },
          child: const Center(child: Text('Conversation pane')),
        ),
      ),
      'menu' => _PreviewMenu(
        args: args,
        locale: locale,
        measureKey: measureKey,
        onStateChanged: onStateChanged,
        partKeys: partKeys,
      ),
      'select' => SizedBox(
        key: measureKey,
        child: _PreviewSelect(
          args: args,
          key: ValueKey(
            '${args['uiSize']}:${args['open']}:${args['value']}:${args['disabled']}:${args['readOnly']}:${args['appearance']}',
          ),
          locale: locale,
          onStateChanged: onStateChanged,
          partKeys: partKeys,
          size: size,
        ),
      ),
      'dialog' => _PreviewDialog(
        args: args,
        key: measureKey,
        locale: locale,
        onStateChanged: onStateChanged,
        partKeys: partKeys,
      ),
      'alert-dialog' => _PreviewAlertDialog(
        args: args,
        key: measureKey,
        locale: locale,
        onStateChanged: onStateChanged,
      ),
      'app-shell' => _PreviewAppShell(
        args: args,
        key: measureKey,
        locale: locale,
        onStateChanged: onStateChanged,
        partKeys: partKeys,
      ),
      'autocomplete' => _PreviewAutocomplete(
        args: args,
        key: measureKey,
        locale: locale,
      ),
      'combobox' => _PreviewCombobox(
        args: args,
        key: measureKey,
        locale: locale,
      ),
      'context-menu' => _PreviewContextMenu(
        args: args,
        key: measureKey,
        locale: locale,
      ),
      'inline-suggestions' => _PreviewInlineSuggestions(
        args: args,
        key: measureKey,
        locale: locale,
      ),
      'drawer' => _PreviewDrawer(args: args, key: measureKey, locale: locale),
      'file-tree' => SizedBox(
        key: measureKey,
        width: 320,
        child: const TRFileTree(
          nodes: [
            TRFileTreeDirectory(
              name: 'lib',
              path: '/lib',
              initiallyExpanded: true,
              children: [
                TRFileTreeFile(name: 'main.dart', path: '/lib/main.dart'),
                TRFileTreeFile(name: 'theme.dart', path: '/lib/theme.dart'),
              ],
            ),
            TRFileTreeFile(name: 'pubspec.yaml', path: '/pubspec.yaml'),
          ],
        ),
      ),
      'form' => _PreviewForm(args: args, key: measureKey, locale: locale),
      'menubar' => _PreviewMenubar(
        args: args,
        key: measureKey,
        onStateChanged: onStateChanged,
      ),
      'navigation-menu' => _PreviewNavigationMenu(args: args, key: measureKey),
      'number-field' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRNumberField(
          appearance: _fieldAppearance(args),
          defaultValue: 12,
          label: 'Replicas',
          min: 0,
          max: 100,
        ),
      ),
      'otp-field' => TROtpField(
        key: measureKey,
        controller: otpFieldController,
        enabled: args['disabled'] != true,
        errorText: args['errorText'] is String && args['errorText'] != ''
            ? args['errorText']! as String
            : null,
        helperText: args['helperText'] is String && args['helperText'] != ''
            ? args['helperText']! as String
            : null,
        label: _otpLabel,
        length: _intArg('length', 6),
        obscureText: args['obscureText'] == true,
        readOnly: args['readOnly'] == true,
        uiSize: size,
        onValueChange: (next) => onStateChanged({
          'args': {'value': next},
        }),
        separatorBuilder: _otpSeparatorBuilder(size, _intArg('length', 6)),
      ),
      'pagination' => TRPagination(
        key: measureKey,
        currentPage: _intArg('currentPage', 3),
        totalPages: _intArg('totalPages', 12),
        boundaryCount: _intArg('boundaryCount', 1),
        siblingCount: _intArg('siblingCount', 1),
        previousLabel: switch (locale) {
          'ko' => '이전',
          'ja' => '前へ',
          _ => 'Previous',
        },
        nextLabel: switch (locale) {
          'ko' => '다음',
          'ja' => '次へ',
          _ => 'Next',
        },
        onPageChanged: (page) => onStateChanged({
          'args': {'currentPage': page},
        }),
      ),
      'popover' => _PreviewPopover(args: args, key: measureKey),
      'preview-card' => _PreviewPreviewCard(args: args, key: measureKey),
      'scroll-area' => SizedBox(
        key: measureKey,
        width: 320,
        height: 160,
        child: TRScrollArea(
          autoHide: args['autoHide'] == true,
          semanticLabel: 'Rack activity',
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              for (var index = 1; index <= 12; index += 1)
                Padding(
                  padding: const EdgeInsets.all(8),
                  child: Text('Deployment $index'),
                ),
            ],
          ),
        ),
      ),
      'slider' => SizedBox(
        key: measureKey,
        height: args['orientation'] == 'vertical' ? 240 : null,
        width: args['orientation'] == 'vertical' ? null : 320,
        child: TRSlider(
          defaultValue: 40,
          enabled: args['disabled'] != true,
          label: args['label'] is String
              ? args['label']! as String
              : switch (locale) {
                  'ko' => '트래픽',
                  'ja' => 'トラフィック',
                  _ => 'Traffic',
                },
          uiSize: size,
          vertical: args['orientation'] == 'vertical',
        ),
      ),
      'toast' => SizedBox(
        width: 416,
        height: 220,
        child: _PreviewToast(
          locale: locale,
          measureKey: measureKey,
          open: args['open'] == true,
        ),
      ),
      'toolbar' => TRToolbar(
        key: measureKey,
        semanticLabel: 'Formatting',
        children: [
          TRToolbarButton(
            onPressed: () {},
            child: const Icon(Icons.format_bold, size: 16),
          ),
          TRToolbarButton(
            onPressed: () {},
            child: const Icon(Icons.format_italic, size: 16),
          ),
          const TRToolbarSeparator(),
          const TRToolbarInput(placeholder: 'Search'),
        ],
      ),
      'tooltip' => _PreviewTooltip(args: args, key: measureKey, locale: locale),
      'tree-nav' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRTreeNav<String>(
          key: ValueKey(
            'tree-nav-${args['collapsed'] == true}-${args['selected'] != false}',
          ),
          defaultValue: args['selected'] == false ? null : 'theming',
          items: [
            TRTreeNavGroup(
              value: 'getting-started',
              label: Text(switch (locale) {
                'ko' => '시작하기',
                'ja' => 'はじめに',
                _ => 'GETTING STARTED',
              }, key: _partKey('group0Label')),
              initiallyExpanded: args['collapsed'] != true,
              children: [
                TRTreeNavLeaf(
                  value: 'install',
                  label: Text(switch (locale) {
                    'ko' => '설치',
                    'ja' => 'インストール',
                    _ => 'Install',
                  }, key: _partKey('leaf0Label')),
                ),
                TRTreeNavGroup(
                  value: 'advanced',
                  label: Text(switch (locale) {
                    'ko' => '고급',
                    'ja' => '高度な設定',
                    _ => 'ADVANCED',
                  }, key: _partKey('group1Label')),
                  initiallyExpanded: true,
                  children: [
                    TRTreeNavLeaf(
                      value: 'plugins',
                      label: Text(switch (locale) {
                        'ko' => '플러그인',
                        'ja' => 'プラグイン',
                        _ => 'Plugins',
                      }, key: _partKey('leaf1Label')),
                    ),
                    TRTreeNavLeaf(
                      value: 'theming',
                      label: Text(switch (locale) {
                        'ko' => '테마',
                        'ja' => 'テーマ',
                        _ => 'Theming',
                      }, key: _partKey('leaf2Label')),
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
      ),
      'table' => TRTable(
        key: measureKey,
        caption: Text(switch (locale) {
          'ko' => '랙 상태',
          'ja' => 'ラックの状態',
          _ => 'Rack status',
        }),
        density: TRTableDensity.values.byName(
          args['density'] as String? ?? 'comfortable',
        ),
        striped: args['striped'] == true,
        columns: const [
          TRTableColumn(label: Text('Rack')),
          TRTableColumn(label: Text('Status')),
        ],
        rows: const [
          TRTableRow(cells: [Text('Rack A'), Text('Healthy')]),
          TRTableRow(cells: [Text('Rack B'), Text('Degraded')]),
        ],
      ),
      'window-frame' => SizedBox(
        key: measureKey,
        width: 400,
        child: TRWindowFrame(
          variant: args['variant'] == 'browser'
              ? TRWindowFrameVariant.browser
              : TRWindowFrameVariant.macos,
          padding: TRWindowFramePadding.values.byName(
            args['padding'] as String? ?? 'md',
          ),
          title: const Text('zsh — tinyrack'),
          address: const Text('https://tinyrack.net'),
          body: const SizedBox(
            height: 39.796875,
            child: Text('❯ tinyrack status\n✓ Ready'),
          ),
        ),
      ),
      'alert' => SizedBox(
        key: measureKey,
        width: 360,
        child: TRAlert(
          actions: args['showActions'] == true
              ? [
                  TRText(
                    'Review',
                    key: _partKey('actions'),
                    variant: TRTextVariant.bodySm,
                    weight: TRTextWeight.medium,
                  ),
                ]
              : const [],
          variant: statusVariant,
          icon: args['showIcon'] == true
              ? _PreviewStatusIcon(key: _partKey('icon'))
              : null,
          title: Text(switch (locale) {
            'ko' => '변경 사항을 저장했어요',
            'ja' => '変更を保存しました',
            _ => 'Changes saved',
          }, key: _partKey('title')),
          description: args['showDescription'] == true
              ? Text(switch (locale) {
                  'ko' => '랙 구성이 최신 상태예요.',
                  'ja' => 'ラック構成は最新です。',
                  _ => 'The rack configuration is up to date.',
                }, key: _partKey('description'))
              : null,
        ),
      ),
      'badge' => TRBadge(
        key: measureKey,
        variant: statusVariant,
        uiSize: size,
        child: Text(switch (locale) {
          'ko' => '정상',
          'ja' => '正常',
          _ => 'Healthy',
        }, key: _partKey('label')),
      ),
      'spinner' => TRSpinner(
        key: measureKey,
        label: switch (locale) {
          'ko' => '불러오는 중',
          'ja' => '読み込み中',
          _ => 'Loading',
        },
        uiSize: size,
        variant: TRSpinnerVariant.values.byName(
          args['variant'] is String ? args['variant']! as String : 'current',
        ),
      ),
      'text' => TRText(
        switch (locale) {
          'ko' => '랙 상태',
          'ja' => 'ラックの状態',
          _ => 'Rack status',
        },
        align: args['align'] is String
            ? TRTextAlign.values.byName(args['align']! as String)
            : null,
        color: args['color'] is String
            ? switch (args['color']) {
                'default' => TRTextColor.defaultColor,
                final String color => TRTextColor.values.byName(color),
                _ => null,
              }
            : null,
        truncate: args['truncate'] == true,
        variant: TRTextVariant.values.byName(
          args['variant'] is String ? args['variant']! as String : 'headingMd',
        ),
        weight: args['weight'] is String
            ? TRTextWeight.values.byName(args['weight']! as String)
            : null,
        key: measureKey,
      ),
      // Even-sized wrappers keep the 1px separator on whole device pixels;
      // centering the bare line would land it on a half pixel, which Flutter
      // antialiases while Chromium snaps.
      'separator' =>
        args['orientation'] == 'vertical'
            ? SizedBox(
                key: measureKey,
                width: 32,
                height: 64,
                child: Align(
                  alignment: Alignment.centerLeft,
                  child: TRSeparator(
                    orientation: TRSeparatorOrientation.vertical,
                    variant: TRSeparatorVariant.values.byName(
                      args['variant'] is String
                          ? args['variant']! as String
                          : 'defaultVariant',
                    ),
                  ),
                ),
              )
            : SizedBox(
                key: measureKey,
                width: 320,
                height: 32,
                child: Align(
                  alignment: Alignment.topCenter,
                  child: TRSeparator(
                    variant: TRSeparatorVariant.values.byName(
                      args['variant'] is String
                          ? args['variant']! as String
                          : 'defaultVariant',
                    ),
                  ),
                ),
              ),
      'skeleton' =>
        args['shape'] == 'circle'
            ? TRSkeleton(
                key: measureKey,
                animate: args['animate'] == true,
                shape: TRSkeletonShape.circle,
              )
            : SizedBox(
                key: measureKey,
                width: 320,
                child: TRSkeleton(
                  animate: args['animate'] == true,
                  shape: args['shape'] == 'rectangle'
                      ? TRSkeletonShape.rectangle
                      : TRSkeletonShape.text,
                ),
              ),
      'code' => TRCode(
        args['data'] is String ? args['data']! as String : 'rack.deploy()',
        key: measureKey,
      ),
      'code-block' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRCodeBlock(
          code: args['code'] is String
              ? args['code']! as String
              : 'tinyrack deploy --env prod',
          language: switch (args['language']) {
            'dart' => 'dart',
            'json' => 'json',
            _ => null,
          },
          wrap: args['wrap'] == true,
        ),
      ),
      'avatar' => TRAvatar(
        key: measureKey,
        fallback: 'AB',
        shape: args['shape'] == 'square'
            ? TRAvatarShape.square
            : TRAvatarShape.circle,
        uiSize: size,
      ),
      'breadcrumbs' => TRBreadcrumbs(
        key: measureKey,
        items: [
          TRBreadcrumbsItem(
            label: switch (locale) {
              'ko' => '홈',
              'ja' => 'ホーム',
              _ => 'Home',
            },
            onTap: () {},
          ),
          TRBreadcrumbsItem(
            label: switch (locale) {
              'ko' => '컴포넌트',
              'ja' => 'コンポーネント',
              _ => 'Components',
            },
            onTap: () {},
          ),
          TRBreadcrumbsItem(
            label: switch (locale) {
              'ko' => '브레드크럼',
              'ja' => 'パンくず',
              _ => 'Breadcrumbs',
            },
          ),
        ],
      ),
      'steps' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRStepsRoot(
          children: [
            TRStepsItem(
              child: TRText(switch (locale) {
                'ko' => '계정 만들기',
                'ja' => 'アカウント作成',
                _ => 'Create account',
              }),
            ),
            TRStepsItem(
              child: TRText(switch (locale) {
                'ko' => '이메일 인증',
                'ja' => 'メール認証',
                _ => 'Verify email',
              }),
            ),
          ],
        ),
      ),
      'fieldset' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRFieldset(
          disabled: args['disabled'] == true,
          legend: args['legend'] is String
              ? args['legend']! as String
              : switch (locale) {
                  'ko' => '연락처',
                  'ja' => '連絡先',
                  _ => 'Contact',
                },
          children: [
            TRText(switch (locale) {
              'ko' => '랙 상태',
              'ja' => 'ラックの状態',
              _ => 'Rack status',
            }, variant: TRTextVariant.bodySm),
          ],
        ),
      ),
      'field' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRField(
          disabled: args['disabled'] == true,
          errorText:
              args['errorText'] is String &&
                  (args['errorText']! as String).isNotEmpty
              ? args['errorText']! as String
              : null,
          label: switch (locale) {
            'ko' => '랙 이름',
            'ja' => 'ラック名',
            _ => 'Rack name',
          },
          description: args['helper'] == 'description'
              ? switch (locale) {
                  'ko' => '랙 목록에 표시돼요.',
                  'ja' => 'ラック一覧に表示されます。',
                  _ => 'Shown on the rack list.',
                }
              : null,
          control: const TRTextField(placeholder: 'Rack alpha'),
        ),
      ),
      'meter' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRMeter(
          label: switch (locale) {
            'ko' => '저장 공간',
            'ja' => 'ストレージ',
            _ => 'Storage',
          },
          value: (args['value'] as num?)?.toDouble() ?? 75,
          valueText: '${((args['value'] as num?)?.toDouble() ?? 75).round()}%',
          variant: statusVariant,
        ),
      ),
      'progress' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRProgress(
          uiSize: size,
          value: args['value'] == 'indeterminate'
              ? null
              : (args['value'] as num?)?.toDouble() ?? 60,
          variant: statusVariant,
        ),
      ),
      'qr-code' => TRQrCode(
        key: measureKey,
        data: args['data'] is String && (args['data']! as String).isNotEmpty
            ? args['data']! as String
            : 'https://tinyrack.net',
        semanticLabel: switch (locale) {
          'ko' => '미리보기 QR 코드',
          'ja' => 'プレビュー QR コード',
          _ => 'Preview QR code',
        },
        uiSize: size,
      ),
      'radial-meter' => TRRadialMeter(
        key: measureKey,
        value: (args['value'] as num?)?.toDouble() ?? 72,
        semanticLabel: switch (locale) {
          'ko' => '컨텍스트 사용량',
          'ja' => 'コンテキスト使用量',
          _ => 'Context usage',
        },
        variant: statusVariant,
        uiSize: size,
      ),
      'link' => TRLink(
        key: measureKey,
        disabled: args['disabled'] == true,
        underline: switch (args['underline']) {
          'always' => TRLinkUnderline.always,
          'none' => TRLinkUnderline.none,
          _ => TRLinkUnderline.hover,
        },
        variant: switch (args['variant']) {
          'muted' => TRLinkVariant.muted,
          'danger' => TRLinkVariant.danger,
          _ => TRLinkVariant.defaultVariant,
        },
        onTap: args['disabled'] == true
            ? null
            : () => onStateChanged({'pressed': true}),
        // Chromium sizes the inline anchor box from the primary latin font's
        // metrics regardless of which fallback renders the glyphs.
        child: Text(
          switch (locale) {
            'ko' => '문서',
            'ja' => 'ドキュメント',
            _ => 'Docs',
          },
          key: _partKey('label'),
          style: const TextStyle(
            fontSize: TRGeneratedTypographySizes.md,
            height:
                TRGeneratedFlutterRendering.normalLineMd /
                TRGeneratedTypographySizes.md,
          ),
        ),
      ),
      'toggle' => TRToggle(
        key: measureKey,
        disabled: args['disabled'] == true,
        pressed: args['pressed'] == true,
        uiSize: size,
        onPressedChange: (next) => onStateChanged({
          'pressed': true,
          'args': {'pressed': next},
        }),
        child: Text(switch (locale) {
          'ko' => '굵게',
          'ja' => '太字',
          _ => 'Bold',
        }, key: _partKey('label')),
      ),
      'checkbox' => TRCheckbox(
        key: measureKey,
        checked: args['mark'] is String
            ? args['mark'] == 'checked'
            : args['checked'] == true,
        indeterminate: args['mark'] is String
            ? args['mark'] == 'indeterminate'
            : args['indeterminate'] == true,
        disabled: args['disabled'] == true,
        uiSize: size,
        onCheckedChange: (checked) => onStateChanged({
          'pressed': true,
          'args': {'checked': checked, 'indeterminate': false},
        }),
      ),
      'radio' => TRRadioGroup(
        key: measureKey,
        value: args['checked'] == true ? 'on' : '',
        onValueChange: (next) => onStateChanged({
          'pressed': true,
          'args': {'checked': next == 'on'},
        }),
        children: [
          TRRadio(
            value: 'on',
            disabled: args['disabled'] == true,
            readOnly: args['readOnly'] == true,
            uiSize: size,
          ),
        ],
      ),
      'switch' => TRSwitch(
        key: measureKey,
        thumbKey: _partKey('thumb'),
        checked: args['checked'] == true,
        disabled: args['disabled'] == true,
        invalid: args['invalid'] == true,
        readOnly: args['readOnly'] == true,
        semanticLabel: switch (locale) {
          'ko' => '자동 백업',
          'ja' => '自動バックアップ',
          _ => 'Automatic backups',
        },
        onCheckedChange: (next) => onStateChanged({
          'pressed': true,
          'args': {'checked': next},
        }),
      ),
      'collapsible' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRCollapsible(
          disabled: args['disabled'] == true,
          open: args['open'] == true,
          onOpenChange: (_) => onStateChanged({'pressed': true}),
          trigger: Text(switch (locale) {
            'ko' => '상세 정보',
            'ja' => '詳細',
            _ => 'Details',
          }),
          content: TRText(
            switch (locale) {
              'ko' => '랙 구성이 최신 상태예요.',
              'ja' => 'ラック構成は最新です。',
              _ => 'The rack configuration is up to date.',
            },
            variant: TRTextVariant.bodySm,
            color: TRTextColor.muted,
          ),
        ),
      ),
      'chat' => SizedBox(
        key: measureKey,
        width: TRMeasurements.measureXl,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            TRChatUserBubble(
              child: Text(switch (locale) {
                'ko' => '테스트를 실행해 주세요.',
                'ja' => 'テストを実行してください。',
                _ => 'Please run the tests.',
              }),
            ),
            TRChatMessageRow(
              icon: LucideIcons.bot,
              tone: TRChatMessageTone.primary,
              child: Text(switch (locale) {
                'ko' => '변경 사항을 확인하고 있어요.',
                'ja' => '変更内容を確認しています。',
                _ => 'I am checking the changes.',
              }),
            ),
            TRChatMessageRow(
              icon: LucideIcons.image,
              alignment: TRChatMessageAlignment.center,
              child: TRCard(
                padding: TRCardPadding.sm,
                child: TRText(switch (locale) {
                  'ko' => '결과 이미지\npreview.png',
                  'ja' => '結果画像\npreview.png',
                  _ => 'Result image\npreview.png',
                }, variant: TRTextVariant.bodySm),
              ),
            ),
            TRChatToolDisclosure(
              icon: LucideIcons.terminal,
              label: switch (locale) {
                'ko' => '명령 실행',
                'ja' => 'コマンドを実行',
                _ => 'Run command',
              },
              secondaryLabel: r'flutter test --coverage',
              status: switch (args['status']) {
                'running' => TRChatToolStatus.running,
                'failed' => TRChatToolStatus.failed,
                'denied' => TRChatToolStatus.denied,
                _ => TRChatToolStatus.succeeded,
              },
              statusLabel: switch (args['status']) {
                'running' => switch (locale) {
                  'ko' => '실행 중',
                  'ja' => '実行中',
                  _ => 'Running',
                },
                'failed' => switch (locale) {
                  'ko' => '실패',
                  'ja' => '失敗',
                  _ => 'Failed',
                },
                'denied' => switch (locale) {
                  'ko' => '거부됨',
                  'ja' => '拒否',
                  _ => 'Denied',
                },
                _ => switch (locale) {
                  'ko' => '완료',
                  'ja' => '完了',
                  _ => 'Done',
                },
              },
              open: args['open'] == true,
              onOpenChange: (_) => onStateChanged({'pressed': true}),
              details: const TRCodeBlock(code: r'$ flutter test'),
            ),
          ],
        ),
      ),
      // The web accordion keeps content-box sizing: 320px of content plus
      // the 1px side borders.
      'accordion' => SizedBox(
        key: measureKey,
        width: 322,
        child: _PreviewAccordion(
          key: ValueKey(contentRevision),
          disabledItem: args['disabledItem'] == true,
          installTriggerKey: _partKey('trigger'),
          configureContentKey: _partKey('configureContent'),
          configureTriggerKey: _partKey('configureTrigger'),
          locale: locale,
          multiple: args['multiple'] == true,
          open: args['open'] == true,
          onStateChanged: onStateChanged,
        ),
      ),
      'animated-number' => _PreviewAnimatedNumber(
        args: args,
        measureKey: measureKey,
      ),
      'copy-button' => TRCopyButton(
        key: measureKey,
        appearance: TRAppearance.values.byName(
          args['appearance'] is String
              ? args['appearance']! as String
              : 'solid',
        ),
        // The shared `intent` fallback is `primary`; the copy button defaults
        // to `neutral` like the widget itself.
        intent: TRIntent.values.byName(
          args['intent'] is String ? args['intent']! as String : 'neutral',
        ),
        uiSize: size,
        onStatusChange: (status) {
          if (status == TRCopyButtonStatus.copied) {
            onStateChanged({'pressed': true});
          }
        },
        resetDelay: switch (args['resetDelay']) {
          final num value => Duration(milliseconds: value.round()),
          _ => const Duration(seconds: 2),
        },
        value: args['value'] is String
            ? args['value']! as String
            : 'tinyrack.net',
        idleLabel: args['idleLabel'] is String
            ? args['idleLabel']! as String
            : switch (locale) {
                'ko' => '복사',
                'ja' => 'コピー',
                _ => 'Copy',
              },
        copiedLabel: args['copiedLabel'] is String
            ? args['copiedLabel']! as String
            : switch (locale) {
                'ko' => '복사됨',
                'ja' => 'コピー済み',
                _ => 'Copied',
              },
      ),
      'toggle-group' => TRToggleGroup(
        key: measureKey,
        disabled: args['disabled'] == true,
        loopFocus: args['loopFocus'] != false,
        multiple: args['multiple'] == true,
        onValueChange: (selectedValues) => onStateChanged({
          'pressed': true,
          'args': {'selectedValues': selectedValues},
        }),
        orientation: args['orientation'] == 'vertical'
            ? Axis.vertical
            : Axis.horizontal,
        value: args['selectedValues'] is List
            ? List<String>.from(args['selectedValues']! as List)
            : const ['start'],
        children: [
          TRToggle(
            value: 'start',
            child: Text(switch (locale) {
              'ko' => '시작',
              'ja' => '先頭',
              _ => 'Start',
            }, key: _partKey('start')),
          ),
          TRToggle(
            value: 'center',
            child: Text(switch (locale) {
              'ko' => '가운데',
              'ja' => '中央',
              _ => 'Center',
            }, key: _partKey('center')),
          ),
          TRToggle(
            disabled: args['disabledItem'] == true,
            value: 'end',
            child: Text(switch (locale) {
              'ko' => '끝',
              'ja' => '末尾',
              _ => 'End',
            }, key: _partKey('end')),
          ),
        ],
      ),
      'checkbox-group' => Column(
        key: measureKey,
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        spacing: TRSpacing.small,
        children: [
          TRText(
            args['label'] is String
                ? args['label']! as String
                : switch (locale) {
                    'ko' => '랙 기능',
                    'ja' => 'ラック機能',
                    _ => 'Rack features',
                  },
            variant: TRTextVariant.label,
          ),
          TRCheckboxGroup(
            disabled: args['disabled'] == true,
            onValueChange: (selectedValues) => onStateChanged({
              'args': {'selectedValues': selectedValues},
            }),
            value: args['selectedValues'] is List
                ? List<String>.from(args['selectedValues']! as List)
                : const ['metrics', 'backups'],
            children: [
              for (final (index, value, label) in [
                (
                  0,
                  'metrics',
                  switch (locale) {
                    'ko' => '지표',
                    'ja' => 'メトリクス',
                    _ => 'Metrics',
                  },
                ),
                (
                  1,
                  'alerts',
                  switch (locale) {
                    'ko' => '알림',
                    'ja' => 'アラート',
                    _ => 'Alerts',
                  },
                ),
                (
                  2,
                  'backups',
                  switch (locale) {
                    'ko' => '자동 백업',
                    'ja' => '自動バックアップ',
                    _ => 'Automated backups',
                  },
                ),
              ])
                MergeSemantics(
                  child: Row(
                    mainAxisSize: MainAxisSize.min,
                    spacing: TRSpacing.small,
                    children: [
                      TRCheckbox(
                        key: index == 0 ? _partKey('first') : null,
                        readOnly: args['readOnly'] == true,
                        value: value,
                      ),
                      TRText(label, variant: TRTextVariant.bodySm),
                    ],
                  ),
                ),
            ],
          ),
        ],
      ),
      'radio-group' => TRRadioGroup(
        key: measureKey,
        disabled: args['disabled'] == true,
        readOnly: args['readOnly'] == true,
        onValueChange: (next) => onStateChanged({
          'pressed': true,
          'args': {'selectedValue': next},
        }),
        value: args['selectedValue'] is String
            ? args['selectedValue']! as String
            : 'start',
        children: [
          TRRadio(
            key: _partKey('first'),
            value: 'start',
            label: TRText(switch (locale) {
              'ko' => '시작',
              'ja' => '先頭',
              _ => 'Start',
            }, variant: TRTextVariant.bodySm),
          ),
          TRRadio(
            value: 'end',
            label: TRText(switch (locale) {
              'ko' => '끝',
              'ja' => '末尾',
              _ => 'End',
            }, variant: TRTextVariant.bodySm),
          ),
        ],
      ),
      'textarea' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRTextarea(
          enabled: args['disabled'] != true,
          initialValue: args['value'] is String
              ? args['value']! as String
              : null,
          placeholder: args['placeholder'] is String
              ? args['placeholder']! as String
              : null,
          readOnly: args['readOnly'] == true,
          uiSize: size,
          appearance: TRFieldAppearance.values.byName(
            args['appearance'] is String
                ? args['appearance']! as String
                : 'solid',
          ),
        ),
      ),
      'tabs' => SizedBox(
        key: measureKey,
        width: 320,
        child: TRTabs(
          defaultValue: 'overview',
          onValueChange: (_) => onStateChanged({'pressed': true}),
          uiSize: size,
          panelBuilder: (_) => TRText(switch (locale) {
            'ko' => '랙 구성이 최신 상태예요.',
            'ja' => 'ラック構成は最新です。',
            _ => 'The rack configuration is up to date.',
          }, variant: TRTextVariant.bodySm),
          tabs: [
            TRTabsTab(
              value: 'overview',
              label: switch (locale) {
                'ko' => '개요',
                'ja' => '概要',
                _ => 'Overview',
              },
            ),
            TRTabsTab(
              value: 'settings',
              label: switch (locale) {
                'ko' => '설정',
                'ja' => '設定',
                _ => 'Settings',
              },
            ),
          ],
        ),
      ),
      _ => const Text('Unsupported preview'),
    };
  }
}

class _PreviewAccordion extends StatefulWidget {
  const _PreviewAccordion({
    required this.configureContentKey,
    required this.configureTriggerKey,
    required this.disabledItem,
    required this.installTriggerKey,
    required this.locale,
    required this.multiple,
    required this.open,
    required this.onStateChanged,
    super.key,
  });

  final Key configureContentKey;
  final Key configureTriggerKey;
  final bool disabledItem;
  final Key installTriggerKey;
  final String locale;
  final bool multiple;
  final bool open;
  final ValueChanged<Map<String, Object?>> onStateChanged;

  @override
  State<_PreviewAccordion> createState() => _PreviewAccordionState();
}

class _PreviewAccordionState extends State<_PreviewAccordion> {
  List<String> _value = const ['install'];

  @override
  void didUpdateWidget(_PreviewAccordion oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.open != widget.open) {
      _value = widget.open ? const ['install'] : const [];
    }
    final next = widget.disabledItem
        ? _value.where((value) => value != 'configure').toList()
        : [..._value];
    if (!widget.multiple && next.length > 1) next.removeRange(1, next.length);
    if (next.length != _value.length) _value = next;
  }

  String _pick(String en, String ko, String ja) => switch (widget.locale) {
    'ko' => ko,
    'ja' => ja,
    _ => en,
  };

  void _handleValueChange(List<String> value) {
    setState(() => _value = value);
    widget.onStateChanged({'pressed': true, 'value': value.join(',')});
  }

  @override
  Widget build(BuildContext context) => TRAccordion(
    multiple: widget.multiple,
    onValueChange: _handleValueChange,
    value: _value,
    items: [
      TRAccordionItem(
        value: 'install',
        trigger: Text(
          _pick('Install', '설치', 'インストール'),
          key: widget.installTriggerKey,
        ),
        content: TRText(
          _pick('Add the package.', '패키지를 추가하세요.', 'パッケージを追加してください。'),
          variant: TRTextVariant.bodySm,
        ),
      ),
      TRAccordionItem(
        value: 'configure',
        disabled: widget.disabledItem,
        trigger: Text(
          _pick('Configure', '설정', '設定'),
          key: widget.configureTriggerKey,
        ),
        content: TRText(
          _pick('Wire up the theme.', '테마를 연결하세요.', 'テーマを接続してください。'),
          key: widget.configureContentKey,
          variant: TRTextVariant.bodySm,
        ),
      ),
    ],
  );
}

class _PreviewAlertDialog extends StatefulWidget {
  const _PreviewAlertDialog({
    required this.args,
    required this.locale,
    required this.onStateChanged,
    super.key,
  });

  final Map<String, Object?> args;
  final String locale;
  final ValueChanged<Map<String, Object?>> onStateChanged;

  @override
  State<_PreviewAlertDialog> createState() => _PreviewAlertDialogState();
}

class _PreviewAlertDialogState extends State<_PreviewAlertDialog> {
  bool _routeOpen = false;
  NavigatorState? _rootNavigator;

  @override
  void initState() {
    super.initState();
    if (widget.args['open'] == true) _scheduleShow();
  }

  @override
  void didUpdateWidget(_PreviewAlertDialog oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.args['open'] != true && widget.args['open'] == true) {
      _scheduleShow();
    } else if (oldWidget.args['open'] == true &&
        widget.args['open'] != true &&
        _routeOpen) {
      Navigator.of(context, rootNavigator: true).maybePop();
    }
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _rootNavigator = Navigator.of(context, rootNavigator: true);
  }

  @override
  void dispose() {
    final navigator = _rootNavigator;
    if (_routeOpen && navigator != null) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (navigator.mounted && navigator.canPop()) navigator.pop();
      });
    }
    super.dispose();
  }

  void _scheduleShow() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (mounted && !_routeOpen) _show(ignoreDisabled: true);
    });
  }

  String _pick(String en, String ko, String ja) => switch (widget.locale) {
    'ko' => ko,
    'ja' => ja,
    _ => en,
  };

  Future<void> _show({bool ignoreDisabled = false}) async {
    if (_routeOpen || (!ignoreDisabled && widget.args['disabled'] == true)) {
      return;
    }
    setState(() => _routeOpen = true);
    widget.onStateChanged({'open': true});
    await showTRAlertDialog<bool>(
      context: context,
      builder: (context) => Center(
        child: SizedBox(
          width: TRGeneratedMeasurements.overlayWidthSm,
          child: TRAlertDialog(
            title: Text(_pick('Delete rack?', '랙을 삭제할까요?', 'ラックを削除しますか?')),
            description: Text(
              _pick(
                'This action cannot be undone.',
                '이 작업은 되돌릴 수 없어요.',
                'この操作は取り消せません。',
              ),
            ),
            actions: [
              TRButton(
                appearance: TRAppearance.outline,
                onPressed: () => Navigator.pop(context, false),
                child: TRLayerPartBoundary(
                  name: 'cancelLabel',
                  child: Text(_pick('Cancel', '취소', 'キャンセル')),
                ),
              ),
              TRButton(
                intent: TRIntent.danger,
                onPressed: () => Navigator.pop(context, true),
                child: TRLayerPartBoundary(
                  name: 'actionLabel',
                  child: Text(_pick('Delete rack', '랙 삭제', 'ラックを削除')),
                ),
              ),
            ],
          ),
        ),
      ),
    );
    if (!mounted) return;
    setState(() => _routeOpen = false);
    widget.onStateChanged({'open': false});
  }

  @override
  Widget build(BuildContext context) => SizedBox(
    width: 128,
    child: TRButton(
      intent: TRIntent.danger,
      onPressed: widget.args['disabled'] == true ? null : _show,
      child: Text(
        widget.args['label'] is String
            ? widget.args['label']! as String
            : _pick('Delete rack', '랙 삭제', 'ラックを削除'),
      ),
    ),
  );
}

class _PreviewAppShell extends StatefulWidget {
  const _PreviewAppShell({
    required this.args,
    required this.locale,
    required this.onStateChanged,
    required this.partKeys,
    super.key,
  });

  final Map<String, Object?> args;
  final String locale;
  final ValueChanged<Map<String, Object?>> onStateChanged;
  final Map<String, GlobalKey> partKeys;

  @override
  State<_PreviewAppShell> createState() => _PreviewAppShellState();
}

class _PreviewAppShellState extends State<_PreviewAppShell> {
  late final TRAppShellController _controller = TRAppShellController();
  bool _syncing = false;

  @override
  void initState() {
    super.initState();
    _sync();
  }

  @override
  void didUpdateWidget(_PreviewAppShell oldWidget) {
    super.didUpdateWidget(oldWidget);
    _sync();
  }

  void _sync() {
    _syncing = true;
    _controller.setMobileOpen(widget.args['open'] == true);
    _controller.setSidebarMode(
      widget.args['sidebarMode'] == 'rail'
          ? TRAppShellSidebarMode.rail
          : TRAppShellSidebarMode.expanded,
    );
    _syncing = false;
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  String _pick(String en, String ko, String ja) => switch (widget.locale) {
    'ko' => ko,
    'ja' => ja,
    _ => en,
  };

  GlobalKey _partKey(String name) =>
      widget.partKeys.putIfAbsent(name, GlobalKey.new);

  @override
  Widget build(BuildContext context) {
    final viewport = MediaQuery.sizeOf(context);
    final appearance = TRAppearance.values.byName(
      widget.args['controlAppearance'] is String
          ? widget.args['controlAppearance']! as String
          : 'ghost',
    );
    final navigation = [
      (Icons.speed_outlined, _pick('Overview', '개요', '概要')),
      (Icons.rocket_launch_outlined, _pick('Deployments', '배포', 'デプロイ')),
      (Icons.widgets_outlined, _pick('Services', '서비스', 'サービス')),
      (Icons.storage_outlined, _pick('Data stores', '데이터 저장소', 'データストア')),
    ];
    return SizedBox(
      width: math.min(720, viewport.width),
      height: math.min(360, viewport.height),
      child: TRAppShell(
        breakpoint: widget.args['breakpoint'] == 'sm'
            ? TRAppShellBreakpoint.sm
            : TRAppShellBreakpoint.lg,
        controller: _controller,
        layout: widget.args['layout'] == 'header-first'
            ? TRAppShellLayout.headerFirst
            : TRAppShellLayout.sidebarFirst,
        mobileSidebar: widget.args['mobileSidebar'] == 'rail'
            ? TRAppShellMobileSidebar.rail
            : TRAppShellMobileSidebar.drawer,
        onMobileOpenChanged: (open) {
          if (!_syncing) {
            widget.onStateChanged({
              'args': {'open': open},
            });
          }
        },
        onSidebarModeChanged: (mode) {
          if (!_syncing) {
            widget.onStateChanged({
              'args': {'sidebarMode': mode.name},
            });
          }
        },
        header: TRAppShellHeader(
          borderBottom: true,
          key: _partKey('header'),
          height: 52,
          padding: const EdgeInsets.symmetric(horizontal: 12),
          children: [
            TRAppShellTrigger(
              appearance: appearance,
              icon: const Icon(Icons.menu),
              label: _pick('Open navigation', '탐색 열기', 'ナビゲーションを開く'),
            ),
            TRAppShellBrand(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Orbit Ops',
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(
                      fontSize: 14,
                      fontWeight: FontWeight.w600,
                      height: 20 / 14,
                    ),
                  ),
                  Text(
                    _pick('Production environment', '프로덕션 환경', '本番環境'),
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(
                      color: context.tinyrackTheme.textMuted,
                      fontSize: 12,
                      height: 16 / 12,
                    ),
                  ),
                ],
              ),
            ),
            TRAppShellActions(
              children: [
                DecoratedBox(
                  decoration: BoxDecoration(
                    color: context.tinyrackTheme.surfaceMuted,
                    borderRadius: BorderRadius.circular(TRGeneratedRadii.full),
                  ),
                  child: const Padding(
                    padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                    child: Text(
                      'us-east',
                      style: TextStyle(fontSize: 12, height: 16 / 12),
                    ),
                  ),
                ),
              ],
            ),
          ],
        ),
        sidebar: TRAppShellSidebar(
          key: _partKey('sidebar'),
          collapsed: widget.args['sidebarCollapsed'] == true,
          padding: const EdgeInsets.all(12),
          scroll: false,
          semanticLabel: _pick('Example navigation', '예제 탐색', 'サンプルナビゲーション'),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              SizedBox(
                height: 32,
                child: Row(
                  children: [
                    Container(
                      width: 28,
                      height: 28,
                      decoration: BoxDecoration(
                        color: context.tinyrackTheme.primary,
                        borderRadius: BorderRadius.circular(
                          TRGeneratedRadii.md,
                        ),
                      ),
                      child: Icon(
                        Icons.widgets_outlined,
                        color: context.tinyrackTheme.onPrimary,
                        size: 16,
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: TRAppShellSidebarLabel(
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            const Text(
                              'Orbit Ops',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: 14,
                                fontWeight: FontWeight.w600,
                              ),
                            ),
                            Text(
                              _pick(
                                'Production workspace',
                                '프로덕션 워크스페이스',
                                '本番ワークスペース',
                              ),
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                color: context.tinyrackTheme.textMuted,
                                fontSize: 12,
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                    TRAppShellSidebarToggle(
                      appearance: appearance,
                      icon: const Icon(Icons.view_sidebar_outlined),
                      label: _pick('Toggle sidebar', '사이드바 전환', 'サイドバーを切り替える'),
                    ),
                    TRAppShellClose(
                      appearance: appearance,
                      icon: const Icon(Icons.close),
                      label: _pick('Close navigation', '탐색 닫기', 'ナビゲーションを閉じる'),
                    ),
                  ],
                ),
              ),
              const SizedBox(height: 16),
              for (var index = 0; index < navigation.length; index++) ...[
                TRLayerPartBoundary(
                  name: 'navigationRow${index}Surface',
                  child: Container(
                    height: 36,
                    padding: const EdgeInsets.symmetric(horizontal: 8),
                    decoration: BoxDecoration(
                      color: index == 0
                          ? context.tinyrackTheme.surfaceMuted
                          : Colors.transparent,
                      borderRadius: BorderRadius.circular(TRGeneratedRadii.md),
                    ),
                    child: Row(
                      children: [
                        Icon(
                          navigation[index].$1,
                          color: index == 0
                              ? context.tinyrackTheme.text
                              : context.tinyrackTheme.textMuted,
                          size: 16,
                        ),
                        const SizedBox(width: 12),
                        TRAppShellSidebarLabel(
                          child: Text(
                            navigation[index].$2,
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                            style: TextStyle(
                              color: index == 0
                                  ? context.tinyrackTheme.text
                                  : context.tinyrackTheme.textMuted,
                              fontSize: 14,
                              height: 20 / 14,
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
                if (index != navigation.length - 1) const SizedBox(height: 4),
              ],
              const Spacer(),
              TRLayerPartBoundary(
                name: 'profileSurface',
                child: Container(
                  height: 46,
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    border: Border.all(color: context.tinyrackTheme.border),
                    borderRadius: BorderRadius.circular(TRGeneratedRadii.md),
                  ),
                  child: Row(
                    children: [
                      Container(
                        width: 28,
                        height: 28,
                        alignment: Alignment.center,
                        decoration: BoxDecoration(
                          color: context.tinyrackTheme.surfaceMuted,
                          shape: BoxShape.circle,
                        ),
                        child: const Text(
                          'AK',
                          style: TextStyle(
                            fontSize: 12,
                            fontWeight: FontWeight.w600,
                          ),
                        ),
                      ),
                      const SizedBox(width: 12),
                      Expanded(
                        child: TRAppShellSidebarLabel(
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              const Text(
                                'Avery Kim',
                                maxLines: 1,
                                overflow: TextOverflow.ellipsis,
                                style: TextStyle(
                                  fontSize: 12,
                                  fontWeight: FontWeight.w500,
                                ),
                              ),
                              Text(
                                _pick('Platform team', '플랫폼 팀', 'プラットフォームチーム'),
                                maxLines: 1,
                                overflow: TextOverflow.ellipsis,
                                style: TextStyle(
                                  color: context.tinyrackTheme.textMuted,
                                  fontSize: 12,
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
        main: TRAppShellMain(
          key: _partKey('main'),
          child: _PreviewAppShellContent(locale: widget.locale),
        ),
      ),
    );
  }
}

class _PreviewAppShellContent extends StatelessWidget {
  const _PreviewAppShellContent({required this.locale});

  final String locale;

  String _pick(String en, String ko, String ja) => switch (locale) {
    'ko' => ko,
    'ja' => ja,
    _ => en,
  };

  @override
  Widget build(BuildContext context) {
    final metrics = [
      (_pick('Healthy services', '정상 서비스', '正常なサービス'), '24 / 24'),
      (_pick('Deployments today', '오늘 배포', '本日のデプロイ'), '18'),
      (_pick('P95 response', 'P95 응답', 'P95 応答'), '128 ms'),
    ];
    final border = context.tinyrackTheme.border;
    return Padding(
      padding: const EdgeInsets.all(20),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          SizedBox(
            height: 44,
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'PRODUCTION / US-EAST',
                        style: TextStyle(
                          color: context.tinyrackTheme.textMuted,
                          fontSize: 12,
                          fontWeight: FontWeight.w500,
                          height: 16 / 12,
                          letterSpacing: .4,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        _pick('System overview', '시스템 개요', 'システム概要'),
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                        style: const TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.w600,
                          height: 24 / 18,
                        ),
                      ),
                    ],
                  ),
                ),
                TRLayerPartBoundary(
                  name: 'statusSurface',
                  child: Container(
                    width: 169,
                    height: 26,
                    padding: const EdgeInsets.symmetric(horizontal: 12),
                    decoration: BoxDecoration(
                      border: Border.all(color: border),
                      borderRadius: BorderRadius.circular(
                        TRGeneratedRadii.full,
                      ),
                    ),
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Container(
                          width: 8,
                          height: 8,
                          decoration: BoxDecoration(
                            color: context.tinyrackTheme.success,
                            shape: BoxShape.circle,
                          ),
                        ),
                        const SizedBox(width: 8),
                        Expanded(
                          child: Text(
                            _pick(
                              'All systems operational',
                              '모든 시스템이 정상이에요',
                              'すべてのシステムが正常です',
                            ),
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                            style: const TextStyle(fontSize: 12),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),
          Row(
            children: [
              for (var index = 0; index < metrics.length; index++) ...[
                Expanded(
                  child: TRLayerPartBoundary(
                    name: 'metric${index}Surface',
                    child: Container(
                      height: 62,
                      padding: const EdgeInsets.all(12),
                      decoration: BoxDecoration(
                        border: Border.all(color: border),
                        borderRadius: BorderRadius.circular(
                          TRGeneratedRadii.md,
                        ),
                      ),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            metrics[index].$1,
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                            style: TextStyle(
                              color: context.tinyrackTheme.textMuted,
                              fontSize: 12,
                              height: 14 / 12,
                            ),
                          ),
                          const SizedBox(height: 4),
                          Text(
                            metrics[index].$2,
                            style: const TextStyle(
                              fontSize: 16,
                              fontWeight: FontWeight.w700,
                              height: 18 / 16,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
                if (index != metrics.length - 1) const SizedBox(width: 8),
              ],
            ],
          ),
          const SizedBox(height: 16),
          Expanded(
            child: TRLayerPartBoundary(
              name: 'activitySurface',
              child: Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  border: Border.all(color: border),
                  borderRadius: BorderRadius.circular(TRGeneratedRadii.md),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    Row(
                      children: [
                        Expanded(
                          child: Text(
                            _pick('Recent activity', '최근 활동', '最近のアクティビティ'),
                            style: const TextStyle(
                              fontSize: 14,
                              fontWeight: FontWeight.w600,
                            ),
                          ),
                        ),
                        Icon(
                          Icons.monitor_heart_outlined,
                          color: context.tinyrackTheme.textMuted,
                          size: 12,
                        ),
                        const SizedBox(width: 4),
                        Text(
                          _pick('Live', '실시간', 'ライブ'),
                          style: TextStyle(
                            color: context.tinyrackTheme.textMuted,
                            fontSize: 12,
                          ),
                        ),
                      ],
                    ),
                    const Spacer(),
                    Text(
                      _pick(
                        'api-gateway deployed successfully    4m',
                        'api-gateway 배포에 성공했어요    4m',
                        'api-gateway のデプロイに成功しました    4m',
                      ),
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                      style: const TextStyle(fontSize: 12),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      _pick(
                        'Database backup completed    18m',
                        '데이터베이스 백업을 마쳤어요    18m',
                        'データベースのバックアップが完了しました    18m',
                      ),
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                      style: const TextStyle(fontSize: 12),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _PreviewAutocomplete extends StatefulWidget {
  const _PreviewAutocomplete({
    required this.args,
    required this.locale,
    super.key,
  });

  final Map<String, Object?> args;
  final String locale;

  @override
  State<_PreviewAutocomplete> createState() => _PreviewAutocompleteState();
}

class _PreviewAutocompleteState extends State<_PreviewAutocomplete> {
  late final TRAutocompleteController<String> _controller =
      TRAutocompleteController<String>();

  @override
  void initState() {
    super.initState();
    _sync();
  }

  @override
  void didUpdateWidget(_PreviewAutocomplete oldWidget) {
    super.didUpdateWidget(oldWidget);
    _sync();
  }

  void _sync() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      widget.args['open'] == true
          ? _controller.focusNode.requestFocus()
          : _controller.focusNode.unfocus();
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => SizedBox(
    width: 320,
    child: TRAutocomplete<String>(
      appearance: _fieldAppearance(widget.args),
      completionMode: TRAutocompleteCompletionMode.values.byName(
        widget.args['completionMode'] is String
            ? widget.args['completionMode']! as String
            : 'list',
      ),
      controller: _controller,
      enabled: widget.args['disabled'] != true,
      errorText: switch (widget.args['errorText']) {
        final String errorText when errorText.isNotEmpty => errorText,
        _ => null,
      },
      label: switch (widget.locale) {
        'ko' => '지역',
        'ja' => '地域',
        _ => 'Region',
      },
      placeholder: widget.args['placeholder'] is String
          ? widget.args['placeholder']! as String
          : switch (widget.locale) {
              'ko' => '지역 검색',
              'ja' => '地域を検索',
              _ => 'Search regions',
            },
      readOnly: widget.args['readOnly'] == true,
      uiSize: TRUiSize.values.byName(
        widget.args['uiSize'] is String
            ? widget.args['uiSize']! as String
            : 'md',
      ),
      items: const [
        TRAutocompleteItem(value: 'seoul', label: 'Seoul'),
        TRAutocompleteItem(value: 'tokyo', label: 'Tokyo'),
        TRAutocompleteItem(value: 'virginia', label: 'Virginia'),
      ],
    ),
  );
}

class _PreviewInlineSuggestions extends StatefulWidget {
  const _PreviewInlineSuggestions({
    required this.args,
    required this.locale,
    super.key,
  });

  final Map<String, Object?> args;
  final String locale;

  @override
  State<_PreviewInlineSuggestions> createState() =>
      _PreviewInlineSuggestionsState();
}

class _PreviewInlineSuggestionsState extends State<_PreviewInlineSuggestions> {
  final TRInlineSuggestionsController<String> _suggestions =
      TRInlineSuggestionsController<String>();
  final TextEditingController _text = TextEditingController(text: '@lib/');
  final FocusNode _focus = FocusNode();

  @override
  void dispose() {
    _suggestions.dispose();
    _text.dispose();
    _focus.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final args = widget.args;
    final disabledOption = args['disabledOption'] == true;
    final status = args['status'] is String
        ? TRInlineSuggestionsStatus.values.byName(args['status']! as String)
        : TRInlineSuggestionsStatus.ready;
    final descriptions = switch (widget.locale) {
      'ko' => <String>['앱 진입점', '컴포저 위젯', '라우터 설정', '테마 설정'],
      'ja' => <String>['アプリのエントリポイント', 'コンポーザーウィジェット', 'ルーター設定', 'テーマ設定'],
      _ => <String>[
        'Application entry point',
        'Composer widget',
        'Router configuration',
        'Theme configuration',
      ],
    };
    return SizedBox(
      width: 320,
      child: TRInlineSuggestions<String>(
        open: args['open'] == true,
        status: status,
        controller: _suggestions,
        maxVisibleItems: 2,
        emptyLabel: switch (widget.locale) {
          'ko' => '일치하는 항목 없음',
          'ja' => '一致する項目がありません',
          _ => 'No matches',
        },
        loadingLabel: switch (widget.locale) {
          'ko' => '검색 중',
          'ja' => '検索中',
          _ => 'Loading',
        },
        errorLabel: switch (widget.locale) {
          'ko' => '제안을 불러오지 못했습니다',
          'ja' => '候補を読み込めませんでした',
          _ => 'Could not load suggestions',
        },
        items: status == TRInlineSuggestionsStatus.ready
            ? <TRInlineSuggestionItem<String>>[
                TRInlineSuggestionItem<String>(
                  value: 'lib/app.dart',
                  label: 'lib/app.dart',
                  description: descriptions.first,
                  matchedIndices: const <int>[0, 1, 2],
                ),
                TRInlineSuggestionItem<String>(
                  value: 'lib/composer.dart',
                  label: 'lib/composer.dart',
                  description: descriptions[1],
                  hint: '<path>',
                  tag: 'lib',
                  enabled: !disabledOption,
                ),
                TRInlineSuggestionItem<String>(
                  value: 'lib/router.dart',
                  label: 'lib/router.dart',
                  description: descriptions[2],
                  tag: 'lib',
                ),
                TRInlineSuggestionItem<String>(
                  value: 'lib/theme.dart',
                  label: 'lib/theme.dart',
                  description: descriptions[3],
                  tag: 'lib',
                ),
              ]
            : const <TRInlineSuggestionItem<String>>[],
        onSelected: (item) => _text.text = item.value,
        child: Focus(
          onKeyEvent: (node, event) => _suggestions.handleKeyEvent(event),
          child: TRTextField(
            controller: _text,
            focusNode: _focus,
            maxLines: 3,
            minLines: 1,
          ),
        ),
      ),
    );
  }
}

class _PreviewCombobox extends StatefulWidget {
  const _PreviewCombobox({required this.args, required this.locale, super.key});

  final Map<String, Object?> args;
  final String locale;

  @override
  State<_PreviewCombobox> createState() => _PreviewComboboxState();
}

class _PreviewComboboxState extends State<_PreviewCombobox> {
  late final TRComboboxController<String> _controller =
      TRComboboxController<String>(value: 'stable');

  @override
  void initState() {
    super.initState();
    _sync();
  }

  @override
  void didUpdateWidget(_PreviewCombobox oldWidget) {
    super.didUpdateWidget(oldWidget);
    _sync();
  }

  void _sync() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      widget.args['open'] == true
          ? _controller.focusNode.requestFocus()
          : _controller.focusNode.unfocus();
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final args = widget.args;
    final disabledOption = args['disabledOption'] == true;
    return SizedBox(
      width: 320,
      child: TRCombobox<String>(
        appearance: _fieldAppearance(widget.args),
        controller: _controller,
        autoHighlight: args['autoHighlight'] == true,
        clearable: args['clearable'] == true,
        enabled: args['disabled'] != true,
        filterMode: args['filterMode'] is String
            ? TRComboboxFilterMode.values.byName(args['filterMode']! as String)
            : TRComboboxFilterMode.contains,
        label: switch (widget.locale) {
          'ko' => '채널',
          'ja' => 'チャンネル',
          _ => 'Channel',
        },
        layout: args['layout'] is String
            ? TRComboboxLayout.values.byName(args['layout']! as String)
            : TRComboboxLayout.list,
        placeholder: args['placeholder'] is String
            ? args['placeholder']! as String
            : switch (widget.locale) {
                'ko' => '채널 선택',
                'ja' => 'チャンネルを選択',
                _ => 'Choose a channel',
              },
        readOnly: args['readOnly'] == true,
        uiSize: args['uiSize'] is String
            ? TRUiSize.values.byName(args['uiSize']! as String)
            : TRUiSize.md,
        items: [
          const TRComboboxItem(value: 'stable', label: 'Stable'),
          TRComboboxItem(
            value: 'beta',
            label: 'Beta',
            enabled: !disabledOption,
          ),
        ],
      ),
    );
  }
}

class _PreviewContextMenu extends StatefulWidget {
  const _PreviewContextMenu({
    required this.args,
    required this.locale,
    super.key,
  });

  final Map<String, Object?> args;
  final String locale;

  @override
  State<_PreviewContextMenu> createState() => _PreviewContextMenuState();
}

class _PreviewContextMenuState extends State<_PreviewContextMenu> {
  final MenuController _controller = MenuController();

  @override
  void initState() {
    super.initState();
    _sync();
  }

  @override
  void didUpdateWidget(_PreviewContextMenu oldWidget) {
    super.didUpdateWidget(oldWidget);
    _sync();
  }

  void _sync() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      if (widget.args['open'] == true && !_controller.isOpen) {
        _controller.open(position: const Offset(120, 60));
      } else if (widget.args['open'] != true && _controller.isOpen) {
        _controller.close();
      }
    });
  }

  @override
  Widget build(BuildContext context) => TRContextMenu(
    controller: _controller,
    menuChildren: [
      TRMenuItem(onPressed: () {}, child: const Text('Open')),
      TRMenuItem(onPressed: () {}, child: const Text('Duplicate')),
      const TRMenuSeparator(),
      TRMenuItem(onPressed: () {}, child: const Text('Delete')),
    ],
    child: Container(
      width: 240,
      height: 120,
      alignment: Alignment.center,
      decoration: BoxDecoration(
        border: Border.all(color: context.tinyrackTheme.border),
        borderRadius: BorderRadius.circular(TRGeneratedRadii.md),
      ),
      child: Text(switch (widget.locale) {
        'ko' => '길게 누르거나 우클릭하세요',
        'ja' => '長押しまたは右クリック',
        _ => 'Long press or right-click',
      }),
    ),
  );
}

class _PreviewDrawer extends StatefulWidget {
  const _PreviewDrawer({required this.args, required this.locale, super.key});

  final Map<String, Object?> args;
  final String locale;

  @override
  State<_PreviewDrawer> createState() => _PreviewDrawerState();
}

class _PreviewDrawerState extends State<_PreviewDrawer> {
  bool _showing = false;

  TRDrawerPlacement get _placement => switch (widget.args['swipeDirection']) {
    'up' => TRDrawerPlacement.top,
    'left' => TRDrawerPlacement.start,
    'right' => TRDrawerPlacement.end,
    _ => TRDrawerPlacement.bottom,
  };

  @override
  void initState() {
    super.initState();
    _sync();
  }

  @override
  void didUpdateWidget(_PreviewDrawer oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.args != widget.args) _sync();
  }

  void _sync() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      if (widget.args['open'] == true && !_showing) {
        _show();
      } else if (widget.args['open'] != true && _showing) {
        Navigator.of(context, rootNavigator: true).maybePop();
      }
    });
  }

  Future<void> _show() async {
    _showing = true;
    await showTRDrawer<void>(
      context: context,
      placement: _placement,
      builder: (context) => TRDrawer(
        placement: _placement,
        maxExtent: switch (_placement) {
          TRDrawerPlacement.top || TRDrawerPlacement.bottom => 0.7,
          TRDrawerPlacement.start || TRDrawerPlacement.end => 1,
        },
        snapPoints: switch (_placement) {
          TRDrawerPlacement.top || TRDrawerPlacement.bottom => null,
          TRDrawerPlacement.start || TRDrawerPlacement.end => const [1],
        },
        title: const Text('Deploy settings'),
        description: const Text('Review the target before deploying.'),
        content: const Text('Channel: Stable\nRegion: Seoul'),
      ),
    );
    _showing = false;
  }

  @override
  Widget build(BuildContext context) => SizedBox(
    width: 128,
    child: TRButton(onPressed: _show, child: const Text('Open drawer')),
  );
}

class _PreviewForm extends StatefulWidget {
  const _PreviewForm({required this.args, required this.locale, super.key});

  final Map<String, Object?> args;
  final String locale;

  @override
  State<_PreviewForm> createState() => _PreviewFormState();
}

class _PreviewFormState extends State<_PreviewForm> {
  final GlobalKey<TRFormState> _formKey = GlobalKey<TRFormState>();
  String _submitted = '';

  String _pick(String en, String ko, String ja) => switch (widget.locale) {
    'ko' => ko,
    'ja' => ja,
    _ => en,
  };

  String? _validateRack(String? value) {
    if ((value ?? '').trim().isEmpty) {
      return _pick('Enter a rack name.', '랙 이름을 입력하세요.', 'ラック名を入力してください。');
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    final required = widget.args['required'] != false;
    final label = widget.args['label'] is String
        ? widget.args['label']! as String
        : _pick('Rack name', '랙 이름', 'ラック名');
    final submitLabel = widget.args['submitLabel'] is String
        ? widget.args['submitLabel']! as String
        : _pick('Save', '저장', '保存');
    return SizedBox(
      width: 320,
      child: TRForm(
        key: _formKey,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          spacing: TRSpacing.medium,
          children: [
            TRTextField(
              name: 'rack',
              label: label,
              validator: required ? _validateRack : null,
            ),
            TRButton(
              onPressed: () {
                final state = _formKey.currentState;
                if (state == null || !state.validate()) {
                  setState(() => _submitted = '');
                  return;
                }
                setState(() => _submitted = '${state.save()['rack'] ?? ''}');
              },
              child: Text(submitLabel),
            ),
            if (_submitted.isNotEmpty)
              TRText(
                '${_pick('Submitted', '제출한 값', '送信値')}: $_submitted',
                variant: TRTextVariant.bodySm,
              ),
          ],
        ),
      ),
    );
  }
}

class _PreviewNavigationMenu extends StatelessWidget {
  const _PreviewNavigationMenu({required this.args, super.key});

  final Map<String, Object?> args;

  @override
  Widget build(BuildContext context) => TRNavigationMenu<String>.controlled(
    value: args['open'] == true ? 'products' : null,
    panelWidth: 322,
    items: const [
      TRNavigationMenuItem(
        value: 'products',
        trigger: SizedBox(
          width: 70,
          child: Text('Products', maxLines: 1, softWrap: false),
        ),
        content: Text('Compute\nStorage\nNetworking'),
      ),
      TRNavigationMenuItem(
        value: 'resources',
        trigger: SizedBox(
          width: 81,
          child: Text('Resources', maxLines: 1, softWrap: false),
        ),
        content: Text('Documentation\nExamples\nSupport'),
      ),
    ],
  );
}

class _PreviewPopover extends StatelessWidget {
  const _PreviewPopover({required this.args, super.key});

  final Map<String, Object?> args;

  @override
  Widget build(BuildContext context) => TRPopover.controlled(
    open: args['open'] == true,
    trigger: SizedBox(
      width: 128,
      child: TRButton(onPressed: () {}, child: const Text('Rack details')),
    ),
    title: const Text('Rack alpha'),
    description: const Text('4 services are healthy.'),
    content: const Text('Latency 18 ms'),
    width: 165,
  );
}

class _PreviewPreviewCard extends StatelessWidget {
  const _PreviewPreviewCard({required this.args, super.key});

  final Map<String, Object?> args;

  @override
  Widget build(BuildContext context) => TRPreviewCard.controlled(
    open: args['open'] == true,
    trigger: SizedBox(
      width: 105,
      child: TRButton(onPressed: () {}, child: const Text('Rack alpha')),
    ),
    content: const Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      mainAxisSize: MainAxisSize.min,
      children: [
        TRLayerPartBoundary(name: 'title', child: Text('Rack alpha')),
        TRLayerPartBoundary(
          name: 'description',
          child: Text('4 services are healthy.'),
        ),
      ],
    ),
    width: 165,
  );
}

class _PreviewTooltip extends StatelessWidget {
  const _PreviewTooltip({required this.args, required this.locale, super.key});

  final Map<String, Object?> args;
  final String locale;

  @override
  Widget build(BuildContext context) => TRTooltip.controlled(
    open: args['open'] == true,
    message: switch (locale) {
      'ko' => '랙 새로고침',
      'ja' => 'ラックを更新',
      _ => 'Refresh rack',
    },
    width: 95,
    child: TRIconButton(
      icon: const Icon(Icons.refresh),
      label: 'Refresh rack',
      onPressed: () {},
    ),
  );
}

class _PreviewToast extends StatefulWidget {
  const _PreviewToast({
    required this.locale,
    required this.measureKey,
    required this.open,
  });

  final String locale;
  final Key measureKey;
  final bool open;

  @override
  State<_PreviewToast> createState() => _PreviewToastState();
}

class _PreviewToastState extends State<_PreviewToast> {
  late final TRToastController _controller = TRToastController();

  @override
  void initState() {
    super.initState();
    _sync();
  }

  @override
  void didUpdateWidget(_PreviewToast oldWidget) {
    super.didUpdateWidget(oldWidget);
    _sync();
  }

  void _sync() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      if (widget.open && _controller.toasts.isEmpty) {
        _controller.show(
          TRToastData(
            title: Text(switch (widget.locale) {
              'ko' => '변경 사항을 저장했어요',
              'ja' => '変更を保存しました',
              _ => 'Changes saved',
            }),
            description: const Text('Rack alpha is up to date.'),
            duration: Duration.zero,
            variant: TRStatusVariant.success,
          ),
        );
      } else if (!widget.open) {
        _controller.dismissAll();
      }
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => TRToastRegion(
    controller: _controller,
    child: Center(
      child: SizedBox(
        key: widget.measureKey,
        width: 128,
        child: TRButton(
          onPressed: () => _controller.show(
            const TRToastData(
              title: Text('Deployment started'),
              description: Text('Stable channel is updating.'),
            ),
          ),
          child: const Text('Show toast'),
        ),
      ),
    ),
  );
}

class _PreviewMenubar extends StatefulWidget {
  const _PreviewMenubar({
    required this.args,
    required this.onStateChanged,
    super.key,
  });

  final Map<String, Object?> args;
  final ValueChanged<Map<String, Object?>> onStateChanged;

  @override
  State<_PreviewMenubar> createState() => _PreviewMenubarState();
}

class _PreviewMenubarState extends State<_PreviewMenubar> {
  final MenuController _fileController = MenuController();

  @override
  void initState() {
    super.initState();
    _syncOpenState();
  }

  @override
  void didUpdateWidget(_PreviewMenubar oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.args['open'] != widget.args['open']) _syncOpenState();
  }

  void _syncOpenState() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      final shouldOpen = widget.args['open'] == true;
      if (shouldOpen && !_fileController.isOpen) _fileController.open();
      if (!shouldOpen && _fileController.isOpen) _fileController.close();
    });
  }

  @override
  Widget build(BuildContext context) => TRMenubar(
    semanticLabel: 'Application',
    menus: [
      TRMenubarMenu(
        controller: _fileController,
        onClose: () => widget.onStateChanged({'open': false}),
        onOpen: () => widget.onStateChanged({'open': true}),
        trigger: const SizedBox(width: 27, child: Center(child: Text('File'))),
        menuChildren: [
          TRMenuItem(onPressed: () {}, child: const Text('New rack')),
          TRMenuItem(onPressed: () {}, child: const Text('Open')),
        ],
      ),
      TRMenubarMenu(
        trigger: const SizedBox(width: 37, child: Center(child: Text('View'))),
        menuChildren: [
          TRMenuItem(onPressed: () {}, child: const Text('Refresh')),
        ],
      ),
    ],
  );
}

class _PreviewMenu extends StatefulWidget {
  const _PreviewMenu({
    required this.args,
    required this.locale,
    required this.measureKey,
    required this.onStateChanged,
    required this.partKeys,
  });

  final Map<String, Object?> args;
  final String locale;
  final Key measureKey;
  final ValueChanged<Map<String, Object?>> onStateChanged;
  final Map<String, GlobalKey> partKeys;

  @override
  State<_PreviewMenu> createState() => _PreviewMenuState();
}

class _PreviewMenuState extends State<_PreviewMenu> {
  final MenuController _controller = MenuController();
  bool _grid = true;
  String _density = 'compact';

  @override
  void initState() {
    super.initState();
    _syncOpenState();
  }

  @override
  void didUpdateWidget(_PreviewMenu oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.args['open'] != widget.args['open']) _syncOpenState();
  }

  void _syncOpenState() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      final shouldOpen = widget.args['open'] == true;
      if (shouldOpen && !_controller.isOpen) _controller.open();
      if (!shouldOpen) {
        if (_controller.isOpen) _controller.close();
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (mounted && widget.args['open'] != true) {
            FocusManager.instance.primaryFocus?.unfocus();
          }
        });
      }
    });
  }

  String _pick(String en, String ko, String ja) => switch (widget.locale) {
    'ko' => ko,
    'ja' => ja,
    _ => en,
  };

  @override
  Widget build(BuildContext context) {
    final popupKey = widget.partKeys.putIfAbsent('popup', GlobalKey.new);
    return SizedBox(
      key: widget.measureKey,
      width: TRGeneratedMeasurements.measureXs,
      child: TRMenu(
        controller: _controller,
        enabled: widget.args['disabled'] != true,
        onClose: () => widget.onStateChanged({'open': false}),
        onOpen: () => widget.onStateChanged({'open': true}),
        trigger: Text(
          _pick('View', '보기', '表示'),
          key: widget.partKeys.putIfAbsent('triggerLabel', GlobalKey.new),
        ),
        menuChildren: [
          KeyedSubtree(
            key: popupKey,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              mainAxisSize: MainAxisSize.min,
              children: [
                TRMenuGroupLabel(
                  child: Text(
                    _pick('Layout', '레이아웃', 'レイアウト'),
                    key: widget.partKeys.putIfAbsent(
                      'groupLabel',
                      GlobalKey.new,
                    ),
                  ),
                ),
                TRMenuCheckboxItem(
                  value: _grid,
                  onChanged: (value) => setState(() => _grid = value ?? false),
                  child: Text(
                    _pick('Show grid', '격자 표시', 'グリッドを表示'),
                    key: widget.partKeys.putIfAbsent(
                      'checkboxLabel',
                      GlobalKey.new,
                    ),
                  ),
                ),
                TRMenuRadioItem<String>(
                  value: 'compact',
                  groupValue: _density,
                  onChanged: (value) => setState(() => _density = value!),
                  child: Text(
                    _pick('Compact', '좁게', 'コンパクト'),
                    key: widget.partKeys.putIfAbsent(
                      'radioLabel',
                      GlobalKey.new,
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class _PreviewSelect extends StatefulWidget {
  const _PreviewSelect({
    required this.args,
    required this.locale,
    required this.onStateChanged,
    required this.partKeys,
    required this.size,
    super.key,
  });

  final Map<String, Object?> args;
  final String locale;
  final ValueChanged<Map<String, Object?>> onStateChanged;
  final Map<String, GlobalKey> partKeys;
  final TRUiSize size;

  @override
  State<_PreviewSelect> createState() => _PreviewSelectState();
}

class _PreviewSelectState extends State<_PreviewSelect> {
  final MenuController _controller = MenuController();
  String? _value;

  @override
  void initState() {
    super.initState();
    final value = widget.args['value'];
    _value = value is String ? (value.isEmpty ? null : value) : 'stable';
    _syncOpenState();
  }

  @override
  void didUpdateWidget(_PreviewSelect oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.args['value'] != widget.args['value']) {
      final value = widget.args['value'];
      _value = value is String ? (value.isEmpty ? null : value) : 'stable';
    }
    if (oldWidget.args['open'] != widget.args['open']) _syncOpenState();
  }

  void _syncOpenState() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      final shouldOpen = widget.args['open'] == true;
      if (shouldOpen && !_controller.isOpen) _controller.open();
      if (!shouldOpen) {
        if (_controller.isOpen) _controller.close();
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (mounted && widget.args['open'] != true) {
            FocusManager.instance.primaryFocus?.unfocus();
          }
        });
      }
    });
  }

  String _pick(String en, String ko, String ja) => switch (widget.locale) {
    'ko' => ko,
    'ja' => ja,
    _ => en,
  };

  @override
  Widget build(BuildContext context) => SizedBox(
    width: 320,
    child: TRSelect<String>.controlled(
      leading: const Icon(Icons.layers),
      appearance: _fieldAppearance(widget.args),
      items: [
        TRSelectItem(
          key: const ValueKey('release-channel-stable'),
          value: 'stable',
          label: _pick('Stable', '안정', '安定版'),
          trailing: KeyedSubtree(
            key: widget.partKeys.putIfAbsent('popup', GlobalKey.new),
            child: const Icon(Icons.check, size: TRGeneratedSpacing.lg),
          ),
        ),
        TRSelectItem(
          key: const ValueKey('release-channel-beta'),
          value: 'beta',
          label: _pick('Beta', '베타', 'ベータ'),
        ),
      ],
      value: _value,
      enabled: widget.args['disabled'] != true,
      errorText:
          widget.args['errorText'] is String &&
              (widget.args['errorText']! as String).isNotEmpty
          ? widget.args['errorText']! as String
          : null,
      menuController: _controller,
      onClose: () => widget.onStateChanged({'open': false}),
      onOpen: () => widget.onStateChanged({'open': true}),
      onValueChange: (value) {
        setState(() => _value = value);
        widget.onStateChanged({
          'args': {'value': value ?? ''},
        });
      },
      placeholder: _pick('Choose a channel', '채널 선택', 'チャンネルを選択'),
      readOnly: widget.args['readOnly'] == true,
      searchable: widget.args['searchable'] == true,
      searchPlaceholder: _pick('Search channels', '채널 검색', 'チャンネルを検索'),
      // The playground drives `open` through a MenuController, which only the
      // dropdown answers, so the surface is pinned rather than left to the
      // preview frame's width.
      surface: TRSelectSurface.menu,
      uiSize: widget.size,
      width: 320,
    ),
  );
}

class _PreviewDialog extends StatefulWidget {
  const _PreviewDialog({
    required this.args,
    required this.locale,
    required this.onStateChanged,
    required this.partKeys,
    super.key,
  });

  final Map<String, Object?> args;
  final String locale;
  final ValueChanged<Map<String, Object?>> onStateChanged;
  final Map<String, GlobalKey> partKeys;

  @override
  State<_PreviewDialog> createState() => _PreviewDialogState();
}

class _PreviewDialogState extends State<_PreviewDialog> {
  bool _routeOpen = false;
  NavigatorState? _rootNavigator;

  @override
  void initState() {
    super.initState();
    if (widget.args['open'] == true) _scheduleShow();
  }

  @override
  void didUpdateWidget(_PreviewDialog oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.args['open'] != true && widget.args['open'] == true) {
      _scheduleShow();
    } else if (oldWidget.args['open'] == true &&
        widget.args['open'] != true &&
        _routeOpen) {
      Navigator.of(context, rootNavigator: true).maybePop();
    }
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _rootNavigator = Navigator.of(context, rootNavigator: true);
  }

  @override
  void dispose() {
    final navigator = _rootNavigator;
    if (_routeOpen && navigator != null) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (navigator.mounted && navigator.canPop()) navigator.pop();
      });
    }
    super.dispose();
  }

  void _scheduleShow() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (mounted && !_routeOpen) _show();
    });
  }

  String _pick(String en, String ko, String ja) => switch (widget.locale) {
    'ko' => ko,
    'ja' => ja,
    _ => en,
  };

  Future<void> _show() async {
    if (_routeOpen) return;
    setState(() => _routeOpen = true);
    widget.onStateChanged({'open': true});
    final placement = TRDialogPlacement.values.byName(
      widget.args['placement'] is String
          ? widget.args['placement']! as String
          : 'middle',
    );
    await showTRDialog<void>(
      context: context,
      builder: (dialogContext) => TRDialog(
        placement: placement,
        title: Text(
          _pick('Deploy rack?', '랙을 배포할까요?', 'ラックをデプロイしますか?'),
          key: widget.partKeys.putIfAbsent('popup', GlobalKey.new),
        ),
        description: Text(
          _pick(
            'The stable channel will be updated.',
            '안정 채널이 업데이트돼요.',
            '安定版チャンネルが更新されます。',
          ),
        ),
        content: Text(_pick('Stable', '안정', '安定版')),
        actions: Wrap(
          spacing: TRSpacing.small,
          children: [
            TRButton(
              appearance: TRAppearance.ghost,
              onPressed: () => Navigator.pop(dialogContext),
              child: SizedBox(
                width: TRGeneratedMeasurements.measureXs,
                height: TRGeneratedControlMetrics.mdLineHeight,
                key: widget.partKeys.putIfAbsent('cancelLabel', GlobalKey.new),
                child: Center(child: Text(_pick('Cancel', '취소', 'キャンセル'))),
              ),
            ),
            TRButton(
              intent: TRIntent.primary,
              onPressed: () => Navigator.pop(dialogContext),
              child: SizedBox(
                width: TRGeneratedMeasurements.measureXs,
                height: TRGeneratedControlMetrics.mdLineHeight,
                key: widget.partKeys.putIfAbsent('actionLabel', GlobalKey.new),
                child: Center(child: Text(_pick('Deploy', '배포', 'デプロイ'))),
              ),
            ),
          ],
        ),
      ),
    );
    if (!mounted) return;
    setState(() => _routeOpen = false);
    widget.onStateChanged({'open': false});
  }

  @override
  Widget build(BuildContext context) => SizedBox(
    width: TRGeneratedMeasurements.measureSm,
    child: TRButton(
      onPressed: _show,
      child: SizedBox(
        width: TRGeneratedMeasurements.measureXs + TRGeneratedSpacing.lg,
        height: TRGeneratedControlMetrics.mdLineHeight,
        key: widget.partKeys.putIfAbsent('triggerLabel', GlobalKey.new),
        child: Center(
          child: Text(_pick('Open dialog', '다이얼로그 열기', 'ダイアログを開く')),
        ),
      ),
    ),
  );
}

class _PreviewPlusIcon extends StatelessWidget {
  const _PreviewPlusIcon({super.key});

  @override
  Widget build(BuildContext context) {
    final color = IconTheme.of(context).color;
    return SizedBox.square(
      dimension: 16,
      child: Stack(
        alignment: Alignment.center,
        children: [
          SizedBox(width: 8, height: 2, child: ColoredBox(color: color!)),
          SizedBox(width: 2, height: 8, child: ColoredBox(color: color)),
        ],
      ),
    );
  }
}

class _PreviewStatusIcon extends StatelessWidget {
  const _PreviewStatusIcon({super.key});

  @override
  Widget build(BuildContext context) => SizedBox.square(
    dimension: 16,
    child: DecoratedBox(
      decoration: BoxDecoration(
        border: Border.all(color: IconTheme.of(context).color!, width: 2),
        shape: BoxShape.circle,
      ),
    ),
  );
}
0
likes
0
points
2.27k
downloads

Publisher

verified publishertinyrack.net

Weekly Downloads

Cross-platform Flutter components and design tokens for Tinyrack applications.

Homepage
Repository (GitHub)
View/report issues

Topics

#design-system #flutter #material #theme

License

unknown (license)

Dependencies

analyzer, flutter, intl, lucide_flutter, material_ui, qr

More

Packages that depend on tinyrack_ui

Packages that implement tinyrack_ui