Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
964dd81
introduce Tailwind v4 alongside Bootstrap
Soxasora Jul 3, 2026
a7bae70
connect Tailwind dark mode to the application theme
Soxasora Jul 3, 2026
3218875
replace Bootstrap utility classes in components
Soxasora Jul 3, 2026
88c2bbe
replace Bootstrap utility classes in pages
Soxasora Jul 3, 2026
e1fc99c
add Base UI foundations and shared class recipes
Soxasora Jul 5, 2026
2dd619a
migrate buttons to the house Button component
Soxasora Jul 7, 2026
e36bf24
migrate badges and alerts to house components
Soxasora Jul 7, 2026
9188a44
replace React Bootstrap layout primitives
Soxasora Jul 7, 2026
ccf25d0
migrate tooltips to Base UI
Soxasora Jul 7, 2026
fe2778e
migrate popovers and preview cards to Base UI
Soxasora Jul 9, 2026
03cda3a
migrate dropdown menus to Base UI
Soxasora Jul 13, 2026
0608183
migrate modals to Base UI Dialog
Soxasora Jul 13, 2026
8503b8c
migrate toast notifications to Base UI
Soxasora Jul 16, 2026
4f1e3d1
migrate editor mode tabs to Base UI
Soxasora Jul 20, 2026
ab9c842
migrate editor toolbar menus to Base UI
Soxasora Jul 20, 2026
2b99eb3
migrate the link editor popover to Base UI
Soxasora Jul 20, 2026
69b3860
migrate core form controls to Base UI
Soxasora Jul 21, 2026
994722a
migrate advanced form controls to Base UI
Soxasora Jul 24, 2026
82a71df
migrate territory selectors to Base UI Combobox
Soxasora Jul 24, 2026
a74d382
migrate mobile navigation to Base UI Drawer
Soxasora Jul 25, 2026
369068e
migrate accordions to Base UI Collapsible
Soxasora Jul 28, 2026
bda1b9f
migrate remaining toggle controls to Base UI
Soxasora Jul 29, 2026
34075ae
respect mobile safe areas in Container
Soxasora Aug 14, 2026
23c5238
upgrade Base UI and finalize component contracts
Soxasora Aug 14, 2026
952ee4e
replace remaining Bootstrap layout utilities
Soxasora Aug 14, 2026
11ad19d
add canonical design tokens
Soxasora Aug 14, 2026
e6f28bc
rename theme variables to the --sn namespace
Soxasora Aug 14, 2026
5d8cb2c
read the active theme from data-theme
Soxasora Aug 14, 2026
8a24b75
convert rendered text styles to CSS
Soxasora Aug 14, 2026
e11e189
remove unused Bootstrap component styles
Soxasora Aug 14, 2026
37788ab
move remaining global styles to app.css
Soxasora Aug 14, 2026
a6c350b
remove the Bootstrap stylesheet
Soxasora Aug 15, 2026
f13537e
replace remaining Bootstrap color utilities
Soxasora Aug 15, 2026
ee2a105
remove Bootstrap and Sass dependencies
Soxasora Aug 15, 2026
14898ae
document the styling architecture
Soxasora Aug 15, 2026
ac24daa
connect brand colors to design tokens
Soxasora Aug 15, 2026
53b7795
remove unused compatibility styles
Soxasora Aug 15, 2026
ee5a6c6
respect mobile safe areas in the footer
Soxasora Aug 15, 2026
87661d6
remove references to the undefined --sn-color token
Soxasora Aug 17, 2026
e3032c3
derive translucent brand colors with color-mix
Soxasora Aug 17, 2026
7797450
use contrast-color for territory text when supported
Soxasora Aug 17, 2026
51ebc29
layer component modules below utilities
Soxasora Aug 17, 2026
66b69b0
remove obsolete important declarations from modules
Soxasora Aug 17, 2026
2b945c6
move global element defaults into the base layer
Soxasora Aug 17, 2026
8e48789
repair cascade layer regressions
Soxasora Aug 17, 2026
aad3adf
enforce CSS module layering during lint
Soxasora Aug 17, 2026
a363c96
cleanup: remove compound pattern
Soxasora Aug 22, 2026
5f70bc6
fix migrated form control behavior
Soxasora Aug 22, 2026
73d16c3
restore accessible control semantics
Soxasora Aug 22, 2026
372a90d
clean migration styling residue
Soxasora Aug 22, 2026
077e07b
fix: restore min-width on sub multiselect
Soxasora Aug 23, 2026
563664e
stop composing color class names at runtime
Soxasora Aug 23, 2026
9a9c652
name shared design values as utilities
Soxasora Aug 23, 2026
d3386a2
unify monospace on the shared font token
Soxasora Aug 23, 2026
a870402
name the static gray tier as tokens
Soxasora Aug 24, 2026
41b29f6
add a class soundness lint gate
Soxasora Aug 24, 2026
430bda3
ride palette tokens in component modules
Soxasora Aug 24, 2026
2cdb079
use one fill utility system
Soxasora Aug 24, 2026
2ed0a96
unify link buttons on the content link color
Soxasora Aug 24, 2026
5e3bd1f
compute text-on-brand contrast with one engine
Soxasora Aug 24, 2026
7e754de
clean carried migration leftovers
Soxasora Aug 24, 2026
93a1596
tint dark link buttons from the territory primary
Soxasora Aug 29, 2026
54b61fd
migrate master's wallet onboarding to the redesign
Soxasora Aug 29, 2026
2e76991
sync the lockfile with the rebased package.json
Soxasora Aug 29, 2026
e92db91
use the shared input recipe for passphrase words
Soxasora Aug 29, 2026
0263c21
keep nested badge hints non-interactive
Soxasora Aug 29, 2026
afc103d
dismiss the link editor when focus leaves
Soxasora Aug 29, 2026
93e43b7
restore the filled warning button variant
Soxasora Aug 29, 2026
3a34666
mark navigation surfaces for the capture service
Soxasora Aug 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ For every edit on dnsmasq, it will give you the option to either edit the `/etc/

## Stack

The site is written in javascript (not typescript 😱) using [Next.js](https://nextjs.org/), a [React](https://react.dev/) framework. The backend API is provided via [GraphQL](https://graphql.org/). The database is [PostgreSQL](https://www.postgresql.org/) modeled with [Prisma](https://www.prisma.io/). The [job queue](https://github.com/timgit/pg-boss) is also maintained in PostgreSQL. We use [lnd](https://github.com/lightningnetwork/lnd) for our lightning node. A customized [Bootstrap](https://react-bootstrap.netlify.app/) theme is used for styling.
The site is written in javascript (not typescript 😱) using [Next.js](https://nextjs.org/), a [React](https://react.dev/) framework. The backend API is provided via [GraphQL](https://graphql.org/). The database is [PostgreSQL](https://www.postgresql.org/) modeled with [Prisma](https://www.prisma.io/). The [job queue](https://github.com/timgit/pg-boss) is also maintained in PostgreSQL. We use [lnd](https://github.com/lightningnetwork/lnd) for our lightning node. Styling uses [Tailwind CSS](https://tailwindcss.com/) utilities with [Base UI](https://base-ui.com/) components; the conventions are documented in [docs/dev/styling.md](./docs/dev/styling.md).

<br>

Expand Down
6 changes: 1 addition & 5 deletions capture/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,7 @@ async function addCaptureCleanupScript (page) {
const style = document.createElement('style')
style.textContent = `
#nprogress,
.navbar,
nav.d-block.d-md-none:has(.navbar),
[class*="sticky"]:has(.navbar),
.d-none.d-md-block:has(.navbar),
.d-block.d-md-none:has(.navbar) {
[data-sn-navigation] {
display: none !important;
}
`
Expand Down
66 changes: 0 additions & 66 deletions components/accordian-item.js

This file was deleted.

41 changes: 41 additions & 0 deletions components/accordion-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useEffect, useState } from 'react'
import { Collapsible, CollapsibleTrigger, CollapsiblePanel } from './ui/collapsible'
import styles from './ui/collapsible.module.css'
import ArrowRight from '@/svgs/arrow-right-s-fill.svg'
import ArrowDown from '@/svgs/arrow-down-s-fill.svg'
import { cn } from '@/lib/cn'

export default function AccordionItem ({ header, body, className, headerColor = 'var(--sn-grey)', show }) {
const [open, setOpen] = useState(!!show)

useEffect(() => {
// `show` transitions force open/close; manual toggles stay free either way
setOpen(!!show)
}, [show])

return (
<Collapsible open={open} onOpenChange={setOpen}>
<CollapsibleTrigger className='flex items-center w-full'>
{open
? <ArrowDown style={{ fill: headerColor }} height={20} width={20} />
: <ArrowRight style={{ fill: headerColor }} height={20} width={20} />}
<div style={{ color: headerColor }}>{header}</div>
</CollapsibleTrigger>
<CollapsiblePanel className={cn('mt-2', className)}>{body}</CollapsiblePanel>
</Collapsible>
)
}

export function AccordionCard ({ header, children, show, className }) {
return (
<Collapsible defaultOpen={!!show} className={cn(styles.card, className)}>
<CollapsibleTrigger className={cn(styles.cardTrigger, 'flex items-center w-full')}>
{header}
<ArrowDown className={cn(styles.cardChevron, 'ms-auto shrink-0')} height={20} width={20} />
</CollapsibleTrigger>
<CollapsiblePanel>
<div className={styles.cardBody}>{children}</div>
</CollapsiblePanel>
</Collapsible>
)
}
8 changes: 4 additions & 4 deletions components/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function SwitchAccountList () {
return (
<>
<div className='my-2'>
<div className='d-flex flex-column flex-wrap mt-2 mb-3'>
<div className='flex flex-col flex-wrap mt-2 mb-4'>
<h4 className='text-muted'>Accounts</h4>
<AccountListRow
account={{ id: USER_ID.anon, name: 'anon' }}
Expand All @@ -47,7 +47,7 @@ export default function SwitchAccountList () {
pathname: '/login',
query: { callbackUrl: window.location.origin + router.asPath, multiAuth: true }
}}
className='text-reset fw-bold'
className='text-reset font-bold'
>
<AddIcon height={20} width={20} /> existing account
</Link>
Expand Down Expand Up @@ -81,10 +81,10 @@ const AccountListRow = ({ account, selected, ...props }) => {
}

return (
<div className='d-flex flex-row'>
<div className='flex flex-row'>
<UserListRow
user={{ ...account, photoId, name }}
className='d-flex align-items-center me-2'
className='flex items-center me-2'
selected={selected}
{...props}
onNymClick={onClick}
Expand Down
15 changes: 6 additions & 9 deletions components/action-dropdown.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Dropdown from 'react-bootstrap/Dropdown'
import { Menu, MenuTrigger, MenuPopup } from '@/components/ui/menu'
import styles from './item.module.css'
import MoreIcon from '@/svgs/more-fill.svg'

Expand All @@ -7,13 +7,10 @@ export default function ActionDropdown ({ children }) {
return null
}
return (
<Dropdown className={`pointer ${styles.dropdown}`} as='span'>
<Dropdown.Toggle variant='success' as='a' onPointerDown={e => e.preventDefault()}>
<MoreIcon className='fill-grey ms-1' height={16} width={16} />
</Dropdown.Toggle>
<Dropdown.Menu>
{children}
</Dropdown.Menu>
</Dropdown>
<Menu className={`pointer ${styles.dropdown}`}>
{/* Base UI gives the rendered span button semantics. */}
<MenuTrigger aria-label='Item actions' nativeButton={false} render={<span><MoreIcon className='fill-muted ms-1' height={16} width={16} /></span>} />
<MenuPopup>{children}</MenuPopup>
</Menu>
)
}
30 changes: 8 additions & 22 deletions components/action-tooltip.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useFormikContext } from 'formik'
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
import Tooltip from 'react-bootstrap/Tooltip'
import Tooltip from '@/components/ui/tooltip'

export default function ActionTooltip ({ children, notForm, disable, overlayText, placement, noWrapper, showDelay, hideDelay, transition }) {
export default function ActionTooltip ({ children, notForm, disable, overlayText, placement, noWrapper, showDelay }) {
// if we're in a form, we want to hide tooltip on submit
let formik
if (!notForm) {
Expand All @@ -12,26 +11,13 @@ export default function ActionTooltip ({ children, notForm, disable, overlayText
return children
}
return (
<OverlayTrigger
placement={placement || 'bottom'}
overlay={
<Tooltip style={{ position: 'fixed' }}>
{overlayText}
</Tooltip>
}
trigger={['hover', 'focus']}
show={formik?.isSubmitting ? false : undefined}
delay={{ show: showDelay || 0, hide: hideDelay || 0 }}
transition={transition || false}
popperConfig={{
modifiers: {
preventOverflow: {
enabled: false
}
}
}}
<Tooltip
content={overlayText}
side={placement || 'bottom'}
delay={showDelay}
disabled={formik?.isSubmitting}
>
{noWrapper ? children : <span>{children}</span>}
</OverlayTrigger>
</Tooltip>
)
}
21 changes: 10 additions & 11 deletions components/adv-post-form.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useState, useEffect } from 'react'
import AccordianItem from './accordian-item'
import { Input, InputUserSuggest, VariableInput, Checkbox } from './form'
import InputGroup from 'react-bootstrap/InputGroup'
import AccordionItem from './accordion-item'
import { Input, InputAddon, InputUserSuggest, VariableInput, Checkbox } from './form'
import { MAX_FORWARDS } from '@/lib/constants'
import { DEFAULT_CROSSPOSTING_RELAYS } from '@/lib/nostr'
import Info from './info'
Expand Down Expand Up @@ -34,7 +33,7 @@ export default function AdvPostForm ({ children, item, storageKeyPrefix }) {
const isDirty = formik?.values.forward?.[0].nym !== '' || formik?.values.forward?.[0].pct !== '' ||
(router.query?.type === 'link' && formik?.values.text !== '')

// if the adv post form is dirty on first render, show the accordian
// if the adv post form is dirty on first render, show the accordion
if (isDirty) {
setShow(FormStatus.DIRTY)
}
Expand All @@ -54,7 +53,7 @@ export default function AdvPostForm ({ children, item, storageKeyPrefix }) {
}, [formik?.values, storageKeyPrefix])

useEffect(() => {
// force show the accordian if there is an error and the form is submitting
// force show the accordion if there is an error and the form is submitting
const hasError = formik?.errors?.forward?.length > 0
// if it's open we don't want to collapse on submit
setShow(show => hasError && formik?.isSubmitting ? FormStatus.ERROR : show)
Expand Down Expand Up @@ -101,7 +100,7 @@ export default function AdvPostForm ({ children, item, storageKeyPrefix }) {
}

return (
<AccordianItem
<AccordionItem
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>options</div>}
show={show}
body={
Expand All @@ -117,20 +116,20 @@ export default function AdvPostForm ({ children, item, storageKeyPrefix }) {
>
{({ index, AppendColumn }) => {
return (
<div key={index} className='d-flex flex-row'>
<div key={index} className='flex flex-row'>
<InputUserSuggest
name={`forward[${index}].nym`}
prepend={<InputGroup.Text>@</InputGroup.Text>}
prepend={<InputAddon>@</InputAddon>}
showValid
groupClassName={`${styles.name} me-3 mb-0`}
groupClassName={`${styles.name} me-4 mb-0`}
/>
<Input
name={`forward[${index}].pct`}
type='number'
step={5}
min={1}
max={100}
append={<InputGroup.Text className='text-monospace'>%</InputGroup.Text>}
append={<InputAddon className='font-mono'>%</InputAddon>}
groupClassName={`${styles.percent} mb-0`}
AppendColumn={AppendColumn}
/>
Expand All @@ -141,7 +140,7 @@ export default function AdvPostForm ({ children, item, storageKeyPrefix }) {
{me && itemType &&
<Checkbox
label={
<div className='d-flex align-items-center'>crosspost to nostr
<div className='flex items-center'>crosspost to nostr
<Info>
<ul>
{renderCrosspostDetails(itemType)}
Expand Down
9 changes: 8 additions & 1 deletion components/adv-post-form.module.css
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
@layer components {

/* Both flex items must shrink below their inputs' intrinsic width on narrow
screens. */
.name {
display: flex;
flex: 1 1 60%;
height: fit-content;
flex-flow: column;
min-width: 0;
}

.percent {
display: flex;
flex: 0 1 fit-content;
height: fit-content;
min-width: 0;
}

.boostMax small {
font-weight: 400;
margin-left: 0.25rem;
margin-right: 0.25rem;
opacity: 0.5;
}
}
}
24 changes: 13 additions & 11 deletions components/avatar.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { useRef, useState } from 'react'
import AvatarEditor from 'react-avatar-editor'
import Button from 'react-bootstrap/Button'
import BootstrapForm from 'react-bootstrap/Form'
import Button from '@/components/ui/button'
import { Slider } from '@/components/form'
import EditImage from '@/svgs/image-edit-fill.svg'
import Moon from '@/svgs/moon-fill.svg'
import { useShowModal } from './modal'
import { FileUpload } from './file-upload'
import { gql } from '@apollo/client'
import { useMutation } from '@apollo/client/react'
import { useToast } from './toast'
import { useToast } from '@/components/ui/toast'

export default function Avatar ({ onSuccess }) {
const [cropPhoto] = useMutation(gql`
Expand All @@ -25,7 +25,7 @@ export default function Avatar ({ onSuccess }) {
const ref = useRef()

return (
<div className='text-end mt-1 p-4'>
<div className='text-end mt-1 p-6'>
<AvatarEditor
ref={ref} width={200} height={200}
image={file}
Expand All @@ -35,13 +35,14 @@ export default function Avatar ({ onSuccess }) {
height: 'auto'
}}
/>
<BootstrapForm.Group controlId='formBasicRange'>
<BootstrapForm.Range
onChange={e => setScale(parseFloat(e.target.value))}
min={1} max={2} step='0.05'
// defaultValue={scale}
<div>
{/* Keep the thumb and crop scale aligned on the first render. */}
<Slider
min={1} max={2} step={0.05}
defaultValue={1}
onValueChange={setScale}
/>
</BootstrapForm.Group>
</div>
<Button
onClick={async () => {
const rect = ref.current.getCroppingRect()
Expand Down Expand Up @@ -118,7 +119,8 @@ export default function Avatar ({ onSuccess }) {
setUploading(true)
}}
>
<div className='position-absolute p-1 bg-dark pointer' style={{ bottom: '0', right: '0' }}>
{/* deliberately static across themes: the chip backs the fill-white icons */}
<div className='absolute p-1 bg-dark pointer' style={{ bottom: '0', right: '0' }}>
{uploading
? <Moon className='fill-white spin' />
: <EditImage className='fill-white' />}
Expand Down
Loading
Loading