- Updated form validation to not validate when form[novalidate]

- Updated tooltips to go multiline, max width of 250px.
-Updated card footer, to remove the margin from the last element if they are buttons or groups when in the footer
- Made container, grid, and row classes to have a parent class, so they will not effect rest of page. This will only work if you use that style css.

- Added tabs
- Added floating labels
- Added responsive nav hamburger menu

Let me know if you find any bugs or have ideas for improvements!
This commit is contained in:
Yohn 2024-12-10 12:30:06 -05:00
parent a937be4b4a
commit f25840f51a
260 changed files with 71329 additions and 11561 deletions

View file

@ -34,6 +34,7 @@
@use "forms/input-range"; // type="range"
@use "forms/input-search"; // type="search"
@use "forms/validation"; // validation for all form elements except select[multiple],select[size], input:not([type="button"], [type="reset"], [type="image"], [type="submit"], [type="checkbox"], [type="radio"]
@use "forms/floating"; // floating labels
// Components
@use "components/accordion"; // details, summary
@ -43,10 +44,11 @@
@use "components/loading"; // aria-busy=true
@use "components/modal"; // dialog
@use "components/nav"; // nav
@use "components/nav-hamburger"; // role="navigation" for hamburger-menu for the nav component
@use "components/progress"; // progress
@use "components/tooltip"; // data-tooltip
@use "components/tab-region"; // section[role="region"] // tabs
@use "components/notification"; // dialog[role="alert"]
@use "components/timeline"; // addition, kind of out of scope but wanted to add
// Utilities

View file

@ -132,6 +132,7 @@ $modules: map.merge(
"forms/input-file": true,
"forms/input-range": true,
"forms/input-search": true,
"forms/floating": true,
// V3 Notes
// Remove original pico validation in favor of :user-[in]valid
"forms/validation": true,
@ -149,7 +150,12 @@ $modules: map.merge(
// incorperate the <menu> tag into navigations.
"components/nav": true,
"components/progress": true,
// I wish role="tablist|tab|tabpanel" could be used on <details> and <summary> tag.
"components/tab-region": true,
"components/tooltip": true,
// V3 Notes
// might find a new way to do this, I'll wait to hear feedback on this approach.
"components/nav-hamburger": true,
// V3 Notes
// Notification should be redone to be more in line with Pico's design

View file

@ -51,6 +51,23 @@
var(#{$css-var-prefix}card-border-color);
border-bottom-right-radius: var(#{$css-var-prefix}border-radius);
border-bottom-left-radius: var(#{$css-var-prefix}border-radius);
// https://github.com/picocss/pico/issues/557#issuecomment-2393213110
[type="submit"],
[type="reset"],
[type="button"],
[role="group"] {
margin-bottom: 0px;
&:last-child {
margin-bottom: 0px;
}
/* Also remove if next input after button is a hidden input */
&:has(+ [type="hidden"]) {
margin-bottom: 0px;
}
}
}
}
}

View file

@ -0,0 +1,84 @@
@use "sass:string";
@use "sass:map";
@use "sass:math";
@use "../settings" as *;
@if map.get($modules, "components/nav") {
/**
* Nav hamburger menu
* modified from https: //codepen.io/brentarias/pen/gOQybod
*/
#{$parent-selector} nav[role="navigation"] {
z-index: 1;
align-items: center;
width: 100%;
overflow: hidden;
&[data-position="start"] {
/* remove the 'flex-direction' to move menu to the right */
flex-direction: row-reverse;
}
> input[type="checkbox"] {
display: none;
user-select: none;
}
> label {
display: none;
cursor: pointer;
user-select: none;
}
}
@each $breakpoint, $values in $breakpoints {
$databp: 'nav[role="navigation"]';
@if $breakpoint != sm {
$databp: #{$databp} + "[data-breakpoint='" + $breakpoint + "']";
}
$viewport: map.get($values, "viewport");
@media (max-width: $viewport) {
#{$parent-selector} #{$databp} {
flex-wrap: wrap;
& label {
display: block;
}
& > [role="list"] {
flex-direction: column;
align-items: flex-start;
width: 90vw;
max-height: 0;
margin: 0 auto;
background-color: var(--pico-muted-border-color); //muted-border-color);
box-shadow: var(--pico-box-shadow);
transition: max-height var(--pico-transition);
& li {
width: calc(100% - calc(var(--pico-nav-link-spacing-vertical) * 2));
margin: calc(var(--pico-nav-link-spacing-vertical) * 0.5)
var(--pico-nav-link-spacing-vertical);
padding: 0;
}
& a {
display: block;
margin: 0;
border-bottom: 1px solid transparent;
border-radius: 0;
transition:
border-color var(--pico-transition),
color var(--pico-transition);
}
& a:hover {
border-bottom-color: var(--pico-underline);
text-decoration: none;
//background-color: var(--pico-primary-background) !important;
//color: var(--pico-primary-inverse);
}
}
& input[type="checkbox"]:checked ~ [role="list"] {
max-height: 100vh;
}
}
}
}
}

