diff --git a/.cspell-project-words.txt b/.cspell-project-words.txt index a3a6be5a032a505498aefc21bc13468bffbd203e..1c342aba563869aadf3c9d37a7d037ee542b47a7 100644 --- a/.cspell-project-words.txt +++ b/.cspell-project-words.txt @@ -15,3 +15,4 @@ sdc's subcomponent toolset daisyui +uips \ No newline at end of file diff --git a/css/color.css b/css/color.css new file mode 100644 index 0000000000000000000000000000000000000000..aa559542155c8c7e97dd48b61484aa08889efd69 --- /dev/null +++ b/css/color.css @@ -0,0 +1,23 @@ +.uips-layout-color { + display: grid !important; + grid-template-columns: repeat(auto-fill, 32px) !important; + gap: 10px !important; +} + +.uips-layout-color .uips-radio-item { + width: 100%; + aspect-ratio: 1 / 1; + cursor: pointer; + border-radius: 50%; +} + +.uips-layout-color .form-radio { + display: none; +} + +.uips-layout-color .form-radio:checked + .uips-radio-item { + border: 2px solid black; + box-shadow: + 0 0 2px #fff, + 0 0 0 4px #222330; +} diff --git a/css/grid.css b/css/grid.css new file mode 100644 index 0000000000000000000000000000000000000000..c26565202aa2ea09a6b9b516145ab2502fe250d7 --- /dev/null +++ b/css/grid.css @@ -0,0 +1,22 @@ +.uips-layout-grid { + display: grid !important; + grid-template-columns: repeat(auto-fill, 64px) !important; + gap: 10px !important; +} + +.uips-layout-grid .uips-radio-item { + display: flex; + align-items: center; + justify-content: center; + aspect-ratio: 1 / 1; + width: 100%; + cursor: pointer; +} + +.uips-layout-grid .form-radio { + display: none; +} + +.uips-layout-grid .form-radio:checked + .uips-radio-item { + border: 2px solid black; +} diff --git a/src/Plugin/UiPatterns/Source/EnumWidget.php b/src/Plugin/UiPatterns/Source/EnumWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..c78d3d7219421588948efb518e11e6fa35d78eae --- /dev/null +++ b/src/Plugin/UiPatterns/Source/EnumWidget.php @@ -0,0 +1,159 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\ui_patterns_settings\Plugin\UiPatterns\Source; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Render\Element; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\ui_patterns\Attribute\Source; +use Drupal\ui_patterns\EnumTrait; +use Drupal\ui_patterns\Plugin\UiPatterns\Source\SelectWidget; + +/** + * Enum widget source. + */ +#[Source( + id: 'enum_widget', + label: new TranslatableMarkup('Enum CSS widget'), + description: new TranslatableMarkup('A drop-down menu or scrolling selection box.'), + prop_types: ['enum', 'variant'], + tags: ['widget'] +)] +class EnumWidget extends SelectWidget { + + use EnumTrait; + + /** + * Alter enum options. + */ + protected function enumAlterOptions(array $options, array $configure): array { + $overwrite_options = $configure['overwrite'] ?? []; + foreach ($overwrite_options as $overwrite_option_id => $overwrite_option) { + if ($overwrite_option['enable'] == '0') { + unset($options[$overwrite_option_id]); + continue; + } + if (!empty($overwrite_option['title'])) { + $options[$overwrite_option_id] = $overwrite_option['title']; + } + } + return $options; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state): array { + $form = parent::settingsForm($form, $form_state); + $form['value']['#options'] = $this->enumAlterOptions($form['value']['#options'], $this->getConfiguration()['configure'] ?? []); + $form['#attached']['library'][] = 'ui_patterns_settings/layout'; + $form['#attached']['library'][] = 'ui_patterns_settings/color'; + $widget = $this->getWidgetSetting('widget'); + $widget_configs = [ + 'select' => ['type' => 'select'], + 'colors' => [ + 'type' => 'radios', + 'wrapper_classes' => 'uips-layout-color', + 'library' => ['ui_patterns_settings/color'], + ], + '4_grid' => [ + 'type' => 'radios', + 'wrapper_classes' => 'uips-layout-grid', + 'library' => ['ui_patterns_settings/grid'], + ], + ]; + $widget_config = $widget_configs[$widget]; + $form['value']['#type'] = $widget_config['type']; + $configure = $this->getEnumConfiguration(); + $configure['css']['wrapper_classes'] .= $widget_config['wrapper_classes'] ?? ''; + $form['value']['#enum_widget'] = $configure; + if ($form['value']['#type'] === 'select') { + $form['value']['#theme'] = 'ui_patterns_settings_select_widget'; + } + elseif ($form['value']['#type'] === 'radios') { + $form['value']['#pre_render'][] = function ($element) use ($configure) { + foreach (Element::children($element) as $key) { + $element[$key]['#theme_wrappers'][0] = ['ui_patterns_settings_radio_wrapper_widget']; + $element[$key]['#theme'] = ['ui_patterns_settings_radio_widget']; + $element[$key]['#enum_widget'] = $configure; + } + return $element; + }; + } + $form['value']['#attached']['library'] = $widget_config['library'] ?? []; + return $form; + } + + /** + * Returns base enum configuration. + */ + private function getEnumConfiguration(): array { + return [ + 'widget' => $this->getWidgetSetting('widget'), + 'overwrite' => $this->getWidgetSetting('overwrite'), + 'css' => $this->getWidgetSetting('css'), + ]; + } + + /** + * {@inheritdoc} + */ + public function widgetSettingsForm(array $form, FormStateInterface $form_state): array { + $form = parent::widgetSettingsForm($form, $form_state); + $configure = $this->getEnumConfiguration(); + $options = self::getEnumOptions($this->getPropDefinition()); + + $form['widget'] = [ + '#type' => 'select', + '#title' => $this->t('Widget'), + '#description' => $this->t('Enum widget type.'), + '#options' => [ + 'select' => $this->t('Select'), + 'colors' => $this->t('Colors'), + '4_grid' => $this->t('4 Grid / Eg. Icons'), + ], + '#default_value' => $configure['widget'] ?? 'select', + ]; + + $form['css']['wrapper_classes'] = [ + '#type' => 'textfield', + '#title' => $this->t('Wrapper CSS classes'), + '#default_value' => $configure['css']['wrapper_classes'] ?? '', + '#description' => $this->t('Classes assigned to wrapping select element'), + ]; + + foreach ($options as $option_id => $label) { + $form['overwrite'][$option_id] = [ + '#type' => 'details', + '#title' => $this->t('Option: %label', ['%label' => $label]), + ]; + $form['overwrite'][$option_id]['enable'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Enable'), + '#default_value' => $configure['overwrite'][$option_id]['enable'] ?? TRUE, + ]; + $form['overwrite'][$option_id]['title'] = [ + '#type' => 'textfield', + '#title' => $this->t('Title'), + '#default_value' => $configure['overwrite'][$option_id]['title'] ?? '', + ]; + $form['overwrite'][$option_id]['icon'] = [ + '#type' => 'icon_autocomplete', + '#title' => $this->t('Icon'), + '#return_id' => TRUE, + '#description' => $this->t('May not work with your widget.'), + '#default_value' => $configure['overwrite'][$option_id]['icon']['target_id'] ?? '', + ]; + $form['overwrite'][$option_id]['css'] = [ + '#type' => 'textfield', + '#title' => $this->t('CSS'), + '#default_value' => $configure['overwrite'][$option_id]['css'] ?? '', + ]; + } + return $form; + + } + +} diff --git a/templates/ui-patterns-settings-radio-widget.html.twig b/templates/ui-patterns-settings-radio-widget.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..627ae719964df85cc98a5ecfc655c26fae764a26 --- /dev/null +++ b/templates/ui-patterns-settings-radio-widget.html.twig @@ -0,0 +1,7 @@ +<input{{ attributes }} />{{ children }} +<div{{ layout_attributes.addClass('uips-radio-item') }}> + {% if icon_id is not empty %} + {{ icon_preview(pack_id, icon_id) }} + {% endif %} +</div> + diff --git a/templates/ui-patterns-settings-radio-wrapper-widget.html.twig b/templates/ui-patterns-settings-radio-wrapper-widget.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..e7ba34d72fdabb93daef84e2a70e56d854a13af4 --- /dev/null +++ b/templates/ui-patterns-settings-radio-wrapper-widget.html.twig @@ -0,0 +1,76 @@ + +{# +/** + * @file + * Default theme implementation for a form element. + * + * Available variables: + * - attributes: HTML attributes for the containing element. + * - errors: (optional) Any errors for this form element, may not be set. + * - prefix: (optional) The form element prefix, may not be set. + * - suffix: (optional) The form element suffix, may not be set. + * - required: The required marker, or empty if the associated form element is + * not required. + * - type: The type of the element. + * - name: The name of the element. + * - label: A rendered label element. + * - label_display: Label display setting. It can have these values: + * - before: The label is output before the element. This is the default. + * The label includes the #title and the required marker, if #required. + * - after: The label is output after the element. For example, this is used + * for radio and checkbox #type elements. If the #title is empty but the + * field is #required, the label will contain only the required marker. + * - invisible: Labels are critical for screen readers to enable them to + * properly navigate through forms but can be visually distracting. This + * property hides the label for everyone except screen readers. + * - attribute: Set the title attribute on the element to create a tooltip but + * output no label element. This is supported only for checkboxes and radios + * in \Drupal\Core\Render\Element\CompositeFormElementTrait::preRenderCompositeFormElement(). + * It is used where a visual label is not needed, such as a table of + * checkboxes where the row and column provide the context. The tooltip will + * include the title and required marker. + * - description: (optional) A list of description properties containing: + * - content: A description of the form element, may not be set. + * - attributes: (optional) A list of HTML attributes to apply to the + * description content wrapper. Will only be set when description is set. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element. This is the default + * value. + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. + * - disabled: True if the element is disabled. + * - title_display: Title display setting. + * + * @see template_preprocess_form_element() + * + * @ingroup themeable + */ +#} +{% + set classes = [ + 'js-form-item', + 'form-item', + 'js-form-type-' ~ type|clean_class, + 'form-item-' ~ name|clean_class, + 'js-form-item-' ~ name|clean_class, + title_display not in ['after', 'before'] ? 'form-no-label', + disabled == 'disabled' ? 'form-disabled', + errors ? 'form-item--error', +] +%} +{% + set description_classes = [ + 'description', + description_display == 'invisible' ? 'visually-hidden', +] +%} +<label{{ attributes.addClass(classes) }}> + + {{ children }} + {% if errors %} + <div class="form-item--error-message"> + {{ errors }} + </div> + {% endif %} +</label> diff --git a/templates/ui-patterns-settings-select-widget.html.twig b/templates/ui-patterns-settings-select-widget.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..ba8834450730c668edcbd6ee759a0ded2e01d06e --- /dev/null +++ b/templates/ui-patterns-settings-select-widget.html.twig @@ -0,0 +1,27 @@ +{# +/** + * @file + * Default theme implementation for a select element. + * + * Available variables: + * - attributes: HTML attributes for the <select> tag. + * - options: The <option> element children. + * + * @see template_preprocess_select() + * + * @ingroup themeable + */ +#} +<select{{ attributes }}> + {% for option in options %} + {% if option.type == 'optgroup' %} + <optgroup label="{{ option.label }}"> + {% for sub_option in option.options %} + <option{{ option.attributes }} value="{{ sub_option.value }}"{{ sub_option.selected ? ' selected="selected"' }}>{{ sub_option.label }}</option> + {% endfor %} + </optgroup> + {% elseif option.type == 'option' %} + <option{{ option.attributes }} value="{{ option.value }}"{{ option.selected ? ' selected="selected"' }}>{{ option.label }}</option> + {% endif %} + {% endfor %} +</select> diff --git a/ui_patterns_settings.libraries.yml b/ui_patterns_settings.libraries.yml new file mode 100644 index 0000000000000000000000000000000000000000..37e7cada3080064fc641fe0da888f3448071a1b1 --- /dev/null +++ b/ui_patterns_settings.libraries.yml @@ -0,0 +1,8 @@ +color: + css: + theme: + css/color.css: {} +grid: + css: + theme: + css/grid.css: {} diff --git a/ui_patterns_settings.module b/ui_patterns_settings.module index 68201a60a12604fe6c06a558e462fdbc5a78fbf2..e55ca90a8b9c395eb1a7aa80271c294ddab9801d 100644 --- a/ui_patterns_settings.module +++ b/ui_patterns_settings.module @@ -4,3 +4,77 @@ * @file * Contains ui_patterns_settings.module. */ + +use Drupal\Core\Render\Element; +use Drupal\Core\Render\Element\RenderElementBase; +use Drupal\Core\Template\Attribute; + +/** + * Implements hook_theme(). + */ +function ui_patterns_settings_theme(): array { + return [ + 'ui_patterns_settings_select_widget' => [ + 'render element' => 'element', + ], + 'ui_patterns_settings_radio_wrapper_widget' => [ + 'render element' => 'element', + ], + 'ui_patterns_settings_radio_widget' => [ + 'render element' => 'element', + ], + ]; +} + +/** + * Implements hook_preprocess_ui_patterns_settings_select_widget(). + */ +function template_preprocess_ui_patterns_settings_select_widget(array &$variables): void { + $element = $variables['element']; + Element::setAttributes($element, ['id', 'name', 'size']); + RenderElementBase::setAttributes($element, ['form-select']); + $variables['attributes'] = $element['#attributes']; + $options = form_select_options($element); + foreach ($options as $delta => $option) { + if (isset($element['#enum_widget']['overwrite'][$option['value']]['css'])) { + $options[$delta]['attributes'] = new Attribute( + ['class' => $element['#enum_widget']['overwrite'][$option['value']]['css']] + ); + + } + if (isset($element['#enum_widget']['overwrite'][$option['value']]['icon']['target_id'])) { + [$options[$delta]['pack_id'], $options[$delta]['icon_id']] = explode(':', $element['#enum_widget']['overwrite'][$option['value']]['icon']['target_id'] ?? ''); + } + } + $variables['options'] = $options; +} + +/** + * Implements hook_radios(). + */ +function ui_patterns_settings_preprocess_radios(array &$variables): void { + if (isset($variables['element']['#enum_widget'])) { + $variables['attributes']['class'][] = $variables['element']['#enum_widget']['css']['wrapper_classes']; + $variables['attributes']['class'][] = 'ck-reset'; + + } +} + +/** + * Implements hook_preprocess_ui_patterns_settings_radio_wrapper_widget(). + */ +function template_preprocess_ui_patterns_settings_radio_wrapper_widget(array &$variables): void { + template_preprocess_form_element($variables); +} + +/** + * Implements hook_preprocess_ui_patterns_settings_radio_widget(). + */ +function template_preprocess_ui_patterns_settings_radio_widget(array &$variables): void { + template_preprocess_input($variables); + $element = &$variables['element']; + $variables['layout_attributes'] = new Attribute(); + $css_config = $element['#enum_widget']['overwrite'][$element['#return_value']]['css'] ?? NULL; + $variables['layout_attributes']->addClass($css_config); + [$variables['pack_id'], $variables['icon_id']] = explode(':', $element['#enum_widget']['overwrite'][$element['#return_value']]['icon']['target_id'] ?? ''); +}