Skip to content

Feat/islands - #554

Draft
kake21 wants to merge 166 commits into
mainfrom
feat/islands
Draft

Feat/islands#554
kake21 wants to merge 166 commits into
mainfrom
feat/islands

Conversation

@kake21

@kake21 kake21 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Major style overhaul of projectNext.

Extends #526 and #514.
Continues work on #357 and #401.
Implements hamburger menu from #7 issue.

Waiting on SVG support from CMS to color logos in mobile navbar.

  • Standardizes core UI components (Button, TextInput, DateInput, Checkbox, Textarea, NumberInput, SubmitButton) onto a consistent card/gap/rounding system, and adds new Dropdown/SearchableDropdown components used to rebuild the admin user list (sortable, standardized).
  • Various admin page redesigns (sidebar as nested cards, /admin/dots, flairs with animated drag-to-reorder, a new component test page) and smaller fixes (CommitteeCard, HeaderItemPopUp, PopUp, jobads/events zoning, omegaquote bubble tail, edit-mode icon visibility, frontpage preview link).
  • PageTitle context: PageTitleProvider (mounted once in layout.tsx) holds the current title; each page
    renders <PageTitleSetter title="..." />, which pushes the title up via a plain useEffect (set on mount/ch
    ange, clear on unmount) and NavBarTitle reads it back out to render in the nav bar. Adopted across ~15 pages
    (auth, admin, users, events, committees, images, error/not-found, etc.) to avoid prop-drilling a title throug
    h every layout.
  • Changed the colors to a base16 inspired layout.

Images showing the proposed stylechange:

Logged in main page desktop:
image

Profile page desktop:
image

Logged in main page mobile:
Screen Shot 2026-08-05 at 17 46 52

Pofile page mobile:
Screen Shot 2026-08-05 at 17 47 53

kake21 and others added 30 commits April 15, 2026 10:01
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
kake21 and others added 2 commits August 5, 2026 17:05
Swap the mount/localStorage useEffect+setState pattern in NavTooltip and
ThemeForm for useSyncExternalStore, which avoids react-hooks/set-state-in-effect
without changing behavior. Also wraps several lines that exceeded max-len.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@kake21 kake21 self-assigned this Aug 5, 2026
kake21 and others added 27 commits August 5, 2026 17:40
…olumns

Switches the footer from a fixed CSS grid to flex-wrap so sections
keep their natural content width and drop to a new row as whole
blocks instead of shrinking narrow enough to break emails/phone
numbers mid-word. Kontakt and Adresse are grouped in a shared
.infoGroup flex item so they always move together rather than
splitting across rows independently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Uses standard scrollbar-color/scrollbar-width plus WebKit pseudo-elements
for Chrome, styled with the existing theme colors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…d alignment

- Updated ReportButton to use dynamic spacing variables and improved hover effect.
- Resized NavBar action buttons and added flex alignment for better UI consistency.
…components

- Introduced expand/collapse functionality to DesktopSideBar with transition effects.
- Updated EditModeNavIcon and AdminNav to support expanded state for better UX.
- Refactored and modularized styles for improved alignment and responsiveness.
- Adjusted layout to handle expanded sidebar properly with grid-template changes.
The avatar button now links straight to /users/me (or /login when
signed out) instead of opening a click-triggered dropdown. The
profile page already surfaces settings, Omega-ID, account and logout,
so the popup was redundant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Link was nested inside StandardImageServer's overlay slot, which
sizes itself to the tightly-fit image box (~30x27px) rather than the
visible 48x48 logo button, so only clicks on the image itself worked.
Wrapping the Link around the whole logoWrapper instead makes the full
button clickable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Adopt the shared PageWrapper component for the page title, header
  row and min-height so the page fills the content area instead of
  collapsing to its content size.
- Swap the bare PopUp for AddHeaderItemPopUp so "new collection" gets
  the app's standard styled round button instead of an unstyled plus.
- Style the Bildealbum/Spesialsamlinger mode-switch buttons with the
  ohma.btn mixin instead of relying on unstyled native buttons with
  only opacity toggling.
- Switch the collection grid from a fixed 4-column CSS grid with three
  manual breakpoints to flex-wrap, which reflows naturally.
- Move the "only administrated collections" checkbox inline with the
  mode-switch buttons instead of stacking it on its own line.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Visible <input> elements should go through @/components/UI (TextInput,
Checkbox, NumberInput, etc.) instead of being written raw, so they get
consistent styling and behavior - e.g. Checkbox's hidden "field
present" input, which keeps unchecked boxes submitting correctly.
type="hidden" inputs are exempt since they're just plain form data
carriers with no component equivalent, and the UI folder itself is
exempt since that's where these primitives are implemented.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The current-image thumbnail shown when changing a CMS image used
object-fit: contain, leaving visible background gaps around
non-square images. Switch to cover so it fills its clip area like
the rest of the app's image thumbnails.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…out !important

