Browse Source

fix: message specific styling/overflow

pull/586/head
Dan Ditomaso 1 year ago
parent
commit
300430df95
  1. 2
      src/components/PageLayout.tsx
  2. 49
      src/components/UI/Avatar.tsx
  3. 7
      src/components/UI/Sidebar/sidebarButton.tsx
  4. 3
      src/core/stores/deviceStore.ts
  5. 8
      src/pages/Messages.tsx

2
src/components/PageLayout.tsx

@ -112,7 +112,7 @@ export const PageLayout = ({
{rightBar && ( {rightBar && (
<aside <aside
className={cn( className={cn(
"w-[281px] shrink-0 border-l border-slate-300 dark:border-slate-700 p-2 overflow-hidden", "max-w-[288px] shrink-0 border-l border-slate-300 dark:border-slate-700 p-2 overflow-hidden",
rightBarClassName rightBarClassName
)} )}
> >

49
src/components/UI/Avatar.tsx

@ -1,6 +1,5 @@
import { cn } from "@core/utils/cn.ts"; import { cn } from "@core/utils/cn.ts";
import { LockKeyholeOpenIcon } from 'lucide-react'; import { LockKeyholeOpenIcon } from 'lucide-react';
import type React from "react";
type RGBColor = { type RGBColor = {
r: number; r: number;
@ -16,7 +15,6 @@ interface AvatarProps {
showError?: boolean; showError?: boolean;
} }
// biome-ignore lint/complexity/noStaticOnlyClass: stop being annoying Biome
class ColorUtils { class ColorUtils {
static hexToRgb(hex: number): RGBColor { static hexToRgb(hex: number): RGBColor {
return { return {
@ -42,22 +40,11 @@ class ColorUtils {
} }
} }
export const Avatar: React.FC<AvatarProps> = ({
text,
size = "sm",
showError = false,
className,
}) => {
const sizes = {
sm: "size-10 text-xs font-light",
lg: "size-16 text-lg",
};
// Pick a color based on the text provided to function
const getColorFromText = (text: string): RGBColor => { const getColorFromText = (text: string): RGBColor => {
let hash = 0; let hash = 0;
for (let i = 0; i < text.length; i++) { for (let i = 0; i < text.length; i++) {
hash = text.charCodeAt(i) + ((hash << 5) - hash); hash = text.charCodeAt(i) + ((hash << 5) - hash);
hash |= 0;
} }
return { return {
@ -68,21 +55,28 @@ export const Avatar: React.FC<AvatarProps> = ({
}; };
}; };
export const Avatar = ({
text,
size = "sm",
showError = false,
className,
}: AvatarProps) => {
const sizes = {
sm: "size-10 text-xs font-light",
lg: "size-16 text-lg",
};
const safeText = text?.toString().toUpperCase() ?? "UNK"; const safeText = text?.toString().toUpperCase() ?? "UNK";
const bgColor = getColorFromText(safeText); const bgColor = getColorFromText(safeText);
const isLight = ColorUtils.isLight(bgColor); const isLight = ColorUtils.isLight(bgColor);
const textColor = isLight ? "#000000" : "#FFFFFF"; const textColor = isLight ? "#000000" : "#FFFFFF";
const initials = safeText.slice(0, 4) ?? "UNK"; const initials = safeText.slice(0, 4) || "UNK";
return ( return (
<div <div
className={cn( className={cn(
`flex `relative flex items-center justify-center rounded-full font-semibold
relative `,
rounded-full
items-center
justify-center
font-semibold`,
sizes[size], sizes[size],
className, className,
)} )}
@ -91,8 +85,17 @@ export const Avatar: React.FC<AvatarProps> = ({
color: textColor, color: textColor,
}} }}
> >
{showError ? <LockKeyholeOpenIcon className="size-4 absolute bottom-0 right-0 z-10 text-red-500 stroke-3" /> : null} {showError ? (
<p className="p-1">{initials}</p> <LockKeyholeOpenIcon
className="absolute bottom-0 right-0 z-10 size-4 text-red-500 stroke-3"
aria-hidden="true"
/>
) : null}
<p
className="p-1"
>
{initials}
</p>
</div> </div>
); );
}; };

7
src/components/UI/Sidebar/sidebarButton.tsx

@ -38,6 +38,7 @@ export const SidebarButton = ({
)} )}
disabled={disabled} disabled={disabled}
> >
{/* Icon */}
{Icon && ( {Icon && (
<Icon <Icon
size={isCollapsed ? 20 : 18} size={isCollapsed ? 20 : 18}
@ -50,9 +51,12 @@ export const SidebarButton = ({
<span <span
className={cn( className={cn(
'flex justify-start text-left', 'flex justify-start text-left',
'min-w-0',
'text-wrap',
'px-1',
'transition-all duration-300 ease-in-out', 'transition-all duration-300 ease-in-out',
isCollapsed isCollapsed
? 'opacity-0 max-w-0 invisible flex-1 w-0 whitespace-wrap' ? 'opacity-0 max-w-0 invisible w-0 overflow-hidden'
: 'opacity-100 max-w-full visible flex-1 whitespace-normal' : 'opacity-100 max-w-full visible flex-1 whitespace-normal'
)} )}
> >
@ -63,6 +67,7 @@ export const SidebarButton = ({
<div <div
className={cn( className={cn(
"ml-auto flex-shrink-0 justify-end text-white text-xs rounded-full px-1.5 py-0.5 bg-red-600", "ml-auto flex-shrink-0 justify-end text-white text-xs rounded-full px-1.5 py-0.5 bg-red-600",
"flex-shrink-0",
"transition-opacity duration-300 ease-in-out", "transition-opacity duration-300 ease-in-out",
isCollapsed ? 'opacity-0 invisible' : 'opacity-100 visible' isCollapsed ? 'opacity-0 invisible' : 'opacity-100 visible'
)} )}

3
src/core/stores/deviceStore.ts

@ -41,7 +41,7 @@ export interface Device {
pendingSettingsChanges: boolean; pendingSettingsChanges: boolean;
messageDraft: string; messageDraft: string;
unreadCounts: Map<number, number>; unreadCounts: Map<number, number>;
nodesMap: Map<number, Protobuf.Mesh.NodeInfo>; nodesMap: Map<number, Protobuf.Mesh.NodeInfo>; // dont access directly, use getNodes, or getNode
dialog: { dialog: {
import: boolean; import: boolean;
QR: boolean; QR: boolean;
@ -148,7 +148,6 @@ export const useDeviceStore = createStore<DeviceState>((set, get) => ({
unreadCounts: new Map(), unreadCounts: new Map(),
nodesMap: new Map(), nodesMap: new Map(),
// --- Standard Setter Methods ---
setStatus: (status: Types.DeviceStatusEnum) => { setStatus: (status: Types.DeviceStatusEnum) => {
set( set(
produce<DeviceState>((draft) => { produce<DeviceState>((draft) => {

8
src/pages/Messages.tsx

@ -25,7 +25,7 @@ export const MessagesPage = () => {
const { isCollapsed } = useSidebar() const { isCollapsed } = useSidebar()
const [searchTerm, setSearchTerm] = useState<string>(""); const [searchTerm, setSearchTerm] = useState<string>("");
const filteredNodes: NodeInfoWithUnread[] = useMemo(() => { const filteredNodes = (): NodeInfoWithUnread[] => {
const lowerCaseSearchTerm = searchTerm.toLowerCase(); const lowerCaseSearchTerm = searchTerm.toLowerCase();
return getNodes(node => { return getNodes(node => {
@ -38,7 +38,7 @@ export const MessagesPage = () => {
unreadCount: unreadCounts.get(node.num) ?? 0, unreadCount: unreadCounts.get(node.num) ?? 0,
})) }))
.sort((a, b) => b.unreadCount - a.unreadCount); .sort((a, b) => b.unreadCount - a.unreadCount);
}, [getNodes, searchTerm, unreadCounts]); }
const allChannels = Array.from(channels.values()); const allChannels = Array.from(channels.values());
@ -121,7 +121,7 @@ export const MessagesPage = () => {
<div className={cn( <div className={cn(
"flex flex-col h-full flex-1 overflow-y-auto gap-2.5 pt-1 ", "flex flex-col h-full flex-1 overflow-y-auto gap-2.5 pt-1 ",
)}> )}>
{filteredNodes?.map((node) => ( {filteredNodes()?.map((node) => (
<SidebarButton <SidebarButton
key={node.num} key={node.num}
label={node.user?.longName ?? `UNK`} label={node.user?.longName ?? `UNK`}
@ -179,7 +179,7 @@ export const MessagesPage = () => {
{(isBroadcast || isDirect) ? ( {(isBroadcast || isDirect) ? (
<MessageInput <MessageInput
to={isDirect ? activeChat : MessageType.Broadcast} to={isDirect ? activeChat : MessageType.Broadcast}
channel={isBroadcast ? currentChat.id : Types.ChannelNumber.Primary} channel={isDirect ? Types.ChannelNumber.Primary : currentChat.id}
maxBytes={200} maxBytes={200}
/> />
) : ( ) : (

Loading…
Cancel
Save