View file

@ -0,0 +1,79 @@
@use "sass:map";
@use "../settings" as *;
@if map.get($modules, "components/tab-region") {
/**
* Tab region
* styling help from: https://github.com/picocss/pico/discussions/608
* and his demo: https://gistpreview.github.io/?86c08db6793d078757aa795845c19ed3
*/
#{$parent-selector} section[role="region"] {
display: flex;
flex-wrap: wrap;
margin-bottom: 0;
details {
display: contents;
margin-bottom: 0;
padding-bottom: 0;
summary {
flex-grow: 1;
order: 0;
margin: 0;
padding: calc(var(#{$css-var-prefix}block-spacing-vertical) * 0.75)
calc(var(#{$css-var-prefix}block-spacing-horizontal) * 1.5);
border-bottom: 1px solid transparent;
background-color: var(#{$css-var-prefix}card-sectioning-background-color);
list-style-type: none;
touch-action: manipulation;
transition: all var(#{$css-var-prefix}transition);
&:hover {
border-bottom-color: var(#{$css-var-prefix}primary-border);
background-color: var(#{$css-var-prefix}card-background-color);
}
&::after {
display: none;
// with the - icon instead of chevron
//transform: rotate(0deg);
//background-image: var(#{$css-var-prefix}icon-minus);
//background-position: center;
//background-size: .75em auto;
}
}
> div {
opacity: 0;
}
&[open] {
> summary {
background-color: var(#{$css-var-prefix}primary-background);
color: var(#{$css-var-prefix}primary-inverse) !important;
&:hover {
background-color: var(#{$css-var-prefix}primary-hover-background);
}
//&::after {
// black chevron icon
// background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
//}
}
// end for summary, keep here
> div {
order: 1;
width: 100%;
padding: var(#{$css-var-prefix}spacing);
padding-bottom: 0;
opacity: 1;
transition: opacity var(#{$css-var-prefix}transition);
}
}
}
}
}

View file

@ -24,6 +24,8 @@
position: absolute;
bottom: 100%;
left: 50%;
width: 250px;
max-width: 250px;
padding: 0.25rem 0.5rem;
overflow: hidden;
transform: translate(-50%, -0.25rem);
@ -34,9 +36,10 @@
font-style: normal;
font-weight: var(#{$css-var-prefix}font-weight);
font-size: 0.875rem;
text-align: center;
text-decoration: none;
text-overflow: ellipsis;
white-space: pre;
white-space: normal;
opacity: 0;
pointer-events: none;
}

View file

@ -257,7 +257,7 @@
}
// Aria-invalid
#{$parent-selector} :where(input, select, textarea) {
#{$parent-selector} :not([novalidate]) :where(input, select, textarea) {
&:not(
[type="checkbox"],
[type="radio"],
@ -331,6 +331,7 @@
}
&[aria-invalid="true"] {
// --pico-form-element-invalid-border-color
#{$css-var-prefix}border-color: var(#{$css-var-prefix}form-element-invalid-border-color);
&:is(:active, :focus) {

View file

@ -136,8 +136,8 @@
}
// Aria-invalid
#{$parent-selector} [type="checkbox"],
#{$parent-selector} [type="checkbox"][role="switch"] {
#{$parent-selector} :not([novalidate]) [type="checkbox"],
#{$parent-selector} :not([novalidate]) [type="checkbox"][role="switch"] {
&[aria-invalid="false"] {
&:checked,
&:checked:active,
@ -156,9 +156,9 @@
}
}
#{$parent-selector} [type="checkbox"],
#{$parent-selector} [type="radio"],
#{$parent-selector} [type="checkbox"][role="switch"] {
#{$parent-selector} :not([novalidate]) [type="checkbox"],
#{$parent-selector} :not([novalidate]) [type="radio"],
#{$parent-selector} :not([novalidate]) [type="checkbox"][role="switch"] {
&[aria-invalid="false"] {
&:checked,
&:checked:active,

74
scss/forms/_floating.scss Normal file
View file

@ -0,0 +1,74 @@
@use "sass:map";
@use "../settings" as *;
@if map.get($modules, "forms/floating") {
// and $enable-classes {
$transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
#{$parent-selector} section[role="form"] {
position: relative;
margin-bottom: 0;
> input::placeholder,
> textarea::placeholder {
color: rgba(0, 0, 0, 0);
transition: color $transition-fast;
}
> input:focus::placeholder,
> textarea:focus::placeholder {
color: var(--pico-form-element-placeholder-color);
}
> input + label,
> textarea + label,
> select + label {
position: absolute;
top: 35%;
left: 0.8rem;
transform: translateY(-50%);
background: var(#{$css-var-prefix}form-element-background-color);
color: var(#{$css-var-prefix}form-element-placeholder-color);
cursor: text;
transition: 0.3s ease;
}
// Used this before I tried: >select:has(option:checked:not([disabled]))~label
//> select + label {
// position: absolute;
// top: -5%;
// left: 0.8rem;
// padding: calc(var(--pico-spacing) * 0.25) calc(var(--pico-spacing) * 0.5);
// transform: translateY(-50%) scale(0.85);
// background: var(#{$css-var-prefix}form-element-background-color);
// cursor: text;
//}
> input:not(:placeholder-shown) + label,
> input:focus + label,
> textarea:not(:placeholder-shown) + label,
> textarea:focus + label,
> select:focus + label,
> select:has(option:checked:not([disabled])) ~ label {
top: -5%;
padding: calc(var(--pico-spacing) * 0.25) calc(var(--pico-spacing) * 0.5);
transform: translateY(-50%) scale(0.85);
color: var(--pico-form-element-active-border-color);
font-size: calc(var(--pico-font-size) * 0.75);
line-height: 1.25;
transition: all $transition-fast;
}
@if map.get($modules, "forms/validation") {
> input:user-invalid:not(:placeholder-shown) + label,
> textarea:user-invalid:not(:placeholder-shown) + label {
color: var(#{$css-var-prefix}form-element-invalid-border-color);
}
> input:user-valid:not(:placeholder-shown) + label,
> textarea:user-valid:not(:placeholder-shown) + label {
color: var(#{$css-var-prefix}form-element-valid-border-color);
}
}
}
}

View file

@ -4,7 +4,9 @@
@if map.get($modules, "forms/validation") {
#{$parent-selector}
form:not([novalidate])
input:user-valid:not(
:placeholder-shown,
[type="button"],
[type="reset"],
[type="image"],
@ -13,7 +15,9 @@
[type="radio"]
),
#{$parent-selector}
form:not([novalidate])
input:user-invalid:not(
:placeholder-shown,
[type="button"],
[type="reset"],
[type="image"],
@ -26,8 +30,8 @@
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
background-repeat: no-repeat;
}
#{$parent-selector} select:user-valid:not([multiple], [size]),
#{$parent-selector} select:user-invalid:not([multiple], [size]) {
#{$parent-selector} form:not([novalidate]) select:user-valid:not([multiple], [size]),
#{$parent-selector} form:not([novalidate]) select:user-invalid:not([multiple], [size]) {
padding-right: calc(1.5em + 0.75rem);
padding-right: 4.2rem;
background-position:
@ -35,21 +39,23 @@
center right 2.25rem;
background-repeat: no-repeat;
}
#{$parent-selector} select:user-invalid:not([multiple], [size]) {
#{$parent-selector} form:not([novalidate]) select:user-invalid:not([multiple], [size]) {
background-image: var(#{$css-var-prefix}icon-chevron), var(#{$css-var-prefix}icon-invalid);
}
#{$parent-selector} select:user-valid:not([multiple], [size]) {
#{$parent-selector} form:not([novalidate]) select:user-valid:not([multiple], [size]) {
background-image: var(#{$css-var-prefix}icon-chevron), var(#{$css-var-prefix}icon-valid);
}
#{$parent-selector} textarea:user-valid,
#{$parent-selector} textarea:user-invalid {
#{$parent-selector} form:not([novalidate]) textarea:user-valid:not(:placeholder-shown),
#{$parent-selector} form:not([novalidate]) textarea:user-invalid:not(:placeholder-shown) {
padding-right: calc(1.5em + 0.75rem);
background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
background-repeat: no-repeat;
}
#{$parent-selector}
form:not([novalidate])
input:user-invalid:not(
:placeholder-shown,
[type="button"],
[type="reset"],
[type="image"],
@ -57,13 +63,19 @@
[type="checkbox"],
[type="radio"]
),
#{$parent-selector} select:user-invalid,
#{$parent-selector} textarea:user-invalid {
#{$parent-selector} form:not([novalidate]) select:user-invalid:not([multiple], [size]),
#{$parent-selector} form:not([novalidate]) textarea:user-invalid:not(:placeholder-shown) {
border-color: var(#{$css-var-prefix}form-element-invalid-border-color);
background-image: var(#{$css-var-prefix}icon-invalid);
&:focus {
border-color: var(#{$css-var-prefix}form-element-invalid-active-border-color);
}
}
#{$parent-selector}
form:not([novalidate])
input:user-valid:not(
:placeholder-shown,
[type="button"],
[type="reset"],
[type="image"],
@ -71,17 +83,23 @@
[type="checkbox"],
[type="radio"]
),
#{$parent-selector} select:user-valid,
#{$parent-selector} textarea:user-valid {
#{$parent-selector} form:not([novalidate]) select:user-valid:not([multiple], [size]),
#{$parent-selector} form:not([novalidate]) textarea:user-valid:not(:placeholder-shown) {
border-color: var(#{$css-var-prefix}form-element-valid-border-color);
background-image: var(#{$css-var-prefix}icon-valid);
&:focus {
border-color: var(#{$css-var-prefix}form-element-valid-active-border-color);
}
}
#{$parent-selector} input:required:user-invalid:is([type="checkbox"]) {
#{$parent-selector} form:not([novalidate]) input:required:user-invalid:is([type="checkbox"]) {
border-color: var(#{$css-var-prefix}form-element-invalid-border-color);
}
/********** To include a message after the element with info ************/
#{$parent-selector}
form:not([novalidate])
input:user-valid:not(
:placeholder-shown,
[type="button"],
[type="reset"],
[type="image"],
@ -90,15 +108,29 @@
[type="radio"]
)
+ small[data-valid]::after,
#{$parent-selector} select:user-valid:not([multiple], [size]) + small[data-valid]::after,
#{$parent-selector} textarea:user-valid + small[data-valid]::after {
#{$parent-selector}
form:not([novalidate])
select:user-valid:not([multiple], [size])
+ small[data-valid]::after,
#{$parent-selector}
form:not([novalidate])
textarea:user-valid:not(:placeholder-shown)
+ small[data-valid]::after {
content: attr(data-valid);
color: var(#{$css-var-prefix}form-element-valid-border-color);
}
#{$parent-selector} textarea:user-invalid + small[data-invalid]::after,
#{$parent-selector} select:user-invalid:not([multiple], [size]) + small[data-invalid]::after,
#{$parent-selector}
form:not([novalidate])
textarea:user-invalid:not(:placeholder-shown)
+ small[data-invalid]::after,
#{$parent-selector}
form:not([novalidate])
select:user-invalid:not([multiple], [size])
+ small[data-invalid]::after,
#{$parent-selector}
form:not([novalidate])
input:user-invalid:not(
:placeholder-shown,
[type="button"],
[type="reset"],
[type="image"],
@ -107,12 +139,18 @@
[type="radio"]
)
+ [data-invalid]::after,
#{$parent-selector} input[type="file"]:user-invalid + ul + [data-invalid]::after {
#{$parent-selector}
form:not([novalidate])
input[type="file"]:user-invalid
+ ul
+ [data-invalid]::after {
content: attr(data-invalid);
color: var(#{$css-var-prefix}form-element-invalid-border-color);
}
#{$parent-selector}
form:not([novalidate])
input:user-valid:not(
:placeholder-shown,
[type="button"],
[type="reset"],
[type="image"],
@ -122,27 +160,42 @@
)
+ [data-valid]::after,
#{$parent-selector}
form:not([novalidate])
input:user-invalid:not(
:placeholder-shown,
[type="button"],
[type="reset"],
[type="image"],
[type="submit"],
[type="checkbox"],
[type="radio"]
)
+ [data-invalid]::after,
#{$parent-selector} textarea:user-valid + [data-valid]::after,
#{$parent-selector} input[type="file"]:user-invalid + [data-invalid]::after,
#{$parent-selector} input[type="file"]:user-invalid + ul + [data-invalid]::after {
#{$parent-selector}
form:not([novalidate])
textarea:user-valid:not(:placeholder-shown)
+ [data-valid]::after,
#{$parent-selector}
form:not([novalidate])
input[type="file"]:user-invalid
+ [data-invalid]::after,
#{$parent-selector}
form:not([novalidate])
input[type="file"]:user-invalid
+ ul
+ [data-invalid]::after {
display: block;
}
//input[type="file"]:user-invalid + [data-invalid]::after,
#{$parent-selector} input[type="file"]:user-invalid + ul + [data-invalid]::after {
#{$parent-selector}
form:not([novalidate])
input[type="file"]:user-invalid
+ ul
+ [data-invalid]::after {
position: relative;
top: -2rem;
}
// the file btn
#{$parent-selector} input[type="file"]:user-invalid::file-selector-button {
#{$parent-selector} form:not([novalidate]) input[type="file"]:user-invalid::file-selector-button {
border-color: var(#{$css-var-prefix}form-element-invalid-border-color);
background-color: var(#{$css-var-prefix}form-element-invalid-border-color);
}

View file

@ -1,4 +1,4 @@
/*!
* Pico CSS v2.1.3 (https://github.com/Yohn/PicoCSS)
* Pico CSS v2.2.0 (https://github.com/Yohn/PicoCSS)
* Copyright 2019-2024 - Licensed under MIT
*/

View file

@ -6,8 +6,8 @@
* Container
*/
.container,
.container-fluid {
#{$parent-selector} .container,
#{$parent-selector} .container-fluid {
width: 100%;
margin-right: auto;
margin-left: auto;
@ -15,7 +15,7 @@
padding-left: var(#{$css-var-prefix}spacing);
}
.container {
#{$parent-selector} .container {
$first-breakpoint: true;
@each $key, $values in $breakpoints {
@if $values {

View file

@ -7,7 +7,7 @@
* Minimal grid system with auto-layout columns
*/
.grid {
#{$parent-selector} .grid {
grid-column-gap: var(#{$css-var-prefix}grid-column-gap);
grid-row-gap: var(#{$css-var-prefix}grid-row-gap);
display: grid;

View file

@ -9,8 +9,8 @@
$helper-cols: "";
$helper-offset: "";
/*--- CSS Grid ---*/
.row-fluid,
.row {
#{$parent-selector} .row-fluid,
#{$parent-selector} .row {
display: grid;
grid-template-columns: repeat($row-columns, 1fr);
gap: var(#{$css-var-prefix}grid-row-gap) var(#{$css-var-prefix}grid-column-gap);
@ -26,29 +26,29 @@
> [class*="col"] > *,
> [class|="col"] > *,
> [class~="col"] > * {
margin: calc(var(#{$css-var-prefix}block-spacing-vertical) * 0.5) auto;
margin: var(#{$css-var-prefix}block-spacing-vertical) auto;
}
}
.row {
#{$parent-selector} .row {
max-width: map.get(map.get($breakpoints, "xl"), "viewport");
margin: 0 auto;
}
/* Defining columns spans and offsets */
// Loop through all column spans and define the .grid-column-end number
@for $col from 1 through $row-columns {
.col-#{$col} {
#{$parent-selector} .col-#{$col} {
grid-column-end: span $col;
}
@if $col == 1 {
$helper-cols: ".col-1";
} @else {
$helper-cols: #{$helper-cols} + ", .col-" + #{$col};
$helper-cols: #{$helper-cols} + ", #{$parent-selector} .col-" + #{$col};
}
}
// Loop through all column offsets and define the .grid-column-start number
@for $offset from 0 through $row-columns - 1 {
.offset-#{$offset} {
#{$parent-selector} .offset-#{$offset} {
grid-column-start: $offset + 1;
}
@if $offset == 0 {
@ -65,15 +65,19 @@
@if $values {
@media (min-width: map.get($values, "viewport")) {
@for $col from 1 through $row-columns {
.col-#{$breakpoint}-#{$col} {
#{$parent-selector} .col-#{$breakpoint}-#{$col} {
grid-column-end: span $col;
}
@if ($breakpoint != "sm") {
$helper-cols: #{$helper-cols} + ", .col-" + #{$breakpoint} + "-" + #{$col};
$helper-cols: #{$helper-cols} +
", #{$parent-selector} .col-" +
#{$breakpoint} +
"-" +
#{$col};
}
}
@for $offset from 0 through $row-columns - 1 {
.offset-#{$breakpoint}-#{$offset} {
#{$parent-selector} .offset-#{$breakpoint}-#{$offset} {
grid-column-start: $offset + 1;
}
@if ($breakpoint != "sm") {