EventCard's images were overflowing their 420x200 card box and being
badly cropped, because CmsImage's wrapper divs sized themselves off
the image's declared width (a fixed pixel value) rather than their
container.

The declared width is Image.tsx's `width` prop, applied as an inline
style on its wrapper div. Inline styles can only be overridden with
!important, which is why ~10 components across the app were already
carrying `width: 100% !important` just to make an image responsive.
Image now sets that width via a `--image-width` custom property
instead, so it renders through a normal stylesheet rule consumers can
override with ordinary CSS - no !important required. Cleaned up the
now-unnecessary !important from CommitteeCard, ImageCard,
ImagePanelImage, ImageDisplay, CollectionCard, OmbulCover, and
Article's cover image, and applied the same depth-agnostic wrapper-div
fix used for EventCard to Article's cover image, which had the same
latent bug for the no-image-chosen-yet fallback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reproduced an intermittent "Maximum update depth exceeded" crash on
article pages, pointing at EditModeNavIcon/EditModeSwitch. Root cause
traced to useEditMode: its registration effect depended on the whole
authResult (and authorizer) object, but useAuthorizer constructs a new
AuthResult on every render (authorizer.auth(...) is unmemoized), and
several callers (e.g. EditableTextField) construct their authorizer
object inline as well. That made the effect's dependency array
different on every render of every component using this hook, so it
re-ran constantly instead of only when authorization actually changed
- amplified further since EditModeContext's Provider value is a fresh
object each render too, cascading the re-renders to every subscriber.

Depend on the primitive authResult.authorized instead of the object,
which only actually changes when authorization status changes. Also
hoisted EditableTextField's inline RequireNothing authorizer to a
module constant, since it takes no dynamic args and was being
reconstructed on every render for no reason.

Reproduced repeatedly before the fix (~1 in 3-4 page loads); toggled
edit mode on/off rapidly several times after the fix with no repeat.
Given the intermittency, treat this as a strong fix rather than a
100%-confirmed one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tructure

- Moved social icons into reusable `socials` section and updated related styling for consistency.
- Removed redundant `.emptyPart` divs and improved spacing with dynamic padding.
- Refactored `InfoBubbles` to simplify icon implementation and align styles.
- Standardized CMS image handling with enhanced container control and fixed overflow issues.
- Added support for children to `Section` components for greater flexibility.
… viewport

height: calc(100dvh - 60px) was a stale approximation of the app shell's
chrome (nav bar, grid gaps, padding) and overflowed the visible content
pane by ~28px. Derive the offset from the same nav-height/gap tokens the
shell layout uses, with a mobile-breakpoint variant, and switch to
min-height so the hero can grow instead of clipping if its content ever
needs more space.
- Replaced static login button with dynamic user-dependent content.
- Added `ProfilePicture` for logged-in users; styled logout state with a distinct button.
- Refined navbar SCSS for conditional styling and responsive layouts.
- Improved frontpage call-to-action buttons with refined styles and animations.
Float the login card on the left as a narrow vertical panel with the
hooded crest logo up top, and use the frontpage hero photo as a large
image panel on the right, instead of the small inverted hat icon.
…ign system

Imports 40 presentational components into the "Ohma Design System" project on
claude.ai/design, so the design agent builds with our real components instead of
generic ones.

projectNext is a Next.js app rather than a component library: there is no library
build, and every component styles itself through *.module.scss, which esbuild
cannot load. .design-sync/stage-pkg.mjs mechanically transforms the real source
into a bundlable package — compiling each SCSS module to *.module.css via sass
(a custom importer resolves the @/styles alias), rewriting only import
specifiers, and shimming next/link and next/navigation. Component bodies are
copied verbatim; nothing here reimplements a component.

All four themes ship with it. stage-pkg.mjs reads theme.ts at build time and
emits a :root[data-theme="<Name>"] block per theme, and the bundle re-exports
themes / applyTheme / subscribeToTheme / getActiveTheme / ThemeName, so designs
can switch palettes exactly as the app's own theme picker does. theme.ts stays
the single source of truth — no palette values are copied.

Contents:
- scope.json    which components sync, and their DS-pane grouping
- stage-pkg.mjs the staging transform
- vendor-inter.mjs vendors Inter, which _fonts.scss names as the site's body
  font but never ships (production currently falls back to system sans)
- previews/     40 authored preview compositions, all graded good
- conventions.md the header prepended to the generated README, telling the
  design agent how to build with Ohma
