pl-fe: styles migrations

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2025-10-19 19:28:38 +02:00
parent fced7f93ff
commit e61d6e64e1
12 changed files with 219 additions and 113 deletions

View File

@ -55,7 +55,6 @@ const Button = React.forwardRef<HTMLButtonElement, IButton>(({
const themeClass = useButtonStyles({
theme,
block,
disabled,
size,
});
@ -68,7 +67,7 @@ const Button = React.forwardRef<HTMLButtonElement, IButton>(({
const renderButton = () => (
<button
{...props}
className={clsx('rtl:space-x-reverse', themeClass, className)}
className={clsx(themeClass, className)}
disabled={disabled}
onClick={handleClick}
ref={ref}

View File

@ -27,7 +27,6 @@ type ButtonThemes = keyof typeof themes
type IButtonStyles = {
theme: ButtonThemes;
block: boolean;
disabled: boolean;
size: ButtonSizes;
}
@ -35,12 +34,10 @@ type IButtonStyles = {
const useButtonStyles = ({
theme,
block,
disabled,
size,
}: IButtonStyles) => {
const buttonStyle = clsx({
'inline-flex items-center place-content-center border font-medium rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 appearance-none transition-all': true,
'select-none disabled:opacity-75 disabled:cursor-default': disabled,
'rtl:space-x-reverse inline-flex items-center place-content-center border font-medium rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 appearance-none transition-all disabled:select-none disabled:opacity-75 disabled:cursor-default': true,
[`${themes[theme]}`]: true,
[`${sizes[size]}`]: true,
'flex w-full justify-center': block,

View File

@ -56,7 +56,7 @@ const Main: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ children, classN
<main
className={clsx({
'⁂-layout__main': true,
'⁂-layout__main--chats-page': features.chats,
'⁂-layout__main--chats': features.chats,
}, className)}
>
{children}

View File

@ -749,7 +749,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
</div>
<div className='mt-6 flex w-full justify-end sm:pb-1'>
<HStack space={2} className='mt-10'>
<HStack space={2} className='mt-10' wrap>
<SubscriptionButton account={account} />
{renderMessageButton()}
{renderShareButton()}

View File

@ -5,6 +5,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { length } from 'stringz';
import 'pl-fe/styles/new/compose.scss';
import {
submitCompose,
clearComposeSuggestions,
@ -97,40 +98,19 @@ interface IComposeButton extends Pick<
const ComposeButton: React.FC<IComposeButton> = ({ actionsMenu, disabled, icon, text, ...props }) => {
const intl = useIntl();
const containerClassName = 'flex items-center gap-px text-sm font-medium text-gray-100';
const buttonClassName = 'inline-flex select-none appearance-none border border-transparent bg-primary-500 transition-all hover:bg-primary-400 focus:bg-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-300 focus:ring-offset-2 disabled:cursor-default disabled:opacity-75 dark:hover:bg-primary-600';
const button = (
<button
{...props}
disabled={disabled}
className={clsx({
'place-content-center items-center gap-x-2 px-4 py-2 rtl:space-x-reverse': true,
[buttonClassName]: true,
'rounded-l-full pr-2': actionsMenu,
[containerClassName]: !actionsMenu,
'rounded-full': !actionsMenu,
})}
>
{icon ? <Icon src={icon} className='size-4' /> : null}
<span>{text}</span>
</button>
return (
<div className='⁂-compose-form__button__container'>
<button {...props} disabled={disabled} className='⁂-compose-form__button'>
{icon ? <Icon src={icon} /> : null}
<span>{text}</span>
</button>
<DropdownMenu items={actionsMenu} placement='bottom' disabled={disabled}>
<button className='⁂-compose-form__button__actions' title={intl.formatMessage(messages.more)}>
<SvgIcon src={require('@phosphor-icons/core/regular/caret-down.svg')} />
</button>
</DropdownMenu>
</div>
);
if (actionsMenu) {
return (
<div className={containerClassName}>
{button}
<DropdownMenu items={actionsMenu} placement='bottom' disabled={disabled}>
<button className={clsx('h-full cursor-pointer py-2.5 pl-1 pr-3 last:rounded-r-full', buttonClassName)} title={intl.formatMessage(messages.more)}>
<SvgIcon src={require('@phosphor-icons/core/regular/caret-down.svg')} className='size-4' />
</button>
</DropdownMenu>
</div>
);
}
return button;
};
interface IComposeForm<ID extends string> {
@ -175,7 +155,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
const [composeFocused, setComposeFocused] = useState(false);
const formRef = useRef<HTMLDivElement>(null);
const formRef = useRef<HTMLFormElement>(null);
const editorRef = useRef<LexicalEditor>(null);
const { isDraggedOver } = useDraggedFiles(formRef);
@ -302,14 +282,14 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
}, []);
const renderButtons = useCallback(() => (
<HStack alignItems='center' space={2}>
<div className='⁂-compose-form__buttons'>
<UploadButtonContainer composeId={id} />
<EmojiPickerDropdown onPickEmoji={handleEmojiPick} condensed={shouldCondense} />
{features.polls && <PollButton composeId={id} />}
{features.scheduledStatuses && <ScheduleButton composeId={id} />}
{anyMedia && features.spoilers && <SensitiveMediaButton composeId={id} />}
{features.interactionRequests && <InteractionPolicyButton composeId={id} />}
</HStack>
</div>
), [features, id, anyMedia]);
const showModifiers = !condensed && (compose.media_attachments.length || compose.is_uploading || compose.poll?.options.length || compose.schedule);
@ -347,7 +327,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
if (features.richText) selectButtons.push(<ContentTypeButton key='compose-type-button' composeId={id} />);
if (features.postLanguages) selectButtons.push(<LanguageDropdown key='language-dropdown' composeId={id} />);
const actionsMenu: Menu | undefined = [];
const actionsMenu: Menu = [];
if (features.createStatusPreview) {
actionsMenu.push({
@ -364,7 +344,14 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
});
return (
<Stack className='w-full' space={4} ref={formRef} onClick={handleClick} element='form' onSubmit={handleSubmit}>
<form
className={clsx('⁂-compose-form', {
'⁂-compose-form--with-avatar': withAvatar,
})}
ref={formRef}
onClick={handleClick}
onSubmit={handleSubmit}
>
{!!compose.in_reply_to && compose.approvalRequired && (
<Warning
message={(
@ -402,10 +389,10 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
<ComposeEditor
key={modifiedLanguage}
ref={editorRef}
className={transparent
? ''
: 'rounded-md border-gray-400 px-3 py-2 ring-2 focus-within:border-primary-500 focus-within:ring-primary-500 dark:border-gray-800 dark:ring-gray-800 dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500'}
placeholderClassName={transparent ? '' : 'pt-2'}
className={clsx('⁂-compose-form__editor', {
'⁂-compose-form__editor--transparent': transparent,
})}
placeholderClassName='⁂-compose-form__editor__placeholder'
composeId={id}
condensed={condensed}
eventDiscussion={!!event}
@ -429,23 +416,22 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
<PreviewComposeContainer composeId={id} />
<div
className={clsx('flex flex-wrap items-center justify-between', {
'hidden': condensed,
'ml-[-56px] sm:ml-0': withAvatar,
className={clsx('⁂-compose-form__footer', {
'⁂-compose-form__footer--condensed': condensed,
})}
>
{renderButtons()}
<HStack space={4} alignItems='center' className='ml-auto rtl:ml-0 rtl:mr-auto'>
<div className='⁂-compose-form__actions'>
{maxTootChars && (
<HStack space={1} alignItems='center'>
<div className='⁂-compose-form__counter'>
<TextCharacterCounter max={maxTootChars} text={text} />
<VisualCharacterCounter max={maxTootChars} text={text} />
</HStack>
</div>
)}
<ComposeButton type='submit' icon={publishIcon} text={publishText} disabled={!canSubmit} actionsMenu={actionsMenu} />
</HStack>
</div>
{compose.redacting && (
<List>
@ -462,7 +448,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
</List>
)}
</div>
</Stack>
</form>
);
};

View File

@ -44,7 +44,7 @@ const SpoilerInput: React.FC<ISpoilerInput> = ({
theme={theme}
searchTokens={[':']}
id='cw-spoiler-input'
className='rounded-md !bg-transparent dark:!bg-transparent'
className='⁂-compose-form__spoiler-input'
lang={modified_language || undefined}
/>
);

View File

@ -5,8 +5,8 @@ import { useLocation, useRouteMatch } from 'react-router-dom';
import { groupComposeModal } from 'pl-fe/actions/compose';
import { useGroup } from 'pl-fe/api/hooks/groups/use-group';
import Avatar from 'pl-fe/components/ui/avatar';
import Button from 'pl-fe/components/ui/button';
import HStack from 'pl-fe/components/ui/hstack';
import Icon from 'pl-fe/components/ui/icon';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useModalsStore } from 'pl-fe/stores/modals';
@ -34,17 +34,14 @@ const HomeComposeButton: React.FC<IComposeButton> = ({ shrink }) => {
const onOpenCompose = () => openModal('COMPOSE');
return (
<Button
theme='accent'
size='lg'
<button
className='⁂-sidebar-navigation__compose-button'
onClick={onOpenCompose}
block
icon={shrink ? require('@phosphor-icons/core/regular/plus.svg') : undefined}
>
{!shrink && (
<FormattedMessage id='navigation.compose' defaultMessage='Compose' />
)}
</Button>
{shrink
? <Icon src={require('@phosphor-icons/core/regular/plus.svg')} />
: <FormattedMessage id='navigation.compose' defaultMessage='Compose' />}
</button>
);
};
@ -60,22 +57,19 @@ const GroupComposeButton: React.FC<IComposeButton> = ({ shrink }) => {
};
return (
<Button
theme='accent'
size='lg'
<button
className='⁂-sidebar-navigation__compose-button'
onClick={onOpenCompose}
block
icon={shrink ? require('@phosphor-icons/core/regular/plus.svg') : undefined}
>
{!shrink && (
<HStack space={3} alignItems='center'>
{shrink
? <Icon src={require('@phosphor-icons/core/regular/plus.svg')} />
: <HStack space={3} alignItems='center'>
<Avatar className='-my-1 border-2 border-white' size={30} src={group.avatar} alt={group.avatar_description} />
<span>
<FormattedMessage id='navigation.compose_group' defaultMessage='Compose to group' />
</span>
</HStack>
)}
</Button>
</HStack>}
</button>
);
};

View File

@ -3,10 +3,9 @@ import React, { useRef } from 'react';
import { useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import 'pl-fe/styles/new/timelines.scss';
import { uploadCompose } from 'pl-fe/actions/compose';
import Avatar from 'pl-fe/components/ui/avatar';
import Card, { CardBody } from 'pl-fe/components/ui/card';
import HStack from 'pl-fe/components/ui/hstack';
import Layout from 'pl-fe/components/ui/layout';
import LinkFooter from 'pl-fe/features/ui/components/link-footer';
import {
@ -56,41 +55,38 @@ const HomeLayout: React.FC<IHomeLayout> = ({ children }) => {
return (
<>
<Layout.Main className='black:space-y-0 dark:divide-gray-800 sm:space-y-3'>
<Layout.Main className='⁂-layout__main--home'>
{me && (
<Card
className={clsx('relative z-[1] border-gray-200 transition black:border-b black:border-gray-800 dark:border-gray-800 max-sm:border-b max-sm:shadow-none', {
'border-2 border-primary-600 border-dashed z-[99]': isDragging,
'ring-2 ring-offset-2 ring-primary-600': isDraggedOver,
<div
className={clsx('⁂-compose-block', {
'⁂-compose-block--dragging': isDragging,
'⁂-compose-block--dragged-over': isDraggedOver,
})}
variant='rounded'
ref={composeBlock}
>
<CardBody>
<HStack alignItems='start' space={2}>
{!disableUserProvidedMedia && (
<Link to={`/@${acct}`}>
<Avatar src={avatar} alt={account?.avatar_description} isCat={account?.is_cat} size={42} username={account?.username} />
</Link>
)}
<div className='⁂-compose-block__body'>
{!disableUserProvidedMedia && (
<Link className='⁂-compose-block__avatar' to={`/@${acct}`}>
<Avatar src={avatar} alt={account?.avatar_description} isCat={account?.is_cat} size={42} username={account?.username} />
</Link>
)}
<div className='w-full translate-y-0.5'>
<ComposeForm
id={composeId}
shouldCondense
autoFocus={false}
clickableAreaRef={composeBlock}
withAvatar
transparent
/>
</div>
</HStack>
</CardBody>
</Card>
<div className='⁂-compose-block__form'>
<ComposeForm
id={composeId}
shouldCondense
autoFocus={false}
clickableAreaRef={composeBlock}
withAvatar
transparent
/>
</div>
</div>
</div>
)}
{children}
</Layout.Main>
</Layout.Main >
<Layout.Aside>
{!me && (

View File

@ -0,0 +1,56 @@
.-compose-form {
@apply flex flex-col gap-y-4 w-full;
&__spoiler-input {
@apply rounded-md !bg-transparent dark:!bg-transparent;
}
&__editor:not(&__editor--transparent) {
@apply rounded-md border-gray-400 px-3 py-2 ring-2 focus-within:border-primary-500 focus-within:ring-primary-500 dark:border-gray-800 dark:ring-gray-800 dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500;
}
&__editor:not(&__editor--transparent) &__editor__placeholder {
@apply pt-2;
}
&__footer {
@apply flex flex-wrap items-center justify-between;
&--condensed {
@apply hidden;
}
}
&--with-avatar &__footer {
@apply ml-[-56px] sm:ml-0;
}
&__buttons {
@apply flex items-center gap-2;
}
&__actions {
@apply flex items-center gap-4 ml-auto rtl:ml-0 rtl:mr-auto;
}
&__counter {
@apply flex items-center gap-1;
}
&__button {
@apply place-content-center items-center gap-x-2 px-4 py-2 rtl:space-x-reverse inline-flex select-none appearance-none border border-transparent bg-primary-500 transition-all hover:bg-primary-400 focus:bg-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-300 focus:ring-offset-2 disabled:cursor-default disabled:opacity-75 dark:hover:bg-primary-600 rounded-l-full pr-2;
&__container {
@apply flex items-center gap-px text-sm font-medium text-gray-100;
svg {
@apply size-4;
}
}
&__actions {
@apply h-full cursor-pointer py-2.5 pl-1 pr-3 last:rounded-r-full inline-flex select-none appearance-none border border-transparent bg-primary-500 transition-all hover:bg-primary-400 focus:bg-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-300 focus:ring-offset-2 disabled:cursor-default disabled:opacity-75 dark:hover:bg-primary-600;
}
}
}

View File

@ -2,6 +2,9 @@
html {
height: 100%;
--font-sans: pl-fe i18n, 'Inter', ui-sans-serif, system-ui, -apple-system, 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-mono: 'Roboto Mono', ui-monospace, monospace;
}
body {
@ -162,6 +165,14 @@ body {
&__links {
@apply flex flex-col gap-y-1.5;
}
&__compose-button {
@include mixins.button($theme: accent, $size: lg, $block: true);
svg {
@apply size-4;
}
}
}
.-sidebar-navigation-link {
@ -308,7 +319,7 @@ body {
&__main {
@apply md:col-span-12 lg:col-span-9 xl:col-span-6 pb-14 lg:pb-0 xl:pb-16 black:border-gray-800 lg:black:border-l xl:black:border-r;
&--chats-page {
&--chats {
@apply xl:pb-16;
}
}

View File

@ -1,8 +1,8 @@
@mixin text($family: sans, $size: md, $theme: default, $tracking: normal, $transform: normal, $truncate: false, $weight: normal) {
@if $family == sans {
@apply font-sans;
font-family: var(--font-sans);
} @else if $family == mono {
font-family: 'Roboto Mono', ui-monospace, monospace;
font-family: var(--font-mono);
} @else {
@warn "Unknown font family `#{$family}`.";
}
@ -109,10 +109,51 @@
@warn "Unknown card size `#{$size}`.";
}
} @else if $variant == slim {
@apply py-4;
padding-top: 1rem;
padding-bottom: 1rem;
}
@if $size != xl {
@apply .black:rounded-none;
@apply black:rounded-none;
}
}
@mixin button($theme: secondary, $block: false, $size: md) {
@apply rtl:space-x-reverse inline-flex items-center place-content-center border font-medium rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 appearance-none transition-all disabled:select-none disabled:opacity-75 disabled:cursor-default;
@if $theme == primary {
@apply bg-primary-500 hover:bg-primary-400 dark:hover:bg-primary-600 border-transparent focus:bg-primary-500 text-gray-100 focus:ring-primary-300;
} @else if $theme == secondary {
@apply border-transparent bg-primary-100 dark:bg-primary-800 hover:bg-primary-50 dark:hover:bg-primary-700 focus:bg-primary-100 dark:focus:bg-primary-800 text-primary-500 dark:text-primary-200;
} @else if $theme == tertiary {
@apply bg-white dark:bg-primary-900 border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-900 dark:text-gray-100 focus:ring-primary-500;
} @else if $theme == accent {
@apply border-transparent bg-secondary-500 hover:bg-secondary-400 focus:bg-secondary-500 text-gray-100 focus:ring-secondary-300;
} @else if $theme == danger {
@apply border-transparent bg-danger-100 dark:bg-danger-900 text-danger-600 dark:text-danger-200 hover:bg-danger-600 hover:text-gray-100 dark:hover:text-gray-100 dark:hover:bg-danger-500 focus:ring-danger-500;
} @else if $theme == transparent {
@apply border-transparent bg-transparent text-primary-600 dark:text-accent-blue dark:bg-transparent hover:bg-gray-200 dark:hover:bg-gray-800/50;
} @else if $theme == outline {
@apply border-gray-100 border-2 bg-transparent text-gray-100 hover:bg-white/10;
} @else if $theme == muted {
@apply border border-solid bg-transparent border-gray-400 dark:border-gray-800 hover:border-primary-300 dark:hover:border-primary-700 focus:border-primary-500 text-gray-800 dark:text-gray-100 focus:ring-primary-500;
} @else {
@warn "Unknown button theme `#{$theme}`.";
}
@if $size == xs {
@apply gap-x-1.5 px-2 py-1 text-xs;
} @else if $size == sm {
@apply gap-x-2 px-3 py-1.5 text-xs leading-4;
} @else if $size == md {
@apply gap-x-2 px-4 py-2 text-sm;
} @else if $size == lg {
@apply gap-x-2 px-6 py-3 text-base;
} @else {
@warn "Unknown button size `#{$size}`.";
}
@if $block {
@apply flex w-full justify-center;
}
}

View File

@ -0,0 +1,26 @@
@use 'mixins';
.-layout__main--home {
@apply black:space-y-0 dark:divide-gray-800 sm:space-y-3;
}
.-compose-block {
@include mixins.card($variant: rounded);
@apply relative z-[1] border-gray-200 transition black:border-b black:border-gray-800 dark:border-gray-800 max-sm:border-b max-sm:shadow-none;
&--dragging {
@apply border-2 border-primary-600 border-dashed z-[99];
}
&--dragged-over {
@apply ring-2 ring-offset-2 ring-primary-600;
}
&__body {
@apply flex items-start gap-2;
}
&__form {
@apply w-full translate-y-0.5;
}
}