feat: standardize input styling with tier system, add depth and admin features
- Define 4 input tier CSS classes (input-standard, input-search, input-embedded, input-danger) in globals.css, migrating ~50 inputs across ~28 component files to use them - Add subtle border and inset shadow to solid input tiers for resting-state visibility - Fix focus ring clipping in settings panel scroll container - Fix phantom Tailwind tokens (border-border-primary, placeholder-txt-muted) - Add admin user management panel, storage management, and account deletion utilities
This commit is contained in:
@@ -83,7 +83,7 @@ export function ExplorePage() {
|
||||
placeholder="Search spaces..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearchChange(e.target.value)}
|
||||
className="w-full bg-surface-base text-txt-primary text-sm px-3 py-1.5 rounded-[4px] outline-none placeholder:text-txt-tertiary/50 focus:ring-1 focus:ring-accent-primary transition-all"
|
||||
className="input-search w-full"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
@@ -230,10 +230,10 @@ function SpaceCard({
|
||||
: null;
|
||||
|
||||
const iconUrl = space.icon
|
||||
? (space.icon.startsWith('http') ? space.icon : `/api/uploads/${space.icon}`)
|
||||
? (space.icon.startsWith('http') || space.icon.startsWith('/') ? space.icon : `/api/uploads/${space.icon}`)
|
||||
: null;
|
||||
const bannerUrl = space.banner
|
||||
? (space.banner.startsWith('http') ? space.banner : `/api/uploads/${space.banner}`)
|
||||
? (space.banner.startsWith('http') || space.banner.startsWith('/') ? space.banner : `/api/uploads/${space.banner}`)
|
||||
: null;
|
||||
|
||||
// Extract dominant colors from icon when no banner is set
|
||||
@@ -412,7 +412,7 @@ function SpaceCard({
|
||||
onChange={(e) => setRequestMessage(e.target.value.slice(0, 200))}
|
||||
placeholder="Why do you want to join? (optional)"
|
||||
rows={2}
|
||||
className="w-full px-3 py-2 bg-surface-input rounded text-sm text-txt-primary outline-none focus:ring-1 focus:ring-accent-primary resize-none placeholder:text-txt-tertiary"
|
||||
className="input-standard w-full resize-none"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
|
||||
@@ -268,7 +268,7 @@ function AddFriendTab({
|
||||
placeholder="You can add a friend with their username"
|
||||
value={addUsername}
|
||||
onChange={(e) => setAddUsername(e.target.value)}
|
||||
className="w-full bg-surface-base text-txt-primary px-4 py-3 rounded-lg border border-transparent focus:border-txt-link outline-none transition-all placeholder:text-txt-tertiary/50"
|
||||
className="input-search w-full px-4 py-3 rounded-lg"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
@@ -301,7 +301,7 @@ function AddFriendTab({
|
||||
placeholder="Search people..."
|
||||
value={discoverQuery}
|
||||
onChange={(e) => handleDiscoverSearch(e.target.value)}
|
||||
className="w-full bg-surface-base text-txt-primary text-sm px-3 py-1.5 rounded-[4px] outline-none placeholder:text-txt-tertiary/50 focus:ring-1 focus:ring-accent-primary transition-all"
|
||||
className="input-search w-full"
|
||||
/>
|
||||
{discoverQuery && (
|
||||
<button
|
||||
@@ -371,10 +371,10 @@ function UserDiscoverCard({
|
||||
: null;
|
||||
|
||||
const avatarUrl = user.avatar
|
||||
? (user.avatar.startsWith('http') ? user.avatar : `/api/uploads/${user.avatar}`)
|
||||
? (user.avatar.startsWith('http') || user.avatar.startsWith('/') ? user.avatar : `/api/uploads/${user.avatar}`)
|
||||
: null;
|
||||
const bannerUrl = user.banner
|
||||
? (user.banner.startsWith('http') ? user.banner : `/api/uploads/${user.banner}`)
|
||||
? (user.banner.startsWith('http') || user.banner.startsWith('/') ? user.banner : `/api/uploads/${user.banner}`)
|
||||
: null;
|
||||
|
||||
const handleSendRequest = async () => {
|
||||
|
||||
@@ -229,7 +229,7 @@ export function Message({ message, isCompact, isFirstInGroup }: MessageProps) {
|
||||
value={editContent}
|
||||
onChange={(e) => setEditContent(e.target.value)}
|
||||
onKeyDown={handleEditSubmit}
|
||||
className="w-full p-3 bg-surface-input rounded-lg text-txt-primary outline-none resize-none text-[15px] leading-[1.5] shadow-inner"
|
||||
className="input-standard w-full p-3 rounded-lg resize-none text-[15px] leading-[1.5] shadow-inner"
|
||||
rows={2}
|
||||
autoFocus
|
||||
/>
|
||||
@@ -262,9 +262,9 @@ export function Message({ message, isCompact, isFirstInGroup }: MessageProps) {
|
||||
<div className="mt-1 grid gap-2">
|
||||
{message.attachments.map((att) => {
|
||||
const isImage = att.mimetype.startsWith('image/');
|
||||
const attUrl = att.filename.startsWith('http') ? att.filename : `/api/uploads/${att.filename}`;
|
||||
const attUrl = att.filename.startsWith('http') || att.filename.startsWith('/') ? att.filename : `/api/uploads/${att.filename}`;
|
||||
const thumbUrl = att.thumbnailFilename
|
||||
? (att.thumbnailFilename.startsWith('http') ? att.thumbnailFilename : `/api/uploads/${att.thumbnailFilename}`)
|
||||
? (att.thumbnailFilename.startsWith('http') || att.thumbnailFilename.startsWith('/') ? att.thumbnailFilename : `/api/uploads/${att.thumbnailFilename}`)
|
||||
: null;
|
||||
if (isImage) {
|
||||
return (
|
||||
|
||||
@@ -349,7 +349,7 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
|
||||
onKeyDown={handleKeyDown}
|
||||
onPaste={canAttachFiles ? handlePaste : undefined}
|
||||
placeholder={`Message ${channelName.startsWith('@') ? channelName : `#${channelName}`}`}
|
||||
className="flex-1 py-[10px] px-1 bg-transparent text-txt-primary placeholder-txt-tertiary/60 outline-none resize-none text-[15px] leading-[1.375rem] max-h-[50vh] scrollbar-thin"
|
||||
className="input-embedded flex-1 py-[10px] px-1 resize-none text-[15px] leading-[1.375rem] max-h-[50vh] scrollbar-thin"
|
||||
rows={1}
|
||||
|
||||
/>
|
||||
|
||||
@@ -172,7 +172,7 @@ export function SearchPopover({ open, onClose, anchorRef, channelId, isDm, onJum
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search messages..."
|
||||
className="flex-1 bg-transparent text-txt-primary text-[14px] placeholder-txt-tertiary outline-none"
|
||||
className="input-embedded flex-1 text-[14px]"
|
||||
/>
|
||||
{query && (
|
||||
<button
|
||||
@@ -210,7 +210,7 @@ export function SearchPopover({ open, onClose, anchorRef, channelId, isDm, onJum
|
||||
value={fromFilter}
|
||||
onChange={(e) => setFromFilter(e.target.value)}
|
||||
placeholder="username"
|
||||
className="w-full bg-surface-input rounded px-2 py-1 text-[13px] text-txt-primary placeholder-txt-tertiary outline-none"
|
||||
className="input-search w-full px-2 py-1 text-[13px]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -218,7 +218,7 @@ export function SearchPopover({ open, onClose, anchorRef, channelId, isDm, onJum
|
||||
<select
|
||||
value={hasFilter}
|
||||
onChange={(e) => setHasFilter(e.target.value)}
|
||||
className="w-full bg-surface-input rounded px-2 py-1 text-[13px] text-txt-primary outline-none appearance-none cursor-pointer"
|
||||
className="input-search w-full px-2 py-1 text-[13px] appearance-none cursor-pointer"
|
||||
>
|
||||
<option value="">Any</option>
|
||||
<option value="file">File</option>
|
||||
@@ -232,7 +232,7 @@ export function SearchPopover({ open, onClose, anchorRef, channelId, isDm, onJum
|
||||
type="date"
|
||||
value={beforeFilter}
|
||||
onChange={(e) => setBeforeFilter(e.target.value)}
|
||||
className="w-full bg-surface-input rounded px-2 py-1 text-[13px] text-txt-primary outline-none"
|
||||
className="input-search w-full px-2 py-1 text-[13px]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -241,7 +241,7 @@ export function SearchPopover({ open, onClose, anchorRef, channelId, isDm, onJum
|
||||
type="date"
|
||||
value={afterFilter}
|
||||
onChange={(e) => setAfterFilter(e.target.value)}
|
||||
className="w-full bg-surface-input rounded px-2 py-1 text-[13px] text-txt-primary outline-none"
|
||||
className="input-search w-full px-2 py-1 text-[13px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user