Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/components/SearchableAwsServices.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
import { getCollection } from 'astro:content';
import { SearchableAwsServices } from './SearchableAwsServices.tsx';

const allServices = await getCollection('docs', ({ id }) => {
return id.startsWith('aws/services/') && !id.includes('/index');
});

const sortedServices = allServices.sort((a, b) => {
const titleA = a.data.title || a.data.linkTitle || '';
const titleB = b.data.title || b.data.linkTitle || '';
return titleA.localeCompare(titleB);
});

const serviceData = sortedServices.map(service => {
const title = service.data.title || service.data.linkTitle || 'Unknown Service';
const description = service.data.description || `Implementation details for ${title} API`;

const href = `/${service.id}`;

return {
title,
description,
href
};
});
---

<SearchableAwsServices services={serviceData} client:load />
75 changes: 75 additions & 0 deletions src/components/SearchableAwsServices.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useState, useMemo } from 'react';
import { ServiceBox } from './ServiceBox.tsx';

interface Service {
title: string;
description: string;
href: string;
}

interface SearchableAwsServicesProps {
services: Service[];
}

export const SearchableAwsServices: React.FC<SearchableAwsServicesProps> = ({ services }) => {
const [searchTerm, setSearchTerm] = useState('');

const filteredServices = useMemo(() => {
if (!searchTerm.trim()) {
return services;
}

const lowercaseSearch = searchTerm.toLowerCase();
return services.filter(service =>
service.title.toLowerCase().includes(lowercaseSearch) ||
service.description.toLowerCase().includes(lowercaseSearch)
);
}, [services, searchTerm]);

return (
<div className="searchable-services">
<div className="search-container">
<div className="search-input-wrapper">
<svg
className="search-icon"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
<input
type="text"
placeholder="Search for Service Name ..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="search-input"
/>
</div>
</div>

{filteredServices.length === 0 && searchTerm.trim() ? (
<div className="no-results">
<p>No services found matching "{searchTerm}"</p>
</div>
) : (
<div className="service-grid">
{filteredServices.map((service, index) => (
<ServiceBox
key={`${service.href}-${index}`}
title={service.title}
description={service.description}
href={service.href}
/>
))}
</div>
)}
</div>
);
};
4 changes: 2 additions & 2 deletions src/content/docs/aws/services/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ sidebar:
order: 3
---

import DynamicAwsServices from '../../../../components/DynamicAwsServices.astro';
import SearchableAwsServices from '../../../../components/SearchableAwsServices.astro';

Browse LocalStack's implemented AWS services and explore their comprehensive feature sets. Each service provides detailed documentation on APIs, configuration options, and practical examples to help you get started quickly.

<DynamicAwsServices />
<SearchableAwsServices />
59 changes: 59 additions & 0 deletions src/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,62 @@
gap: 1rem;
margin-top: 2rem;
}

/* Search styles */
.searchable-services {
margin-top: 2rem;
}

.search-container {
margin-bottom: 2rem;
}

.search-input-wrapper {
position: relative;
max-width: 600px;
margin: 0 auto;
}

.search-icon {
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
width: 1.25rem;
height: 1.25rem;
color: var(--sl-color-gray-3);
pointer-events: none;
z-index: 1;
}

.search-input {
width: 100%;
padding: 1rem 1rem 1rem 3rem;
border: 2px solid var(--sl-color-gray-5);
border-radius: 2rem;
background-color: var(--sl-color-bg-nav);
color: var(--sl-color-white);
font-size: 1rem;
transition: all 0.2s ease;
outline: none;
}

.search-input::placeholder {
color: var(--sl-color-gray-3);
}

.search-input:focus {
border-color: var(--sl-color-accent);
box-shadow: 0 0 0 3px rgba(var(--sl-color-accent-rgb), 0.1);
}

.no-results {
text-align: center;
padding: 2rem;
color: var(--sl-color-gray-2);
}

.no-results p {
margin: 0;
font-size: 1.125rem;
}