Browse Source

icons a type styles

master
rdrew 1 week ago
parent
commit
2be1e4e32c
  1. 10
      css/base.css
  2. 25
      css/components/breadcrumb.css
  3. 89
      css/components/button.css
  4. 2
      css/components/dialog.css
  5. 25
      css/components/footer.css
  6. 42
      css/components/form.css
  7. 54
      css/components/pager.css
  8. 50
      css/components/search.css
  9. 4
      css/components/tabs.css
  10. 26
      css/tokens.css
  11. 64
      druid.icons.yml
  12. 2
      druid.libraries.yml
  13. 4
      icons/magnifying-glass.svg
  14. 15
      templates/block/block--search-form-block.html.twig
  15. 2
      templates/layout/page.html.twig

10
css/base.css

@ -12,7 +12,7 @@
background: var(--surface-1); background: var(--surface-1);
color: var(--text-1); color: var(--text-1);
font-family: var(--font-sans); font-family: var(--font-sans);
line-height: 1.5; line-height: var(--font-lineheight-3);
} }
/* Headings use the serif token (Adelle). Drupal core inserts <h1><h6> /* Headings use the serif token (Adelle). Drupal core inserts <h1><h6>
@ -21,7 +21,15 @@
component or utility if a specific UI surface needs the sans family. */ component or utility if a specific UI surface needs the sans family. */
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-family: var(--font-serif); font-family: var(--font-serif);
line-height: var(--font-lineheight-1);
margin-block: var(--size-3);
} }
h1 { font-size: var(--font-size-6); }
h2 { font-size: var(--font-size-5); }
h3 { font-size: var(--font-size-4); }
h4 { font-size: var(--font-size-3); }
h5 { font-size: var(--font-size-2); }
h6 { font-size: var(--font-size-1); }
a { a {
color: var(--brand); color: var(--brand);

25
css/components/breadcrumb.css

@ -1,19 +1,24 @@
@layer components {
/** /**
* @file * @file
* Styles for breadcrumbs. * Styles for breadcrumbs.
*
* Markup from templates/navigation/breadcrumb.html.twig:
* <nav class="breadcrumb"><ol><li><a></a></li><li>current</li></ol></nav>
* The current page is the last <li> and is not a link.
*/ */
@layer components {
.breadcrumb { .breadcrumb {
padding-bottom: 0.5em; padding-bottom: 0.5em;
font-size: 0.875em;
color: var(--text-2);
} }
.breadcrumb ol { .breadcrumb ol {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
[dir="rtl"] .breadcrumb ol { [dir="rtl"] .breadcrumb ol {
/* This is required to win over specificity of [dir="rtl"] ol */ /* Wins over specificity of [dir="rtl"] ol. */
margin-right: 0; margin-right: 0;
} }
.breadcrumb li { .breadcrumb li {
@ -22,12 +27,24 @@
padding: 0; padding: 0;
list-style-type: none; list-style-type: none;
} }
/* IE8 does not support :not() and :last-child. */
.breadcrumb li::before { .breadcrumb li::before {
margin-inline: var(--size-2);
content: "\BB"; content: "\BB";
color: var(--text-2);
} }
.breadcrumb li:first-child::before { .breadcrumb li:first-child::before {
content: none; content: none;
} }
.breadcrumb a {
color: var(--text-2);
text-decoration: none;
}
.breadcrumb a:hover {
color: var(--upei-red);
text-decoration: underline;
}
.breadcrumb li:last-child {
color: var(--text-1);
}
} }

89
css/components/button.css

@ -1,19 +1,92 @@
@layer components {
/** /**
* @file * @file
* Visual styles for buttons. * Visual styles for buttons.
*
* Drupal applies .button and typically .button--primary for the main
* action automatically to <input type="submit">, <button>, and
* <a class="button"> via core RenderElement processing.
*
* Variants commonly emitted by core/contrib:
* .button base, neutral
* .button--primary primary action (form submit defaults to this)
* .button--danger destructive action
* .button--small compact size
* .button[disabled] disabled state (.is-disabled in some contexts)
*/ */
@layer components {
.button, .button,
.image-button { .image-button {
margin-right: 1em; display: inline-block;
margin-left: 1em; padding: var(--size-2) var(--size-3);
border: 1px solid var(--text-2);
border-radius: var(--radius-2);
background-color: var(--surface-1);
color: var(--text-1);
font: inherit;
line-height: 1.2;
text-decoration: none;
cursor: pointer;
transition:
background-color 0.15s var(--ease-out-2),
border-color 0.15s var(--ease-out-2),
color 0.15s var(--ease-out-2);
}
.button:not(:first-child),
.image-button:not(:first-child) {
margin-inline-start: var(--size-3);
}
.button:hover,
.image-button:hover {
background-color: var(--surface-2);
border-color: var(--text-1);
}
.button:focus,
.image-button:focus {
outline: 2px solid var(--upei-red);
outline-offset: 2px;
}
/* Primary — brand-colored. Form submits typically get this class. */
.button--primary {
background-color: var(--upei-red);
border-color: var(--upei-red);
color: white;
}
.button--primary:hover {
background-color: var(--upei-dark-red);
border-color: var(--upei-dark-red);
}
/* Danger — destructive action. Outlined by default, fills on hover. */
.button--danger {
background-color: var(--surface-1);
border-color: var(--upei-red);
color: var(--upei-red);
}
.button--danger:hover {
background-color: var(--upei-red);
color: white;
}
/* Small variant. */
.button--small {
padding: var(--size-1) var(--size-2);
font-size: 0.875em;
}
/* Disabled. */
.button[disabled],
.button.is-disabled,
.image-button[disabled] {
opacity: 0.5;
cursor: not-allowed;
} }
.button:first-child, .button[disabled]:hover,
.image-button:first-child { .button.is-disabled:hover,
margin-right: 0; .image-button[disabled]:hover {
margin-left: 0; background-color: var(--surface-1);
border-color: var(--text-2);
} }
} }

2
css/components/dialog.css

@ -63,7 +63,7 @@
height: 24px; height: 24px;
padding: 4px; padding: 4px;
opacity: 0.9; opacity: 0.9;
border-radius: 7px; border-radius: var(--radius-2);
background-color: #232323; background-color: #232323;
background-image: url(../../images/icons/loading-small.svg); background-image: url(../../images/icons/loading-small.svg);
background-repeat: no-repeat; background-repeat: no-repeat;

25
css/components/footer.css

@ -0,0 +1,25 @@
/**
* @file
* Site footer visual styling.
*
* Padding lives in css/layout.css alongside <main>'s page padding
* (footer is a structural region, treated symmetrically with main).
* This file handles the skin: dark surface, light text and links.
*/
@layer components {
.footer {
background-color: var(--surface-dark);
color: var(--text-on-dark);
a {
color: var(--text-on-dark);
}
a:hover,
a.is-active {
color: var(--upei-yellow);
text-decoration: underline;
}
}
}

42
css/components/form.css

@ -5,6 +5,48 @@
* Visual styles for form components. * Visual styles for form components.
*/ */
/* Text-like inputs, select, and textarea shared baseline.
Uses Drupal's Form API class hooks, applied by core to every rendered
form element. The .form-textarea-wrapper rule in textarea.css makes
textareas full-width inside their wrapper; this file handles the skin. */
.form-text,
.form-email,
.form-tel,
.form-url,
.form-password,
.form-search,
.form-number,
.form-date,
.form-time,
.form-datetime,
.form-month,
.form-week,
.form-select,
.form-textarea {
padding: var(--size-2) var(--size-3);
border: 1px solid var(--border-1);
border-radius: var(--radius-2);
background-color: var(--surface-1);
color: var(--text-1);
font: inherit;
line-height: 1.4;
max-width: 100%;
&:hover {
border-color: var(--text-2);
}
&:focus {
outline: 2px solid var(--upei-red);
outline-offset: 2px;
border-color: var(--upei-red);
}
}
.form-textarea {
min-height: 8em;
resize: vertical;
}
form .field-multiple-table { form .field-multiple-table {
margin: 0; margin: 0;
} }

54
css/components/pager.css

@ -1,20 +1,58 @@
@layer components {
/** /**
* @file * @file
* Visual styles for pager. * Visual styles for the pager.
*
* Markup hooks from templates/navigation/pager.html.twig:
* .pager outer <nav>
* .pager__items <ul>
* .pager__item each <li>
* .pager__item--first "First" link
* .pager__item--previous "Previous" link
* .pager__item--next "Next" link
* .pager__item--last "Last" link
* .pager__item--ellipsis gap between non-adjacent page ranges
* .is-active added to the current page <li>
*/ */
@layer components {
.pager {
margin-block: var(--size-4);
}
.pager__items { .pager__items {
clear: both; margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: var(--size-1);
}
.pager__item a {
display: inline-block;
min-width: 2.5em;
padding: var(--size-1) var(--size-2);
border: 1px solid var(--surface-2);
border-radius: var(--radius-2);
color: var(--text-1);
line-height: 1.4;
text-align: center; text-align: center;
text-decoration: none;
} }
.pager__item { .pager__item a:hover,
display: inline; .pager__item a:focus {
padding: 0.5em; background-color: var(--surface-2);
border-color: var(--text-2);
} }
.pager__item.is-active { .pager__item.is-active a {
background-color: var(--upei-red);
border-color: var(--upei-red);
color: white;
font-weight: bold; font-weight: bold;
} }
.pager__item--ellipsis {
align-self: center;
padding: var(--size-1) var(--size-2);
color: var(--text-2);
}
} }

50
css/components/search.css

@ -1,16 +1,18 @@
/** /**
* @file * @file
* Search block input + submit button styling. * Search block input styling.
*
* The submit button inherits from .button / .button--primary (Drupal applies
* those classes automatically); see css/components/button.css.
* *
* Markup hooks come from templates/block/block--search-form-block.html.twig * Markup hooks come from templates/block/block--search-form-block.html.twig
* (outer .block-search) and Drupal's Form API (.form-search on the input, * (outer .block-search) and Drupal's Form API (.form-search on the input).
* .form-submit on the button).
*/ */
@layer components { @layer components {
.block-search .form-search { .block-search .form-search {
padding: var(--size-2) var(--size-3); padding: var(--size-2) var(--size-3);
border: 1px solid var(--surface-2); border: 1px solid var(--surface-2);
border: 1px solid #ccc;
border-radius: var(--radius-2); border-radius: var(--radius-2);
font: inherit; font: inherit;
} }
@ -19,17 +21,37 @@
outline-offset: 2px; outline-offset: 2px;
} }
.block-search .form-submit { /* Icon-on-input treatment: the .block-search__field wrapper (added in
padding: var(--size-2) var(--size-3); templates/block/block--search-form-block.html.twig) provides the
border: none; positioning context for an absolute-positioned icon overlaid on
border-radius: var(--radius-2); the left side of the search input.
background-color: var(--upei-red);
color: white; The SVG is sized via `width` / `height` here rather than on the
font: inherit; <svg> root, which lets the same icon scale to any context just by
cursor: pointer; changing the .block-search__icon size. The `color` property
recolors the icon because the SVG uses stroke="currentColor". */
.block-search__field {
position: relative;
display: inline-flex;
align-items: center;
}
.block-search__icon {
position: absolute;
left: var(--size-3);
top: 50%;
width: 1em;
height: 1em;
transform: translateY(-50%);
color: var(--text-2);
pointer-events: none;
} }
.block-search .form-submit:hover { .block-search__icon svg {
background-color: var(--upei-dark-red); width: 100%;
height: 100%;
display: block;
}
.block-search .form-search {
padding-left: calc(var(--size-3) * 2 + 1em);
} }
} }

4
css/components/tabs.css

@ -30,6 +30,10 @@
text-decoration: none; text-decoration: none;
border-bottom: 2px solid transparent; border-bottom: 2px solid transparent;
margin-bottom: -1px; margin-bottom: -1px;
transition:
background-color 0.15s var(--ease-out-2),
border-color 0.15s var(--ease-out-2),
color 0.15s var(--ease-out-2);
} }
.tabs a:hover, .tabs a:hover,
.tabs a:focus { .tabs a:focus {

26
css/tokens.css

@ -57,15 +57,39 @@
--sidebar-width: 240px; --sidebar-width: 240px;
/* Border radius */ /* Border radius */
--radius-2: 8px; --radius-1: 4px;
--radius-2: 6px;
--radius-3: 8px;
/* Surfaces (backgrounds) */ /* Surfaces (backgrounds) */
--surface-1: #ffffff; --surface-1: #ffffff;
--surface-2: #f5f5f5; --surface-2: #f5f5f5;
--surface-dark: #333;
/* Text colors */ /* Text colors */
--text-1: #111; --text-1: #111;
--text-2: #555; --text-2: #555;
--text-on-dark: #fff;
/* Borders / dividers */
--border-1: #ccc;
/* Easing — cherry-picked from Open Props (props.easing.css). */
--ease-out-2: cubic-bezier(0, 0, .5, 1);
/* Typography scale — cherry-picked from Open Props (props.fontsize.css). */
--font-size-0: .75rem;
--font-size-1: 1rem;
--font-size-2: 1.1rem;
--font-size-3: 1.25rem;
--font-size-4: 1.5rem;
--font-size-5: 2rem;
--font-size-6: 2.5rem;
--font-size-7: 3rem;
/* Line heights — cherry-picked from Open Props (props.font-lineheight.css). */
--font-lineheight-1: 1.25;
--font-lineheight-3: 1.5;
/* Brand */ /* Brand */
--upei-yellow: #fdc902; --upei-yellow: #fdc902;

64
druid.icons.yml

@ -0,0 +1,64 @@
# druid.icons.yml — declares an icon pack for the druid theme.
#
# ─────────────────────────────────────────────────────────────────────────
# How to add a new icon
# ─────────────────────────────────────────────────────────────────────────
#
# 1. Drop the SVG into <theme>/icons/<name>.svg.
# Tips:
# - Use currentColor for stroke / fill so CSS can recolor it via
# the `color` property on the parent element.
# - Strip width / height from the <svg> root (keep viewBox); let
# CSS size the icon.
# - Source: lucide.dev is the easiest free, currentColor-ready set.
# phosphoricons.com and tablericons.com also work.
#
# 2. Reference it in any Twig template:
#
# {{ icon('druid', 'name') }}
#
# where 'name' matches the filename (without .svg). Example:
# icons/arrow-right.svg → {{ icon('druid', 'arrow-right') }}.
#
# 3. Clear cache:
#
# drush cr
#
# This is required after ADDING a new file (Drupal's icon discovery
# caches the file list). Editing an SVG file's contents does NOT
# require a cache clear during development.
#
# ─────────────────────────────────────────────────────────────────────────
# Prerequisites
# ─────────────────────────────────────────────────────────────────────────
#
# The Icon API ships with Drupal core. On Drupal 11.0 it lived in the
# `experimental_icon` module and needed enabling. On 11.1+ it's stable
# and on by default. If `{{ icon('druid', '…') }}` errors with
# "Unknown function icon", enable the core Icon module at
# /admin/modules.
#
# Schema reference:
# https://www.drupal.org/docs/develop/theming-drupal/using-the-icon-api
#
# ─────────────────────────────────────────────────────────────────────────
# The pack
# ─────────────────────────────────────────────────────────────────────────
#
# The top-level key ("druid") is the pack id — the first argument to
# every icon() call. Don't rename it without grep-replacing every usage.
druid:
label: 'Druid theme icons'
description: 'Theme-local SVG icon set. Add new icons by dropping SVGs into the theme''s icons/ directory.'
license:
name: 'ISC'
url: 'https://lucide.dev/license'
extractor: svg
# `template` is INLINE Twig source (not a file path). Wraps each icon's
# parsed inner SVG content in an outer <svg> that re-applies the
# original root attributes (viewBox, fill, stroke, …) via {{ attributes }}.
template: '<svg{{ attributes.addClass("icon").setAttribute("aria-hidden", "true") }}>{{ content|raw }}</svg>'
config:
sources:
- icons/{icon_id}.svg

2
druid.libraries.yml

@ -86,6 +86,8 @@ components:
weight: -10 weight: -10
css/components/field.css: css/components/field.css:
weight: -10 weight: -10
css/components/footer.css:
weight: -10
css/components/form.css: css/components/form.css:
weight: -10 weight: -10
css/components/header.css: css/components/header.css:

4
icons/magnifying-glass.svg

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8"/>
<path d="m21 21-4.3-4.3"/>
</svg>

After

Width:  |  Height:  |  Size: 229 B

15
templates/block/block--search-form-block.html.twig

@ -27,6 +27,16 @@
* @see search_preprocess_block() * @see search_preprocess_block()
*/ */
#} #}
{#
Icon usage example (Drupal 11 Icon API). The search input gets a
magnifying-glass icon overlaid on its left side.
Pattern for ANY icon in ANY Twig template:
{{ icon('druid', 'icon-name') }}
where 'icon-name' matches a file at <theme>/icons/<icon-name>.svg.
See druid.icons.yml (theme root) for the full reference.
#}
{% {%
set classes = [ set classes = [
'block', 'block',
@ -41,6 +51,11 @@
{% endif %} {% endif %}
{{ title_suffix }} {{ title_suffix }}
{% block content %} {% block content %}
<div class="block-search__field">
<span class="block-search__icon" aria-hidden="true">
{{ icon('druid', 'magnifying-glass') }}
</span>
{{ content }} {{ content }}
</div>
{% endblock %} {% endblock %}
</div> </div>

2
templates/layout/page.html.twig

@ -109,7 +109,7 @@
</main> </main>
{% if page.footer %} {% if page.footer %}
<footer role="contentinfo"> <footer class="footer" role="contentinfo">
<div class="content-rail"> <div class="content-rail">
{{ page.footer }} {{ page.footer }}
</div> </div>

Loading…
Cancel
Save