- NOTES.md      environment quirks, decisions, and re-sync risks

NOTES.md also records real defects surfaced while authoring the previews:
unstyled native Select, FileInput's dark-on-black default, invisible
Slider/TextInput 'secondary' variants, Checkbox losing its styling with
children, BorderButton having no disabled state, SimpleTable's `links` prop
emitting invalid HTML, and a palette mismatch between globals.scss and
themes.Standard. These are reproduced faithfully in the cards rather than
papered over.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ld idiom

FileInput, Select and ColorInput each rendered a bare native control while every
other input used the same floating-label field: surface-raised fill, 2px border
going accent-blue on focus, and a label that floats above the value. They are now
all built the same way — the real input stays focusable but visually hidden, and
the styled surface doubles as its label.

  FileInput   the chosen file name takes the place of the field value (multiple
              files collapse to "N filer valgt"); useId() replaces a per-render
              uuid that risked hydration mismatches
  Select      appearance:none plus the field treatment and a chevron; option
              rows are styled so the native popup is dark rather than white
  ColorInput  the swatch stands in for the value with the hex beside it

All three gained `background="raised"`, and Select/FileInput gained the `white`
colour the other fields already had. No caller signatures changed.

Every field now measures exactly 56px with padding on an 8px grid (border 4 +
24 top + 8 bottom + 20px line box); every $gap multiplier across the eight
stylesheets is a whole number. DateInput needed its height pinned because the
native picker indicator inflated its content box, and the select-backed fields
needed explicit line boxes.

Also fixes a subtle bug shared by Dropdown: globals.scss sets `color` on the
universal selector, which matches a value <span> directly and beats the colour
inherited from its parent — so `color` variants silently did nothing wherever the
value was rendered in a span. Those rules now target the value itself.
SearchableDropdown was unaffected; it renders its value in an <input>.

Adds Color input and Selects sections to /admin/component-test, and updates the
design-system previews to match, including driving real file selection and colour
choices so the variant axes actually vary in the published cards.

.design-sync/stage-pkg.mjs now emits the .d.ts tree itself: it wipes its output
dir (types/ included) and the converter derives the component list from that
tree, so a forgotten tsc silently produced a 12-component bundle instead of 40.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The outline button existed twice over: as a BorderButton component used in only
three places, and as a `borderBtn` mixin applied at ten more sites across seven
page stylesheets. In every one of those, it was the secondary action sitting
beside a filled primary — so the tier collapses into the filled secondary style
we already had, and the design system is left with two action tiers instead of
three.

- deletes BorderButton (component + styles) and the `borderBtn` mixin
- adds a `secondaryBtn` mixin holding the filled-secondary treatment, which
  Button.secondary now uses too instead of repeating it
- converts all ten former mixin sites to `secondaryBtn`; CmsLink's explicit
  `.primary` variant becomes the filled primary tier instead
- login: the submit is now `Button color="primary"` (it is the page's main
  action and was previously rendered as an outline, reading as secondary) and
  Feide sign-in is `color="secondary"`
- AddParts: the CMS add-part toolbar becomes `color="secondary"`

Section.module.scss styled `.readMore` identically in its `.blue` and
`:not(.blue)` branches once the outline was gone, so that split is collapsed.

Appearance changes, deliberately: every former outline button is now filled.
That covers the login screen, the frontpage hero and section CTAs, CMS links,
the news archive link and the applications/jobads/flairs secondary actions.

Design system: BorderButton drops out of scope.json and its preview is deleted,
taking the count from 40 to 39 components. conventions.md now tells the design
agent there is no outline/ghost variant and not to invent one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Events were 2-up cards ~460px tall: a 200px cover image, a 150px body and
separate header/footer strips. They are now 96px full-width rows on the 8px
grid — thumbnail, date block, title + tags, then a right-aligned meta column,
with the attendance bar hugging the bottom edge. Roughly five times as many
events fit on a screen.

The cover image is kept as a square thumbnail rather than dropped, so the
CmsImage cover editing (canEdit) still works from the listings.

- EventCardListLayout becomes EventRowListLayout: a stacked single column at
  every breakpoint instead of a 2-column grid
- LoggedInSection takes a `layout` prop; the frontpage events section opts into
  'rows' so the rows are not squeezed into its 3-column card grid
- on mobile the row stays a row and the meta column drops out, rather than
  reflowing back into a stacked card
- month abbreviations are Norwegian; they were full English month names in an
  otherwise Norwegian UI

Also stops the row underlining itself on hover. The row is a single large
anchor, so globals' `a:hover { text-decoration: underline }` underlined every
text run inside it — date, title and all the meta. The background shift is the
hover affordance now.

