Own all form styling in components; remove form CSS from input.css
Form controls were styled "at a distance": Django renders bare <input>/<select>/<textarea>/<label>, so input.css reached in with ID-scoped #add-form descendant rules plus a global form *:disabled rule and .errorlist. The #add-form ID specificity forced state rules to climb, needed :not([data-search-select-search]) carve-outs, and broke on markup changes — it surfaced as the add_purchase Name/related_game fields not reading as disabled. Components now own all form styling via utilities on the elements themselves: - PrimitiveWidgetsMixin stamps INPUT/SELECT/TEXTAREA_CLASS (incl. disabled: variants) onto native widgets by type, skipping SearchSelect (self-styled) and checkboxes. - New FormFields(form, *, extras=...) renders label + control + errors + row layout with their own classes (replaces form.as_div()); the <form> owns its flex layout. extras appends a node into a named field's row (session timestamp buttons). - AddForm/purchase/session render via FormFields; login too — a new LoginForm(PrimitiveWidgetsMixin, AuthenticationForm) styles its inputs and auth.py renders it via FormFields + a StyledButton (was as_table). - input.css loses the entire #add-form block, the global :disabled rule, and .errorlist. State (disabled:) now lives on the element — no specificity wars, no carve-outs, robust to markup edits. Tests: error rendering uses the component class (not .errorlist); add-form labels/inputs carry their own classes; e2e login fixtures click the Login button by text (submit is now a <button>); Name disabled cursor asserted. CLAUDE.md documents the no-styling-at-a-distance + FormFields conventions. 513 passed; lint/format/ts-check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+13
-132
@@ -2369,6 +2369,9 @@
|
||||
.p-3 {
|
||||
padding: calc(var(--spacing) * 3);
|
||||
}
|
||||
.p-3\.5 {
|
||||
padding: calc(var(--spacing) * 3.5);
|
||||
}
|
||||
.p-4 {
|
||||
padding: calc(var(--spacing) * 4);
|
||||
}
|
||||
@@ -3435,6 +3438,16 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
.disabled\:bg-neutral-secondary-strong {
|
||||
&:disabled {
|
||||
background-color: var(--color-neutral-secondary-strong);
|
||||
}
|
||||
}
|
||||
.disabled\:text-fg-disabled {
|
||||
&:disabled {
|
||||
color: var(--color-fg-disabled);
|
||||
}
|
||||
}
|
||||
.has-\[\:disabled\]\:cursor-not-allowed {
|
||||
&:has(*:is(:disabled)) {
|
||||
cursor: not-allowed;
|
||||
@@ -4405,20 +4418,6 @@
|
||||
border-left-color: var(--color-slate-500);
|
||||
}
|
||||
}
|
||||
form input:disabled:not([data-search-select-search]), select:disabled, textarea:disabled {
|
||||
cursor: not-allowed;
|
||||
background-color: var(--color-neutral-secondary-strong);
|
||||
color: var(--color-fg-disabled);
|
||||
}
|
||||
.errorlist {
|
||||
margin-top: calc(var(--spacing) * 4);
|
||||
margin-bottom: calc(var(--spacing) * 1);
|
||||
width: 300px;
|
||||
background-color: var(--color-red-600);
|
||||
padding-block: calc(var(--spacing) * 2);
|
||||
padding-left: calc(var(--spacing) * 3);
|
||||
color: var(--color-slate-200);
|
||||
}
|
||||
#button-container button {
|
||||
margin-inline: calc(var(--spacing) * 1);
|
||||
}
|
||||
@@ -4512,124 +4511,6 @@ form input:disabled:not([data-search-select-search]), select:disabled, textarea:
|
||||
margin-bottom: 0.5em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
#add-form {
|
||||
label + select, input, textarea {
|
||||
margin-top: calc(var(--spacing) * 1);
|
||||
}
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--spacing) * 3);
|
||||
}
|
||||
.form-row-button-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: calc(var(--spacing) * 0);
|
||||
padding: calc(var(--spacing) * 0);
|
||||
button {
|
||||
margin-right: calc(var(--spacing) * 0);
|
||||
&:first-child {
|
||||
border-start-end-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
}
|
||||
&:nth-child(2) {
|
||||
border-radius: 0;
|
||||
}
|
||||
&:last-child {
|
||||
border-start-start-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
label {
|
||||
margin-bottom: calc(var(--spacing) * 2.5);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
--tw-font-weight: var(--font-weight-medium);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-heading);
|
||||
}
|
||||
input:not([type="checkbox"]):not([data-search-select-search]) {
|
||||
margin-bottom: calc(var(--spacing) * 3);
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-radius: var(--radius-base);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
border-color: var(--color-default-medium);
|
||||
background-color: var(--color-neutral-secondary-medium);
|
||||
padding-inline: calc(var(--spacing) * 3);
|
||||
padding-block: calc(var(--spacing) * 2.5);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
color: var(--color-heading);
|
||||
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
&::placeholder {
|
||||
color: var(--color-body);
|
||||
}
|
||||
&:focus {
|
||||
border-color: var(--color-brand);
|
||||
}
|
||||
&:focus {
|
||||
--tw-ring-color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
select {
|
||||
width: 100%;
|
||||
border-radius: var(--radius-base);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
border-color: var(--color-default-medium);
|
||||
background-color: var(--color-neutral-secondary-medium);
|
||||
padding-inline: calc(var(--spacing) * 3);
|
||||
padding-block: calc(var(--spacing) * 2.5);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
color: var(--color-heading);
|
||||
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
&::placeholder {
|
||||
color: var(--color-body);
|
||||
}
|
||||
&:focus {
|
||||
border-color: var(--color-brand);
|
||||
}
|
||||
&:focus {
|
||||
--tw-ring-color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-radius: var(--radius-base);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
border-color: var(--color-default-medium);
|
||||
background-color: var(--color-neutral-secondary-medium);
|
||||
padding: calc(var(--spacing) * 3.5);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--tw-leading, var(--text-sm--line-height));
|
||||
color: var(--color-heading);
|
||||
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.05));
|
||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||
&::placeholder {
|
||||
color: var(--color-body);
|
||||
}
|
||||
&:focus {
|
||||
border-color: var(--color-brand);
|
||||
}
|
||||
&:focus {
|
||||
--tw-ring-color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
:has(> label + input[type="checkbox"]) {
|
||||
margin-top: calc(var(--spacing) * 3);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
@layer utilities {
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
|
||||
Reference in New Issue
Block a user