Verified against the running dev app on /events and the frontpage, at desktop
and 430px. The archive listing shares EventsLandingLayout with /events but has
no seeded data to render.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Job ads were ImageCards in a 3-up grid, ~450px tall. They are now 96px
full-width rows matching the event listings: thumbnail, application deadline,
title + company/type, then a right-aligned meta column.

The row geometry moved into a `listRow` mixin that both EventCard and JobAd
include, so the two lists cannot drift apart. Consumers supply .thumb, .lead,
.main, .meta and an optional .bar; EventCard was refactored onto those names.
EventRowListLayout became the generic RowListLayout for the same reason.

JobAd no longer goes through ImageCard. That component is still used by News,
Articles and Education, so it is untouched — only the job-ad rendering changed.

- deadline occupies the lead slot; applicationDeadline is nullable, so ads
  without one read "Løpende" rather than leaving the column blank
- the description is dropped from the listing (there is no room in a row, and
  it is on the detail page); company and job type take its place
- the "Inaktiv" badge moves into the meta column
- frontpage Jobbannonser opts into the row layout, as the events section does
- JobAdInactiveList's .loadingInfo loses `grid-column`, which is inert in a
  flex column

Verified against the running dev app on /career/jobads and the frontpage; the
inactive archive has no seeded data to render.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The logged-in landing page painted one surface-base panel behind everything and
stacked its sections inside it. Each section is now its own island — a
surface-base panel with rounded corners — and the page background shows through
the gaps between them.

- LoggedInSection gains a `span` prop: Nyheter and Bilder run full width,
  Hvad der hender and Jobbannonser pair up on a row once there is room
- the hero image and the footer are rounded, and the frontpage wrapper is a
  flex column with a $gap step, so the hero, the content and the footer are
  each separated by one grid unit
- content max-width goes 900px -> 1200px, and the frontpage drops its own
  side padding; what remains at the sides is pagePadding centring, not padding
- the rule under each section title is gone and "Les mer" is now a pill button
  matching the "Ny +" header item (same height, radius, surface and hover)
- social icons are gone from the logged-in page; they remain in the footer

listRow switches from a viewport media query to a container query. Rows now sit
both full width and inside half-width islands, and a viewport query left the
title about 50px in a narrow island on a wide screen — the meta column now
gives way based on the space the row actually has.

Also defines .footer in page.module.scss. LoggedIn and LoggedOut both referenced
styles.footer, which was never declared, so that wrapper rendered with
class="undefined" on both frontpages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hvad der hender and Jobbannonser share a row on wide screens, but their
flex-basis of 50% kept them side by side all the way down, splitting a phone
screen in two. They now take the full width below the mobile breakpoint.

On phone-width rows the date no longer sits in a column beside the title —
there is not enough horizontal room for it there. The row becomes a grid: the
thumbnail keeps the left edge at full height, and the date runs horizontally
above the title in the space that remains.

Fixes the container-query setup underneath both. `container-type` was on the
row itself, but a container query can only style descendants of its container,
never the container element — so rules targeting the row were silently dead
(the earlier `> .meta { display: none }` worked only because it targets a
child). The container moves to RowListLayout, the list wrapping the rows, which
has the same width and the right semantics. All four row lists get it: the
frontpage islands, the events landing page, the job-ad listing and its archive.

Measured against the compiled CSS in headless chromium: at 1152px and 652px the
row stays flex with a vertical date and its meta column; at 382px it is grid,
the date is horizontal and above the title, both share a left edge clear of the
96px thumbnail, and the row is still 96px tall.

Also includes: "Les mer" becomes "Se flere", and ImageCard's image corners use
$rounding rather than $cardRounding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two component defects the previous sync documented are fixed: Select is
no longer an unstyled native control and FileInput's default `black` variant
no longer renders dark-on-black. Verified against the freshly built CSS, so
the "prefer Dropdown" steer and the FileInput warning are removed from the
design agent's header and struck through in the notes.

Also record the 56px field shell across all ten fields, correct the stale
`buildCmd` (stage-pkg.mjs emits the .d.ts tree itself), and drop the preview
count to 39 now that BorderButton is retired.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ESLint was walking .design-sync/.cache/ and ds-bundle/, neither of which is
tracked, which buried the run under 594 errors from generated code. Ignore
both, and fix the 16 real errors in the tracked previews:

- Previews typed children as React.ReactNode without importing React; use a
  `import type { ReactNode }` instead, as the components in src/ do.
- SimpleTable inlined a styled span per row, pushing three lines past max-len;
  pull Name and Status out so the table body reads as data.
- Dropzone's WithFiles copied the generated files into a second useState via
  an effect, cascading renders. usePngFiles now hands back its own state pair.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants