├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── .stackbit ├── models │ ├── BackgroundImage.ts │ ├── Button.ts │ ├── CheckboxFormControl.ts │ ├── Config.ts │ ├── ContactBlock.ts │ ├── ContactSection.ts │ ├── CtaSection.ts │ ├── DividerSection.ts │ ├── EmailFormControl.ts │ ├── FeaturedItem.ts │ ├── FeaturedItemsSection.ts │ ├── FeaturedPostsSection.ts │ ├── FeaturedProjectsSection.ts │ ├── Footer.ts │ ├── FormBlock.ts │ ├── Header.ts │ ├── HeroSection.ts │ ├── ImageBlock.ts │ ├── Label.ts │ ├── LabelsSection.ts │ ├── Link.ts │ ├── MediaGallerySection.ts │ ├── MetaTag.ts │ ├── PageLayout.ts │ ├── Person.ts │ ├── PostFeedLayout.ts │ ├── PostFeedSection.ts │ ├── PostLayout.ts │ ├── ProjectFeedLayout.ts │ ├── ProjectFeedSection.ts │ ├── ProjectLayout.ts │ ├── QuoteSection.ts │ ├── RecentPostsSection.ts │ ├── RecentProjectsSection.ts │ ├── SelectFormControl.ts │ ├── Social.ts │ ├── Testimonial.ts │ ├── TestimonialsSection.ts │ ├── TextFormControl.ts │ ├── TextSection.ts │ ├── TextareaFormControl.ts │ ├── ThemeStyle.ts │ ├── VideoBlock.ts │ ├── index.ts │ ├── page-common-fields.ts │ └── section-common-fields.ts └── presets │ ├── contact-section.json │ ├── cta-section.json │ ├── divider-section.json │ ├── featured-items-section.json │ ├── featured-posts-section.json │ ├── featured-projects-section.json │ ├── hero-section.json │ ├── images │ ├── contact-primary-no-media.png │ ├── contact-secondary-image-right.png │ ├── contact-transparent-no-media.png │ ├── cta-primary-btn-bottom.png │ ├── cta-transparent-btn-bottom.png │ ├── cta-transparent-btn-right.png │ ├── divider-full.png │ ├── divider-narrow.png │ ├── divider-wide.png │ ├── feat-items-secondary-2-col.png │ ├── feat-items-transparent-1-col.png │ ├── hero-primary-image-right.png │ ├── hero-secondary-image-left.png │ ├── hero-transparent-text.png │ ├── media-gallery-dark-5-col.png │ ├── media-gallery-primary-2-col.png │ ├── media-gallery-transparent-4-col.png │ ├── page-empty.png │ ├── page-info.png │ ├── page-landing.png │ ├── posts-secondary-list-alt.png │ ├── posts-transparent-2-col.png │ ├── posts-transparent-3-col.png │ ├── posts-transparent-list-alt.png │ ├── posts-transparent-list.png │ ├── projects-primary-list.png │ ├── projects-secondary-3-col.png │ ├── projects-transparent-2-col.png │ ├── quote-secondary-left.png │ ├── quote-transparent-centered.png │ ├── quote-transparent-left.png │ ├── skills-secondary-left.png │ ├── skills-transparent-centered.png │ ├── skills-transparent-left.png │ ├── testimonials-dark-small-images-col.png │ ├── testimonials-secondary-big-images-list.png │ ├── testimonials-white-small-images-list.png │ ├── text-secondary-left.png │ ├── text-transparent-center.png │ └── text-transparent-left.png │ ├── labels-section.json │ ├── media-gallery-section.json │ ├── page-layout.json │ ├── quote-section.json │ ├── recent-posts-section.json │ ├── recent-projects-section.json │ ├── testimonials-section.json │ └── text-section.json ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── README.md ├── content ├── data │ ├── config.json │ ├── style.json │ └── team │ │ └── doris-soto.json └── pages │ ├── blog │ ├── index.md │ ├── post-five.md │ ├── post-four.md │ ├── post-one.md │ ├── post-seven.md │ ├── post-six.md │ ├── post-three.md │ └── post-two.md │ ├── index.md │ ├── info.md │ └── projects │ ├── index.md │ ├── project-one.md │ ├── project-three.md │ └── project-two.md ├── netlify.toml ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── images │ ├── about.jpg │ ├── bg.jpg │ ├── bg1.jpg │ ├── bg2.jpg │ ├── bg3.jpg │ ├── bg4.jpg │ ├── contact.jpg │ ├── favicon.svg │ ├── featured-Image1.jpg │ ├── featured-Image2.jpg │ ├── featured-Image3.jpg │ ├── featured-Image4.jpg │ ├── featured-Image5.jpg │ ├── featured-Image6.jpg │ ├── gallery-1.jpg │ ├── gallery-2.jpg │ ├── gallery-3.jpg │ ├── gallery-4.jpg │ ├── logo1.svg │ ├── logo2.svg │ ├── logo3.svg │ ├── logo4.svg │ ├── logo5.svg │ ├── person-1.jpg │ ├── person-2.jpg │ ├── person-3.jpg │ ├── post-1.jpg │ ├── post-2.jpg │ ├── post-3.jpg │ └── post-4.png ├── personal-nextjs-theme-capture.png ├── personal-nextjs-theme-screenshot-2x.jpg ├── personal-nextjs-theme-screenshot-2x.png ├── personal-nextjs-theme-screenshot.jpg └── personal-nextjs-theme-screenshot.png ├── renovate.json ├── src ├── components │ ├── Annotated.tsx │ ├── atoms │ │ ├── Action │ │ │ └── index.tsx │ │ ├── BackgroundImage │ │ │ └── index.tsx │ │ ├── Link │ │ │ └── index.tsx │ │ ├── Social │ │ │ └── index.tsx │ │ └── index.ts │ ├── components-registry.tsx │ ├── layouts │ │ ├── BaseLayout │ │ │ └── index.tsx │ │ ├── PageLayout │ │ │ └── index.tsx │ │ ├── PostFeedLayout │ │ │ └── index.tsx │ │ ├── PostLayout │ │ │ └── index.tsx │ │ ├── ProjectFeedLayout │ │ │ └── index.tsx │ │ └── ProjectLayout │ │ │ └── index.tsx │ ├── molecules │ │ ├── FormBlock │ │ │ ├── CheckboxFormControl │ │ │ │ └── index.tsx │ │ │ ├── EmailFormControl │ │ │ │ └── index.tsx │ │ │ ├── SelectFormControl │ │ │ │ └── index.tsx │ │ │ ├── TextFormControl │ │ │ │ └── index.tsx │ │ │ ├── TextareaFormControl │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── ImageBlock │ │ │ └── index.tsx │ │ └── VideoBlock │ │ │ └── index.tsx │ ├── sections │ │ ├── ContactSection │ │ │ └── index.tsx │ │ ├── CtaSection │ │ │ └── index.tsx │ │ ├── DividerSection │ │ │ └── index.tsx │ │ ├── FeaturedItemsSection │ │ │ ├── FeaturedItem │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── FeaturedPostsSection │ │ │ └── index.tsx │ │ ├── FeaturedProjectsSection │ │ │ └── index.tsx │ │ ├── Footer │ │ │ └── index.tsx │ │ ├── Header │ │ │ ├── HeaderLink │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── HeroSection │ │ │ └── index.tsx │ │ ├── LabelsSection │ │ │ └── index.tsx │ │ ├── MediaGallerySection │ │ │ └── index.tsx │ │ ├── PostFeedSection │ │ │ └── index.tsx │ │ ├── ProjectFeedSection │ │ │ └── index.tsx │ │ ├── QuoteSection │ │ │ └── index.tsx │ │ ├── RecentPostsSection │ │ │ └── index.tsx │ │ ├── RecentProjectsSection │ │ │ └── index.tsx │ │ ├── Section │ │ │ └── index.tsx │ │ ├── TestimonialsSection │ │ │ └── index.tsx │ │ └── TextSection │ │ │ └── index.tsx │ └── svgs │ │ ├── apple.tsx │ │ ├── arrow-left-circle.tsx │ │ ├── arrow-left.tsx │ │ ├── arrow-right-circle.tsx │ │ ├── arrow-right.tsx │ │ ├── arrow-up-left.tsx │ │ ├── arrow-up-right.tsx │ │ ├── bluesky.tsx │ │ ├── cart.tsx │ │ ├── chevron-left.tsx │ │ ├── chevron-right.tsx │ │ ├── close.tsx │ │ ├── facebook.tsx │ │ ├── github.tsx │ │ ├── google-play.tsx │ │ ├── index.js │ │ ├── instagram.tsx │ │ ├── linkedin.tsx │ │ ├── mail.tsx │ │ ├── menu.tsx │ │ ├── play-circle.tsx │ │ ├── play.tsx │ │ ├── reddit.tsx │ │ ├── send.tsx │ │ ├── twitter.tsx │ │ ├── vimeo.tsx │ │ └── youtube.tsx ├── css │ └── main.css ├── pages │ ├── [[...slug]].tsx │ └── _app.js ├── types │ ├── base.ts │ ├── generated.ts │ └── index.ts └── utils │ ├── common.ts │ ├── content.ts │ ├── data-utils.ts │ ├── get-data-attrs.ts │ ├── get-video-data.ts │ ├── highlighted-markdown.tsx │ ├── map-styles-to-class-names.ts │ ├── seo-utils.js │ ├── static-props-resolvers.ts │ └── theme-style-utils.ts ├── stackbit.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "@next/next/no-img-element": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # stackbit 7 | .stackbit/cache 8 | .cache 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | Thumbs.db 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env 31 | .env.local 32 | .env.development.local 33 | .env.test.local 34 | .env.production.local 35 | 36 | # IDE 37 | *.code-workspace 38 | .idea 39 | 40 | # Local Netlify folder 41 | .netlify 42 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "tabWidth": 4, 6 | "overrides": [ 7 | { 8 | "files": ["*.md", "*.yaml"], 9 | "options": { 10 | "tabWidth": 2 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.stackbit/models/CheckboxFormControl.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const CheckboxFormControlModel: Model = { 4 | type: 'object', 5 | name: 'CheckboxFormControl', 6 | label: 'Checkbox', 7 | labelField: 'label', 8 | fieldGroups: [ 9 | { 10 | name: 'styles', 11 | label: 'Styles', 12 | icon: 'palette' 13 | }, 14 | { 15 | name: 'settings', 16 | label: 'Settings', 17 | icon: 'gear' 18 | } 19 | ], 20 | fields: [ 21 | { 22 | type: 'string', 23 | name: 'name', 24 | label: 'Name', 25 | default: 'updates', 26 | required: true, 27 | description: "Must be unique - this is the property name that will be sent to the server with this field's value." 28 | }, 29 | { 30 | type: 'string', 31 | name: 'label', 32 | label: 'Label', 33 | default: 'Sign me up to receive updates' 34 | }, 35 | { 36 | type: 'enum', 37 | name: 'width', 38 | group: 'styles', 39 | label: 'Width', 40 | options: [ 41 | { 42 | label: 'Full', 43 | value: 'full' 44 | }, 45 | { 46 | label: 'One half', 47 | value: '1/2' 48 | } 49 | ], 50 | default: 'full', 51 | required: true 52 | }, 53 | { 54 | type: 'boolean', 55 | name: 'isRequired', 56 | group: 'settings', 57 | label: 'Is the field required?', 58 | default: false 59 | } 60 | ] 61 | }; 62 | -------------------------------------------------------------------------------- /.stackbit/models/Config.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const ConfigModel: Model = { 4 | type: 'data', 5 | name: 'Config', 6 | label: 'Site configuration', 7 | labelField: 'fixedLabel', 8 | singleInstance: true, 9 | file: 'content/data/config.json', 10 | canDelete: false, 11 | fieldGroups: [ 12 | { 13 | name: 'seo', 14 | label: 'SEO', 15 | icon: 'page' 16 | } 17 | ], 18 | fields: [ 19 | { 20 | type: 'string', 21 | name: 'fixedLabel', 22 | const: 'Site configuration', 23 | hidden: true 24 | }, 25 | { 26 | type: 'image', 27 | name: 'favicon', 28 | label: 'Favicon', 29 | default: 'https://assets.stackbit.com/components/images/default/favicon.svg' 30 | }, 31 | { 32 | type: 'model', 33 | name: 'header', 34 | label: 'Header configuration', 35 | models: ['Header'] 36 | }, 37 | { 38 | type: 'model', 39 | name: 'footer', 40 | label: 'Footer configuration', 41 | models: ['Footer'] 42 | }, 43 | { 44 | type: 'string', 45 | name: 'titleSuffix', 46 | label: 'Suffix for page titles', 47 | description: 48 | 'Suffix to append to the title tag of all pages, except in pages where the this behavior is disabled (e.g. typically the home page should have the site name as a prefix)', 49 | default: null, 50 | group: 'seo' 51 | }, 52 | { 53 | type: 'image', 54 | name: 'defaultSocialImage', 55 | label: 'Default image for social sharing', 56 | description: 57 | 'Default image to use for the og:image meta tag in all pages, except in pages that define another image.', 58 | default: null, 59 | group: 'seo' 60 | }, 61 | { 62 | type: 'list', 63 | name: 'defaultMetaTags', 64 | label: 'Default additional meta tags', 65 | description: 66 | 'Additional meta tags to set as default in all pages. Tags defined here are low-priority: they may be overriden by page-level settings.', 67 | group: 'seo', 68 | items: { 69 | type: 'model', 70 | models: ['MetaTag'] 71 | } 72 | } 73 | ] 74 | }; 75 | -------------------------------------------------------------------------------- /.stackbit/models/ContactBlock.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const ContactBlockModel: Model = { 4 | type: 'object', 5 | name: 'ContactBlock', 6 | label: 'Contact', 7 | labelField: 'title', 8 | fieldGroups: [ 9 | { 10 | name: 'settings', 11 | label: 'Settings', 12 | icon: 'gear' 13 | } 14 | ], 15 | fields: [ 16 | { 17 | type: 'string', 18 | name: 'title', 19 | description: 'The value of the field is used for presentation purposes in Stackbit', 20 | default: 'Contact details' 21 | }, 22 | { 23 | type: 'string', 24 | name: 'phoneNumber', 25 | label: 'Phone number', 26 | default: '850-123-5021' 27 | }, 28 | { 29 | type: 'string', 30 | name: 'phoneAltText', 31 | label: 'Phone alt text', 32 | default: 'Phone' 33 | }, 34 | { 35 | type: 'string', 36 | name: 'email', 37 | label: 'Email address', 38 | default: 'john@doe.com' 39 | }, 40 | { 41 | type: 'string', 42 | name: 'emailAltText', 43 | label: 'Email address alt text', 44 | default: 'Email' 45 | }, 46 | { 47 | type: 'string', 48 | name: 'address', 49 | label: 'Address', 50 | default: '312 Lovely Street, NY' 51 | }, 52 | { 53 | type: 'string', 54 | name: 'addressAltText', 55 | label: 'Address alt text', 56 | default: 'Address' 57 | }, 58 | { 59 | type: 'string', 60 | name: 'elementId', 61 | group: 'settings', 62 | label: 'Element ID', 63 | description: 'The unique ID for an HTML element, must not contain whitespace', 64 | default: '' 65 | } 66 | ] 67 | }; 68 | -------------------------------------------------------------------------------- /.stackbit/models/DividerSection.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | import { settingFields, settingFieldsGroup } from './section-common-fields'; 4 | 5 | export const DividerSectionModel: Model = { 6 | type: 'object', 7 | name: 'DividerSection', 8 | label: 'Divider', 9 | labelField: 'title', 10 | thumbnail: 'https://assets.stackbit.com/components/models/thumbnails/default.png', 11 | groups: ['SectionModels'], 12 | fieldGroups: [...settingFieldsGroup], 13 | fields: [ 14 | { 15 | type: 'string', 16 | name: 'title', 17 | description: 'The value of the field is used for presentation purposes in Stackbit', 18 | default: 'Divider' 19 | }, 20 | ...settingFields, 21 | { 22 | type: 'style', 23 | name: 'styles', 24 | styles: { 25 | self: { 26 | width: ['narrow', 'wide', 'full'], 27 | padding: ['tw0:96'], 28 | borderWidth: ['1:8'], 29 | borderStyle: '*' 30 | } 31 | }, 32 | default: { 33 | self: { 34 | width: 'wide', 35 | padding: ['pt-12', 'pb-12', 'pl-4', 'pr-4'], 36 | borderWidth: 1, 37 | borderStyle: 'solid' 38 | } 39 | } 40 | } 41 | ] 42 | }; 43 | -------------------------------------------------------------------------------- /.stackbit/models/EmailFormControl.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const EmailFormControlModel: Model = { 4 | type: 'object', 5 | name: 'EmailFormControl', 6 | label: 'Email', 7 | labelField: 'label', 8 | fieldGroups: [ 9 | { 10 | name: 'styles', 11 | label: 'Styles', 12 | icon: 'palette' 13 | }, 14 | { 15 | name: 'settings', 16 | label: 'Settings', 17 | icon: 'gear' 18 | } 19 | ], 20 | fields: [ 21 | { 22 | type: 'string', 23 | name: 'name', 24 | label: 'Name', 25 | default: 'email-address', 26 | description: "Must be unique - this is the property name that will be sent to the server with this field's value." 27 | }, 28 | { 29 | type: 'string', 30 | name: 'label', 31 | label: 'Label', 32 | default: 'Name' 33 | }, 34 | { 35 | type: 'boolean', 36 | name: 'hideLabel', 37 | label: 'Hide label', 38 | default: false 39 | }, 40 | { 41 | type: 'string', 42 | name: 'placeholder', 43 | label: 'Placeholder text', 44 | default: 'Your name' 45 | }, 46 | 47 | { 48 | type: 'enum', 49 | name: 'width', 50 | group: 'styles', 51 | label: 'Width', 52 | options: [ 53 | { 54 | label: 'Full', 55 | value: 'full' 56 | }, 57 | { 58 | label: 'One half', 59 | value: '1/2' 60 | } 61 | ], 62 | default: 'full', 63 | required: true 64 | }, 65 | { 66 | type: 'boolean', 67 | name: 'isRequired', 68 | group: 'settings', 69 | label: 'Is the field required?', 70 | default: false 71 | } 72 | ] 73 | }; 74 | -------------------------------------------------------------------------------- /.stackbit/models/Footer.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const FooterModel: Model = { 4 | type: 'object', 5 | name: 'Footer', 6 | label: 'Footer', 7 | labelField: 'copyrightText', 8 | fields: [ 9 | { 10 | type: 'list', 11 | name: 'primaryLinks', 12 | label: 'Primary navigation links', 13 | items: { 14 | type: 'model', 15 | models: ['Button', 'Link'] 16 | }, 17 | default: [ 18 | { 19 | type: 'Link', 20 | label: 'Projects', 21 | url: '/', 22 | altText: 'Projects' 23 | }, 24 | { 25 | type: 'Link', 26 | label: 'Info', 27 | url: '/', 28 | altText: 'Info' 29 | } 30 | ] 31 | }, 32 | { 33 | type: 'model', 34 | name: 'contacts', 35 | label: 'Contacts', 36 | models: ['ContactBlock'], 37 | default: { 38 | phoneNumber: '850-123-5021', 39 | phoneAltText: 'Call us', 40 | email: 'john@doe.com', 41 | emailAltText: 'Email us' 42 | } 43 | }, 44 | { 45 | type: 'markdown', 46 | name: 'copyrightText', 47 | label: 'Copyright text', 48 | default: 'Copyright text' 49 | }, 50 | { 51 | type: 'style', 52 | name: 'styles', 53 | styles: { 54 | self: { 55 | width: ['narrow', 'wide', 'full'], 56 | padding: ['tw0:36'] 57 | } 58 | }, 59 | default: { 60 | self: { 61 | width: 'narrow', 62 | padding: ['pt-16', 'pb-16', 'pl-4', 'pr-4'] 63 | } 64 | } 65 | } 66 | ] 67 | }; 68 | -------------------------------------------------------------------------------- /.stackbit/models/FormBlock.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const FormBlockModel: Model = { 4 | type: 'object', 5 | name: 'FormBlock', 6 | label: 'Form', 7 | labelField: 'title', 8 | fieldGroups: [ 9 | { 10 | name: 'settings', 11 | label: 'Settings', 12 | icon: 'gear' 13 | } 14 | ], 15 | fields: [ 16 | { 17 | type: 'string', 18 | name: 'title', 19 | description: 'The value of the field is used for presentation purposes in Stackbit', 20 | default: 'Title of the form' 21 | }, 22 | { 23 | type: 'list', 24 | name: 'fields', 25 | label: 'Fields', 26 | items: { 27 | type: 'model', 28 | models: [ 29 | 'TextFormControl', 30 | 'EmailFormControl', 31 | 'TextareaFormControl', 32 | 'CheckboxFormControl', 33 | 'SelectFormControl' 34 | ] 35 | }, 36 | default: [ 37 | { 38 | type: 'TextFormControl', 39 | label: 'Name', 40 | name: 'name', 41 | placeholder: 'Your name', 42 | isRequired: true, 43 | width: '1/2' 44 | }, 45 | { 46 | type: 'EmailFormControl', 47 | label: 'Email', 48 | name: 'email', 49 | placeholder: 'Your email', 50 | isRequired: true, 51 | width: '1/2' 52 | }, 53 | { 54 | type: 'TextareaFormControl', 55 | label: 'Message', 56 | name: 'message', 57 | placeholder: 'Type your message here', 58 | isRequired: false, 59 | width: 'full' 60 | }, 61 | { 62 | type: 'CheckboxFormControl', 63 | name: 'updates', 64 | label: 'Sign me up to receive updates', 65 | isRequired: false, 66 | width: 'full' 67 | } 68 | ] 69 | }, 70 | { 71 | type: 'string', 72 | name: 'submitLabel', 73 | label: 'Button', 74 | default: 'Send Message' 75 | }, 76 | { 77 | type: 'string', 78 | name: 'elementId', 79 | group: 'settings', 80 | label: 'Element ID', 81 | description: 'The unique ID used for id and name attributes, must not contain whitespace', 82 | default: 'contact-form', 83 | required: true 84 | }, 85 | { 86 | type: 'style', 87 | name: 'styles', 88 | styles: { 89 | self: { 90 | textAlign: ['left', 'center', 'right'] 91 | } 92 | }, 93 | default: { 94 | self: { 95 | textAlign: 'left' 96 | } 97 | } 98 | } 99 | ] 100 | }; 101 | -------------------------------------------------------------------------------- /.stackbit/models/ImageBlock.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const ImageBlockModel: Model = { 4 | type: 'object', 5 | name: 'ImageBlock', 6 | label: 'Image', 7 | labelField: 'altText', 8 | fieldGroups: [ 9 | { 10 | name: 'settings', 11 | label: 'Settings', 12 | icon: 'gear' 13 | } 14 | ], 15 | fields: [ 16 | { 17 | type: 'image', 18 | name: 'url', 19 | label: 'Image', 20 | description: 'The URL of the image', 21 | default: 'https://assets.stackbit.com/components/images/default/default-image.png' 22 | }, 23 | { 24 | type: 'string', 25 | name: 'altText', 26 | label: 'Alt text', 27 | description: 'The alt text of the image', 28 | default: 'altText of the image' 29 | }, 30 | { 31 | type: 'string', 32 | name: 'caption', 33 | label: 'Caption', 34 | description: 'The caption of the image', 35 | default: 'Caption of the image' 36 | }, 37 | { 38 | type: 'string', 39 | name: 'elementId', 40 | group: 'settings', 41 | label: 'Element ID', 42 | description: 'The unique ID for an HTML element, must not contain whitespace', 43 | default: '' 44 | } 45 | ] 46 | }; 47 | -------------------------------------------------------------------------------- /.stackbit/models/Label.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const LabelModel: Model = { 4 | type: 'object', 5 | name: 'Label', 6 | label: 'Label', 7 | labelField: 'label', 8 | thumbnail: 'https://assets.stackbit.com/components/models/thumbnails/default.png', 9 | fields: [ 10 | { 11 | type: 'string', 12 | name: 'label', 13 | label: 'Label', 14 | default: 'Label title', 15 | required: true 16 | }, 17 | { 18 | type: 'string', 19 | name: 'url', 20 | label: 'URL', 21 | default: '' 22 | } 23 | ] 24 | }; 25 | -------------------------------------------------------------------------------- /.stackbit/models/LabelsSection.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | import { colorFields, settingFields, settingFieldsGroup, styleFieldsGroup } from './section-common-fields'; 3 | 4 | export const LabelsSectionModel: Model = { 5 | type: 'object', 6 | name: 'LabelsSection', 7 | label: 'Labels', 8 | labelField: 'title', 9 | thumbnail: 'https://assets.stackbit.com/components/models/thumbnails/default.png', 10 | groups: ['SectionModels'], 11 | fieldGroups: [...styleFieldsGroup, ...settingFieldsGroup], 12 | fields: [ 13 | { 14 | type: 'string', 15 | name: 'title', 16 | label: 'Title', 17 | default: 'Skills' 18 | }, 19 | { 20 | type: 'string', 21 | name: 'subtitle', 22 | label: 'Subtitle', 23 | default: 'The section subtitle' 24 | }, 25 | { 26 | type: 'list', 27 | name: 'items', 28 | label: 'Items', 29 | items: { 30 | type: 'model', 31 | models: ['Label'] 32 | }, 33 | default: [ 34 | { 35 | type: 'Label', 36 | label: 'Label title' 37 | } 38 | ] 39 | }, 40 | ...colorFields, 41 | ...settingFields, 42 | { 43 | type: 'style', 44 | name: 'styles', 45 | styles: { 46 | self: { 47 | height: ['auto', 'screen'], 48 | width: ['narrow', 'wide', 'full'], 49 | margin: ['tw0:96'], 50 | padding: ['tw0:96'], 51 | borderRadius: '*', 52 | borderWidth: ['0:8'], 53 | borderStyle: '*', 54 | borderColor: [ 55 | { 56 | value: 'border-(--theme-light)', 57 | label: 'Light color', 58 | color: '$light' 59 | }, 60 | { 61 | value: 'border-(--theme-dark)', 62 | label: 'Dark color', 63 | color: '$dark' 64 | }, 65 | { 66 | value: 'border-(--theme-primary)', 67 | label: 'Primary color', 68 | color: '$primary' 69 | }, 70 | { 71 | value: 'border-(--theme-secondary)', 72 | label: 'Secondary color', 73 | color: '$secondary' 74 | }, 75 | { 76 | value: 'border-(--theme-complementary)', 77 | label: 'Complementary color', 78 | color: '$complementary' 79 | } 80 | ], 81 | textAlign: ['left', 'center', 'right'] 82 | } 83 | }, 84 | default: { 85 | self: { 86 | height: 'auto', 87 | width: 'wide', 88 | margin: ['mt-0', 'mb-0', 'ml-0', 'mr-0'], 89 | padding: ['pt-12', 'pb-12', 'pl-4', 'pr-4'], 90 | borderRadius: 'none', 91 | borderWidth: 0, 92 | borderStyle: 'none', 93 | borderColor: 'border-(--theme-dark)', 94 | textAlign: 'center' 95 | } 96 | } 97 | } 98 | ] 99 | }; 100 | -------------------------------------------------------------------------------- /.stackbit/models/MetaTag.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const MetaTagModel: Model = { 4 | type: 'object', 5 | name: 'MetaTag', 6 | label: 'MetaTag', 7 | labelField: 'property', 8 | fields: [ 9 | { 10 | type: 'enum', 11 | name: 'property', 12 | label: 'Property', 13 | default: 'og:title', 14 | options: [ 15 | { 16 | label: 'og:title', 17 | value: 'og:title' 18 | }, 19 | { 20 | label: 'og:type', 21 | value: 'og:type' 22 | }, 23 | { 24 | label: 'og:image', 25 | value: 'og:image' 26 | }, 27 | { 28 | label: 'og:image:alt', 29 | value: 'og:image:alt' 30 | }, 31 | { 32 | label: 'og:url', 33 | value: 'og:url' 34 | }, 35 | { 36 | label: 'og:description', 37 | value: 'og:description' 38 | }, 39 | { 40 | label: 'og:locale', 41 | value: 'og:locale' 42 | }, 43 | { 44 | label: 'og:site_name', 45 | value: 'og:site_name' 46 | }, 47 | { 48 | label: 'og:video', 49 | value: 'og:video' 50 | }, 51 | { 52 | label: 'twitter:card', 53 | value: 'twitter:card' 54 | }, 55 | { 56 | label: 'twitter:site', 57 | value: 'twitter:site' 58 | }, 59 | { 60 | label: 'twitter:creator', 61 | value: 'twitter:creator' 62 | }, 63 | { 64 | label: 'twitter:description', 65 | value: 'twitter:description' 66 | }, 67 | { 68 | label: 'twitter:title', 69 | value: 'twitter:title' 70 | }, 71 | { 72 | label: 'twitter:image', 73 | value: 'twitter:image' 74 | }, 75 | { 76 | label: 'twitter:image:alt', 77 | value: 'twitter:image:alt' 78 | }, 79 | { 80 | label: 'twitter:player', 81 | value: 'twitter:player' 82 | } 83 | ] 84 | }, 85 | { 86 | type: 'string', 87 | name: 'content', 88 | label: 'Content', 89 | default: '' 90 | } 91 | ] 92 | }; 93 | -------------------------------------------------------------------------------- /.stackbit/models/PageLayout.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | import { seoFields, seoFieldsGroup, styleFields, styleFieldsGroup } from './page-common-fields'; 3 | 4 | export const PageLayoutModel: Model = { 5 | type: 'page', 6 | name: 'PageLayout', 7 | label: 'Page', 8 | hideContent: true, 9 | urlPath: '/{slug}', 10 | filePath: 'content/pages/{slug}.md', 11 | thumbnail: 'https://assets.stackbit.com/components/models/thumbnails/default.png', 12 | fieldGroups: [...seoFieldsGroup, ...styleFieldsGroup], 13 | fields: [ 14 | { 15 | type: 'string', 16 | name: 'title', 17 | label: 'Title', 18 | default: 'This is a new page', 19 | required: true 20 | }, 21 | { 22 | type: 'list', 23 | name: 'sections', 24 | label: 'Sections', 25 | items: { 26 | type: 'model', 27 | models: [], 28 | groups: ['SectionModels'] 29 | }, 30 | default: [ 31 | { 32 | type: 'HeroSection', 33 | elementId: 'homepage-hero-1', 34 | title: 'This Is A Big Hero Headline', 35 | text: 'Aenean eros ipsum, interdum quis dignissim non, sollicitudin vitae nisl. Aenean vel aliquet elit, at blandit ipsum. Sed eleifend felis sit amet erat molestie, hendrerit malesuada justo ultrices. Nunc volutpat at erat itae interdum. Ut nec massa eget lorem blandit condimentum et at risus.', 36 | actions: [ 37 | { 38 | type: 'Button', 39 | label: 'Get Started', 40 | url: '/', 41 | style: 'primary', 42 | elementId: 'hero-main-button' 43 | }, 44 | { 45 | type: 'Button', 46 | label: 'Learn More', 47 | url: '/', 48 | style: 'secondary' 49 | } 50 | ], 51 | media: { 52 | type: 'ImageBlock', 53 | url: '/images/hero.webp', 54 | altText: 'Image alt text' 55 | }, 56 | styles: { 57 | self: { 58 | height: 'auto', 59 | width: 'wide', 60 | margin: ['mt-0', 'mb-0', 'ml-0', 'mr-0'], 61 | padding: ['pt-12', 'pb-12', 'pl-4', 'pr-4'], 62 | flexDirection: 'row', 63 | textAlign: 'left' 64 | } 65 | } 66 | } 67 | ] 68 | }, 69 | ...seoFields, 70 | ...styleFields 71 | ] 72 | }; 73 | -------------------------------------------------------------------------------- /.stackbit/models/Person.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const PersonModel: Model = { 4 | type: 'data', 5 | name: 'Person', 6 | label: 'Person', 7 | labelField: 'firstName', 8 | filePath: 'content/data/team/{slug}.json', 9 | fields: [ 10 | { 11 | type: 'string', 12 | name: 'firstName', 13 | label: 'First name', 14 | default: 'Name', 15 | required: true 16 | }, 17 | { 18 | type: 'string', 19 | name: 'lastName', 20 | label: 'Last name', 21 | default: 'Surname' 22 | }, 23 | { 24 | type: 'string', 25 | name: 'role', 26 | label: 'Role', 27 | default: 'Role' 28 | }, 29 | { 30 | type: 'markdown', 31 | name: 'bio', 32 | label: 'Bio', 33 | default: 34 | 'With over 10 years in both public and private sectors, Johnna has experience in management consultation, team building, professional development, strategic implementation, and company collaboration.' 35 | }, 36 | { 37 | type: 'model', 38 | name: 'image', 39 | label: 'Image', 40 | models: ['ImageBlock'], 41 | default: { 42 | type: 'ImageBlock', 43 | url: 'https://assets.stackbit.com/components/images/default/default-person.png', 44 | altText: 'Person photo' 45 | } 46 | } 47 | ] 48 | }; 49 | -------------------------------------------------------------------------------- /.stackbit/models/PostFeedLayout.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | import { seoFields, seoFieldsGroup, styleFields, styleFieldsGroup } from './page-common-fields'; 3 | 4 | export const PostFeedLayoutModel: Model = { 5 | type: 'page', 6 | name: 'PostFeedLayout', 7 | label: 'Blog', 8 | labelField: 'title', 9 | hideContent: true, 10 | singleInstance: true, 11 | urlPath: '/blog', 12 | file: 'content/pages/blog/index.md', 13 | fieldGroups: [...seoFieldsGroup, ...styleFieldsGroup], 14 | fields: [ 15 | { 16 | type: 'string', 17 | name: 'title', 18 | label: 'Title', 19 | default: 'This is a page title' 20 | }, 21 | { 22 | type: 'model', 23 | name: 'postFeed', 24 | readOnly: true, 25 | label: 'Post Feed', 26 | models: ['PostFeedSection'], 27 | default: { 28 | title: null, 29 | subtitle: null, 30 | showDate: true, 31 | showAuthor: true, 32 | variant: 'variant-a' 33 | } 34 | }, 35 | { 36 | type: 'list', 37 | name: 'topSections', 38 | label: 'Top Sections', 39 | items: { 40 | type: 'model', 41 | models: [], 42 | groups: ['SectionModels'] 43 | } 44 | }, 45 | { 46 | type: 'list', 47 | name: 'bottomSections', 48 | label: 'Bottom Sections', 49 | items: { 50 | type: 'model', 51 | models: [], 52 | groups: ['SectionModels'] 53 | } 54 | }, 55 | ...seoFields, 56 | ...styleFields 57 | ] 58 | }; 59 | -------------------------------------------------------------------------------- /.stackbit/models/PostLayout.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | import { seoFields, seoFieldsGroup, styleFields, styleFieldsGroup } from './page-common-fields'; 3 | 4 | export const PostLayoutModel: Model = { 5 | type: 'page', 6 | name: 'PostLayout', 7 | label: 'Post', 8 | urlPath: '/blog/{slug}', 9 | filePath: 'content/pages/blog/{slug}.md', 10 | thumbnail: 'https://assets.stackbit.com/components/models/thumbnails/default.png', 11 | fieldGroups: [...seoFieldsGroup, ...styleFieldsGroup], 12 | fields: [ 13 | { 14 | type: 'string', 15 | name: 'title', 16 | label: 'Title', 17 | default: 'This is a blog post title', 18 | required: true 19 | }, 20 | { 21 | type: 'date', 22 | name: 'date', 23 | label: 'Date', 24 | required: true 25 | }, 26 | { 27 | type: 'reference', 28 | name: 'author', 29 | label: 'Author', 30 | models: ['Person'] 31 | }, 32 | { 33 | type: 'string', 34 | name: 'excerpt', 35 | label: 'Excerpt', 36 | default: 37 | 'Nunc rutrum felis dui, ut consequat sapien scelerisque vel. Integer condimentum dignissim justo vel faucibus.' 38 | }, 39 | { 40 | type: 'model', 41 | name: 'featuredImage', 42 | label: 'Featured image', 43 | models: ['ImageBlock'], 44 | default: { 45 | type: 'ImageBlock', 46 | url: 'https://assets.stackbit.com/components/images/default/post-4.jpeg', 47 | altText: 'Post thumbnail image' 48 | } 49 | }, 50 | { 51 | type: 'model', 52 | name: 'media', 53 | label: 'Media', 54 | models: ['ImageBlock', 'VideoBlock'], 55 | default: { 56 | type: 'ImageBlock', 57 | url: 'https://assets.stackbit.com/components/images/default/post-4.jpeg', 58 | altText: 'Post image' 59 | } 60 | }, 61 | { 62 | type: 'list', 63 | name: 'bottomSections', 64 | label: 'Sections', 65 | items: { 66 | type: 'model', 67 | models: [], 68 | groups: ['SectionModels'] 69 | } 70 | }, 71 | ...seoFields, 72 | ...styleFields 73 | ] 74 | }; 75 | -------------------------------------------------------------------------------- /.stackbit/models/ProjectFeedLayout.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | import { seoFields, seoFieldsGroup, styleFields, styleFieldsGroup } from './page-common-fields'; 3 | 4 | export const ProjectFeedLayoutModel: Model = { 5 | type: 'page', 6 | name: 'ProjectFeedLayout', 7 | label: 'Projects', 8 | hideContent: true, 9 | singleInstance: true, 10 | urlPath: '/projects', 11 | file: 'content/pages/projects/index.md', 12 | fieldGroups: [...seoFieldsGroup, ...styleFieldsGroup], 13 | fields: [ 14 | { 15 | type: 'string', 16 | name: 'title', 17 | label: 'Title', 18 | default: 'This is a page title' 19 | }, 20 | { 21 | type: 'model', 22 | name: 'projectFeed', 23 | readOnly: true, 24 | label: 'Project feed', 25 | models: ['ProjectFeedSection'], 26 | default: { 27 | title: null, 28 | subtitle: null, 29 | showDate: true, 30 | showDescription: true, 31 | variant: 'variant-a' 32 | } 33 | }, 34 | { 35 | type: 'list', 36 | name: 'topSections', 37 | label: 'Top Sections', 38 | items: { 39 | type: 'model', 40 | models: [], 41 | groups: ['SectionModels'] 42 | } 43 | }, 44 | { 45 | type: 'list', 46 | name: 'bottomSections', 47 | label: 'Bottom Sections', 48 | items: { 49 | type: 'model', 50 | models: [], 51 | groups: ['SectionModels'] 52 | } 53 | }, 54 | ...seoFields, 55 | ...styleFields 56 | ] 57 | }; 58 | -------------------------------------------------------------------------------- /.stackbit/models/ProjectLayout.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | import { seoFields, seoFieldsGroup, styleFields, styleFieldsGroup } from './page-common-fields'; 3 | 4 | export const ProjectLayoutModel: Model = { 5 | type: 'page', 6 | name: 'ProjectLayout', 7 | label: 'Project page', 8 | urlPath: '/projects/{slug}', 9 | filePath: 'content/pages/projects/{slug}.md', 10 | thumbnail: 'https://assets.stackbit.com/components/models/thumbnails/default.png', 11 | fieldGroups: [...seoFieldsGroup, ...styleFieldsGroup], 12 | fields: [ 13 | { 14 | type: 'string', 15 | name: 'title', 16 | label: 'Title', 17 | default: 'This is a project title', 18 | required: true 19 | }, 20 | { 21 | type: 'date', 22 | name: 'date', 23 | label: 'Date', 24 | required: true 25 | }, 26 | { 27 | type: 'string', 28 | name: 'client', 29 | label: 'Client', 30 | default: 'Awesome client' 31 | }, 32 | { 33 | type: 'string', 34 | name: 'description', 35 | label: 'Description', 36 | default: 37 | 'Nunc rutrum felis dui, ut consequat sapien scelerisque vel. Integer condimentum dignissim justo vel faucibus.' 38 | }, 39 | { 40 | type: 'model', 41 | name: 'featuredImage', 42 | label: 'Featured image', 43 | models: ['ImageBlock'], 44 | default: { 45 | type: 'ImageBlock', 46 | url: 'https://assets.stackbit.com/components/images/default/post-4.jpeg', 47 | altText: 'Project thumbnail image', 48 | caption: '' 49 | } 50 | }, 51 | { 52 | type: 'model', 53 | name: 'media', 54 | label: 'Media', 55 | models: ['ImageBlock', 'VideoBlock'], 56 | default: { 57 | type: 'ImageBlock', 58 | url: 'https://assets.stackbit.com/components/images/default/post-4.jpeg', 59 | altText: 'Project image' 60 | } 61 | }, 62 | { 63 | type: 'list', 64 | name: 'bottomSections', 65 | label: 'Sections', 66 | items: { 67 | type: 'model', 68 | models: [], 69 | groups: ['SectionModels'] 70 | } 71 | }, 72 | ...seoFields, 73 | ...styleFields 74 | ] 75 | }; 76 | -------------------------------------------------------------------------------- /.stackbit/models/QuoteSection.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | import { colorFields, settingFields, settingFieldsGroup, styleFieldsGroup } from './section-common-fields'; 3 | 4 | export const QuoteSectionModel: Model = { 5 | type: 'object', 6 | name: 'QuoteSection', 7 | label: 'Quote', 8 | labelField: 'name', 9 | thumbnail: 'https://assets.stackbit.com/components/models/thumbnails/default.png', 10 | groups: ['SectionModels'], 11 | fieldGroups: [...styleFieldsGroup, ...settingFieldsGroup], 12 | fields: [ 13 | { 14 | type: 'string', 15 | name: 'quote', 16 | label: 'Quote', 17 | default: 18 | "“We think coding should be required in every school because it's as important as any kind of second language.”", 19 | required: true 20 | }, 21 | { 22 | type: 'string', 23 | name: 'name', 24 | label: 'Author name', 25 | default: 'Johnna Doe' 26 | }, 27 | { 28 | type: 'string', 29 | name: 'title', 30 | label: 'Author title', 31 | default: 'Product Marketing Manager at Acme' 32 | }, 33 | ...colorFields, 34 | ...settingFields, 35 | { 36 | type: 'style', 37 | name: 'styles', 38 | styles: { 39 | self: { 40 | height: ['auto', 'screen'], 41 | width: ['narrow', 'wide', 'full'], 42 | margin: ['tw0:96'], 43 | padding: ['tw0:96'], 44 | borderRadius: '*', 45 | borderWidth: ['0:8'], 46 | borderStyle: '*', 47 | borderColor: [ 48 | { 49 | value: 'border-(--theme-light)', 50 | label: 'Light color', 51 | color: '$light' 52 | }, 53 | { 54 | value: 'border-(--theme-dark)', 55 | label: 'Dark color', 56 | color: '$dark' 57 | }, 58 | { 59 | value: 'border-(--theme-primary)', 60 | label: 'Primary color', 61 | color: '$primary' 62 | }, 63 | { 64 | value: 'border-(--theme-secondary)', 65 | label: 'Secondary color', 66 | color: '$secondary' 67 | }, 68 | { 69 | value: 'border-(--theme-complementary)', 70 | label: 'Complementary color', 71 | color: '$complementary' 72 | } 73 | ], 74 | textAlign: ['left', 'center', 'right'] 75 | } 76 | }, 77 | default: { 78 | self: { 79 | height: 'auto', 80 | width: 'wide', 81 | margin: ['mt-0', 'mb-0', 'ml-0', 'mr-0'], 82 | padding: ['pt-12', 'pb-12', 'pl-4', 'pr-4'], 83 | borderRadius: 'none', 84 | borderWidth: 0, 85 | borderStyle: 'none', 86 | borderColor: 'border-(--theme-dark)', 87 | textAlign: 'center' 88 | } 89 | } 90 | } 91 | ] 92 | }; 93 | -------------------------------------------------------------------------------- /.stackbit/models/SelectFormControl.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const SelectFormControlModel: Model = { 4 | type: 'object', 5 | name: 'SelectFormControl', 6 | label: 'Select', 7 | labelField: 'label', 8 | fieldGroups: [ 9 | { 10 | name: 'styles', 11 | label: 'Styles', 12 | icon: 'palette' 13 | }, 14 | { 15 | name: 'settings', 16 | label: 'Settings', 17 | icon: 'gear' 18 | } 19 | ], 20 | fields: [ 21 | { 22 | type: 'string', 23 | name: 'name', 24 | label: 'Name', 25 | required: true, 26 | default: 'subject', 27 | description: "Must be unique - this is the property name that will be sent to the server with this field's value." 28 | }, 29 | { 30 | type: 'string', 31 | name: 'label', 32 | label: 'Label', 33 | default: 'Subject' 34 | }, 35 | { 36 | type: 'boolean', 37 | name: 'hideLabel', 38 | label: 'Hide label', 39 | default: false 40 | }, 41 | { 42 | type: 'string', 43 | name: 'defaultValue', 44 | label: 'Default value', 45 | default: 'Please choose...' 46 | }, 47 | { 48 | type: 'list', 49 | name: 'options', 50 | label: 'Options', 51 | items: { 52 | type: 'string' 53 | }, 54 | default: ['Logo design', 'Other'] 55 | }, 56 | { 57 | type: 'enum', 58 | name: 'width', 59 | group: 'styles', 60 | label: 'Width', 61 | options: [ 62 | { 63 | label: 'Full', 64 | value: 'full' 65 | }, 66 | { 67 | label: 'One half', 68 | value: '1/2' 69 | } 70 | ], 71 | default: 'full', 72 | required: true 73 | }, 74 | { 75 | type: 'boolean', 76 | name: 'isRequired', 77 | group: 'settings', 78 | label: 'Is the field required?', 79 | default: false 80 | } 81 | ] 82 | }; 83 | -------------------------------------------------------------------------------- /.stackbit/models/Social.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const SocialModel: Model = { 4 | type: 'object', 5 | name: 'Social', 6 | label: 'Social', 7 | labelField: 'label', 8 | fieldGroups: [ 9 | { 10 | name: 'styles', 11 | label: 'Styles', 12 | icon: 'palette' 13 | }, 14 | { 15 | name: 'settings', 16 | label: 'Settings', 17 | icon: 'gear' 18 | } 19 | ], 20 | fields: [ 21 | { 22 | type: 'string', 23 | name: 'label', 24 | label: 'Label', 25 | default: 'Facebook' 26 | }, 27 | { 28 | type: 'string', 29 | name: 'altText', 30 | label: 'Alt text', 31 | default: '', 32 | description: 'The alternative text for screen readers' 33 | }, 34 | { 35 | type: 'string', 36 | name: 'url', 37 | label: 'URL', 38 | default: '/', 39 | required: true 40 | }, 41 | { 42 | type: 'enum', 43 | name: 'icon', 44 | group: 'styles', 45 | label: 'Icon', 46 | options: [ 47 | { 48 | label: 'Bluesky', 49 | value: 'bluesky' 50 | }, 51 | { 52 | label: 'Facebook', 53 | value: 'facebook' 54 | }, 55 | { 56 | label: 'GitHub', 57 | value: 'github' 58 | }, 59 | { 60 | label: 'Instagram', 61 | value: 'instagram' 62 | }, 63 | { 64 | label: 'LinkedIn', 65 | value: 'linkedin' 66 | }, 67 | { 68 | label: 'Reddit', 69 | value: 'reddit' 70 | }, 71 | { 72 | label: 'X (Twitter)', 73 | value: 'twitter' 74 | }, 75 | { 76 | label: 'Vimeo', 77 | value: 'vimeo' 78 | }, 79 | { 80 | label: 'YouTube', 81 | value: 'youtube' 82 | } 83 | ], 84 | default: 'facebook', 85 | required: true 86 | }, 87 | { 88 | type: 'string', 89 | name: 'elementId', 90 | group: 'settings', 91 | label: 'Element ID', 92 | description: 'The unique ID for an HTML element, must not contain whitespace', 93 | default: '' 94 | } 95 | ] 96 | }; 97 | -------------------------------------------------------------------------------- /.stackbit/models/Testimonial.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const TestimonialModel: Model = { 4 | type: 'object', 5 | name: 'Testimonial', 6 | label: 'Testimonial', 7 | labelField: 'name', 8 | fieldGroups: [ 9 | { 10 | name: 'settings', 11 | label: 'Settings', 12 | icon: 'gear' 13 | } 14 | ], 15 | fields: [ 16 | { 17 | type: 'string', 18 | name: 'quote', 19 | label: 'Quote', 20 | default: 21 | '“It’s great to see someone taking action while still maintaining a sustainable fish supply to home cooks.”', 22 | required: true 23 | }, 24 | { 25 | type: 'string', 26 | name: 'name', 27 | label: 'Author name', 28 | default: 'Johnna Doe' 29 | }, 30 | { 31 | type: 'string', 32 | name: 'title', 33 | label: 'Author title', 34 | default: 'Product Marketing Manager at Acme' 35 | }, 36 | { 37 | type: 'model', 38 | name: 'image', 39 | label: 'Author image', 40 | models: ['ImageBlock'], 41 | default: { 42 | url: 'https://assets.stackbit.com/components/images/default/default-person.png', 43 | altText: 'Person photo' 44 | } 45 | }, 46 | { 47 | type: 'string', 48 | name: 'elementId', 49 | group: 'settings', 50 | label: 'Element ID', 51 | description: 'The unique ID for an HTML element, must not contain whitespace', 52 | default: '' 53 | } 54 | ] 55 | }; 56 | -------------------------------------------------------------------------------- /.stackbit/models/TextFormControl.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const TextFormControlModel: Model = { 4 | type: 'object', 5 | name: 'TextFormControl', 6 | label: 'Text', 7 | labelField: 'label', 8 | fieldGroups: [ 9 | { 10 | name: 'styles', 11 | label: 'Styles', 12 | icon: 'palette' 13 | }, 14 | { 15 | name: 'settings', 16 | label: 'Settings', 17 | icon: 'gear' 18 | } 19 | ], 20 | fields: [ 21 | { 22 | type: 'string', 23 | name: 'name', 24 | label: 'Name', 25 | required: true, 26 | default: 'name', 27 | description: "Must be unique - this is the property name that will be sent to the server with this field's value." 28 | }, 29 | { 30 | type: 'string', 31 | name: 'label', 32 | label: 'Label', 33 | default: 'Name' 34 | }, 35 | { 36 | type: 'boolean', 37 | name: 'hideLabel', 38 | label: 'Hide label', 39 | default: false 40 | }, 41 | { 42 | type: 'string', 43 | name: 'placeholder', 44 | label: 'Placeholder text', 45 | default: 'Your name' 46 | }, 47 | { 48 | type: 'enum', 49 | name: 'width', 50 | group: 'styles', 51 | label: 'Width', 52 | options: [ 53 | { 54 | label: 'Full', 55 | value: 'full' 56 | }, 57 | { 58 | label: 'One half', 59 | value: '1/2' 60 | } 61 | ], 62 | default: 'full', 63 | required: true 64 | }, 65 | { 66 | type: 'boolean', 67 | name: 'isRequired', 68 | group: 'settings', 69 | label: 'Is the field required?', 70 | default: false 71 | } 72 | ] 73 | }; 74 | -------------------------------------------------------------------------------- /.stackbit/models/TextareaFormControl.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const TextareaFormControlModel: Model = { 4 | type: 'object', 5 | name: 'TextareaFormControl', 6 | label: 'Textarea', 7 | labelField: 'label', 8 | fieldGroups: [ 9 | { 10 | name: 'styles', 11 | label: 'Styles', 12 | icon: 'palette' 13 | }, 14 | { 15 | name: 'settings', 16 | label: 'Settings', 17 | icon: 'gear' 18 | } 19 | ], 20 | fields: [ 21 | { 22 | type: 'string', 23 | name: 'name', 24 | label: 'Name', 25 | required: true, 26 | default: 'message', 27 | description: "Must be unique - this is the property name that will be sent to the server with this field's value." 28 | }, 29 | { 30 | type: 'string', 31 | name: 'label', 32 | label: 'Label', 33 | default: 'Message' 34 | }, 35 | { 36 | type: 'boolean', 37 | name: 'hideLabel', 38 | label: 'Hide label', 39 | default: false 40 | }, 41 | { 42 | type: 'string', 43 | name: 'placeholder', 44 | label: 'Placeholder text', 45 | default: 'Type your message here' 46 | }, 47 | { 48 | type: 'enum', 49 | name: 'width', 50 | group: 'styles', 51 | label: 'Width', 52 | options: [ 53 | { 54 | label: 'Full', 55 | value: 'full' 56 | }, 57 | { 58 | label: 'One half', 59 | value: '1/2' 60 | } 61 | ], 62 | default: 'full', 63 | required: true 64 | }, 65 | { 66 | type: 'boolean', 67 | name: 'isRequired', 68 | group: 'settings', 69 | label: 'Is the field required?', 70 | default: false 71 | } 72 | ] 73 | }; 74 | -------------------------------------------------------------------------------- /.stackbit/models/VideoBlock.ts: -------------------------------------------------------------------------------- 1 | import { Model } from '@stackbit/types'; 2 | 3 | export const VideoBlockModel: Model = { 4 | type: 'object', 5 | name: 'VideoBlock', 6 | label: 'Video', 7 | labelField: 'title', 8 | fieldGroups: [ 9 | { 10 | name: 'settings', 11 | label: 'Settings', 12 | icon: 'gear' 13 | } 14 | ], 15 | fields: [ 16 | { 17 | type: 'string', 18 | name: 'title', 19 | description: 'The value of the field is used for presentation purposes in Stackbit', 20 | default: 'Title of the video' 21 | }, 22 | { 23 | type: 'string', 24 | name: 'url', 25 | label: 'Video URL (YouTube, Vimeo, .mp4)', 26 | default: 'https://youtu.be/BD-YliszGkA' 27 | }, 28 | { 29 | type: 'string', 30 | name: 'elementId', 31 | label: 'Element ID', 32 | description: 'The unique ID for an HTML element, must not contain whitespace', 33 | default: '', 34 | group: 'settings' 35 | }, 36 | { 37 | type: 'boolean', 38 | name: 'autoplay', 39 | label: 'Autoplay', 40 | default: false, 41 | group: 'settings' 42 | }, 43 | { 44 | type: 'boolean', 45 | name: 'loop', 46 | label: 'Loop', 47 | default: false, 48 | group: 'settings' 49 | }, 50 | { 51 | type: 'boolean', 52 | name: 'muted', 53 | label: 'Muted', 54 | default: false, 55 | group: 'settings' 56 | }, 57 | { 58 | type: 'boolean', 59 | name: 'controls', 60 | label: 'Controls', 61 | default: true, 62 | group: 'settings' 63 | }, 64 | { 65 | type: 'enum', 66 | name: 'aspectRatio', 67 | label: 'Aspect ratio', 68 | controlType: 'button-group', 69 | options: [ 70 | { 71 | label: '4:3', 72 | value: '4:3' 73 | }, 74 | { 75 | label: '16:9', 76 | value: '16:9' 77 | } 78 | ], 79 | default: '16:9', 80 | group: 'settings' 81 | } 82 | ] 83 | }; 84 | -------------------------------------------------------------------------------- /.stackbit/models/index.ts: -------------------------------------------------------------------------------- 1 | import { BackgroundImageModel } from './BackgroundImage'; 2 | import { ButtonModel } from './Button'; 3 | import { CheckboxFormControlModel } from './CheckboxFormControl'; 4 | import { ConfigModel } from './Config'; 5 | import { ContactBlockModel } from './ContactBlock'; 6 | import { ContactSectionModel } from './ContactSection'; 7 | import { CtaSectionModel } from './CtaSection'; 8 | import { DividerSectionModel } from './DividerSection'; 9 | import { EmailFormControlModel } from './EmailFormControl'; 10 | import { FeaturedItemModel } from './FeaturedItem'; 11 | import { FeaturedItemsSectionModel } from './FeaturedItemsSection'; 12 | import { FeaturedPostsSectionModel } from './FeaturedPostsSection'; 13 | import { FeaturedProjectsSectionModel } from './FeaturedProjectsSection'; 14 | import { FooterModel } from './Footer'; 15 | import { FormBlockModel } from './FormBlock'; 16 | import { HeaderModel } from './Header'; 17 | import { HeroSectionModel } from './HeroSection'; 18 | import { ImageBlockModel } from './ImageBlock'; 19 | import { LabelModel } from './Label'; 20 | import { LabelsSectionModel } from './LabelsSection'; 21 | import { LinkModel } from './Link'; 22 | import { MediaGallerySectionModel } from './MediaGallerySection'; 23 | import { MetaTagModel } from './MetaTag'; 24 | import { PageLayoutModel } from './PageLayout'; 25 | import { PersonModel } from './Person'; 26 | import { PostFeedLayoutModel } from './PostFeedLayout'; 27 | import { PostFeedSectionModel } from './PostFeedSection'; 28 | import { PostLayoutModel } from './PostLayout'; 29 | import { ProjectFeedLayoutModel } from './ProjectFeedLayout'; 30 | import { ProjectFeedSectionModel } from './ProjectFeedSection'; 31 | import { ProjectLayoutModel } from './ProjectLayout'; 32 | import { QuoteSectionModel } from './QuoteSection'; 33 | import { RecentPostsSectionModel } from './RecentPostsSection'; 34 | import { RecentProjectsSectionModel } from './RecentProjectsSection'; 35 | import { SelectFormControlModel } from './SelectFormControl'; 36 | import { SocialModel } from './Social'; 37 | import { TestimonialModel } from './Testimonial'; 38 | import { TestimonialsSectionModel } from './TestimonialsSection'; 39 | import { TextareaFormControlModel } from './TextareaFormControl'; 40 | import { TextFormControlModel } from './TextFormControl'; 41 | import { TextSectionModel } from './TextSection'; 42 | import { ThemeStyleModel } from './ThemeStyle'; 43 | import { VideoBlockModel } from './VideoBlock'; 44 | 45 | export const allModels = [ 46 | BackgroundImageModel, 47 | ButtonModel, 48 | CheckboxFormControlModel, 49 | ConfigModel, 50 | ContactBlockModel, 51 | ContactSectionModel, 52 | CtaSectionModel, 53 | DividerSectionModel, 54 | EmailFormControlModel, 55 | FeaturedItemModel, 56 | FeaturedItemsSectionModel, 57 | FeaturedPostsSectionModel, 58 | FeaturedProjectsSectionModel, 59 | FooterModel, 60 | FormBlockModel, 61 | HeaderModel, 62 | HeroSectionModel, 63 | ImageBlockModel, 64 | LabelModel, 65 | LabelsSectionModel, 66 | LinkModel, 67 | MediaGallerySectionModel, 68 | MetaTagModel, 69 | PageLayoutModel, 70 | PersonModel, 71 | PostFeedLayoutModel, 72 | PostFeedSectionModel, 73 | PostLayoutModel, 74 | ProjectFeedLayoutModel, 75 | ProjectFeedSectionModel, 76 | ProjectLayoutModel, 77 | QuoteSectionModel, 78 | RecentPostsSectionModel, 79 | RecentProjectsSectionModel, 80 | SelectFormControlModel, 81 | SocialModel, 82 | TestimonialModel, 83 | TestimonialsSectionModel, 84 | TextareaFormControlModel, 85 | TextFormControlModel, 86 | TextSectionModel, 87 | ThemeStyleModel, 88 | VideoBlockModel 89 | ]; 90 | -------------------------------------------------------------------------------- /.stackbit/models/section-common-fields.ts: -------------------------------------------------------------------------------- 1 | import { Field, FieldGroupItem } from '@stackbit/types'; 2 | 3 | export const backgroundFields: Field[] = [ 4 | { 5 | type: 'enum', 6 | name: 'backgroundSize', 7 | group: 'styles', 8 | label: 'Background size', 9 | controlType: 'button-group', 10 | options: [ 11 | { 12 | label: 'Full', 13 | value: 'full' 14 | }, 15 | { 16 | label: 'Inset', 17 | value: 'inset' 18 | } 19 | ], 20 | default: 'full' 21 | } 22 | ]; 23 | 24 | export const colorFields: Field[] = [ 25 | { 26 | type: 'enum', 27 | name: 'colors', 28 | label: 'Colors', 29 | description: 'The color theme of the section', 30 | group: 'styles', 31 | controlType: 'palette', 32 | options: [ 33 | { 34 | label: 'Colors A', 35 | value: 'colors-a', 36 | textColor: '$onDark', 37 | backgroundColor: '$dark', 38 | borderColor: '#ececec' 39 | }, 40 | { 41 | label: 'Colors B', 42 | value: 'colors-b', 43 | textColor: '$onLight', 44 | backgroundColor: '$light', 45 | borderColor: '#ececec' 46 | }, 47 | { 48 | label: 'Colors C', 49 | value: 'colors-c', 50 | textColor: '$onPrimary', 51 | backgroundColor: '$primary', 52 | borderColor: '#ececec' 53 | }, 54 | { 55 | label: 'Colors D', 56 | value: 'colors-d', 57 | textColor: '$onSecondary', 58 | backgroundColor: '$secondary', 59 | borderColor: '#ececec' 60 | }, 61 | { 62 | label: 'Colors E', 63 | value: 'colors-e', 64 | textColor: '$onComplementary', 65 | backgroundColor: '$complementary', 66 | borderColor: '#ececec' 67 | }, 68 | { 69 | label: 'Colors F', 70 | value: 'colors-f', 71 | textColor: '$onLight', 72 | backgroundColor: 'transparent', 73 | borderColor: '#ececec' 74 | } 75 | ], 76 | default: 'colors-f' 77 | } 78 | ]; 79 | 80 | export const settingFields: Field[] = [ 81 | { 82 | type: 'string', 83 | name: 'elementId', 84 | group: 'settings', 85 | label: 'Element ID', 86 | description: 'The unique ID for an HTML element, must not contain whitespace', 87 | default: '' 88 | } 89 | ]; 90 | 91 | export const styleFieldsGroup: FieldGroupItem[] = [ 92 | { 93 | name: 'styles', 94 | label: 'Styles', 95 | icon: 'palette' 96 | } 97 | ]; 98 | 99 | export const settingFieldsGroup: FieldGroupItem[] = [ 100 | { 101 | name: 'settings', 102 | label: 'Settings', 103 | icon: 'gear' 104 | } 105 | ]; 106 | -------------------------------------------------------------------------------- /.stackbit/presets/divider-section.json: -------------------------------------------------------------------------------- 1 | { 2 | "model": "DividerSection", 3 | "presets": [ 4 | { 5 | "label": "Divider narrow", 6 | "thumbnail": "images/divider-narrow.png", 7 | "data": { 8 | "styles": { 9 | "self": { 10 | "width": "narrow", 11 | "padding": ["pt-36", "pb-36", "pl-4", "pr-4"], 12 | "borderWidth": 1 13 | } 14 | } 15 | } 16 | }, 17 | { 18 | "label": "Divider wide", 19 | "thumbnail": "images/divider-narrow.png", 20 | "data": { 21 | "styles": { 22 | "self": { 23 | "width": "wide", 24 | "padding": ["pt-36", "pb-36", "pl-4", "pr-4"], 25 | "borderWidth": 1 26 | } 27 | } 28 | } 29 | }, 30 | { 31 | "label": "Divider full", 32 | "thumbnail": "images/divider-full.png", 33 | "data": { 34 | "styles": { 35 | "self": { 36 | "width": "full", 37 | "padding": ["pt-36", "pb-36"], 38 | "borderWidth": 1 39 | } 40 | } 41 | } 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /.stackbit/presets/images/contact-primary-no-media.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/contact-primary-no-media.png -------------------------------------------------------------------------------- /.stackbit/presets/images/contact-secondary-image-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/contact-secondary-image-right.png -------------------------------------------------------------------------------- /.stackbit/presets/images/contact-transparent-no-media.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/contact-transparent-no-media.png -------------------------------------------------------------------------------- /.stackbit/presets/images/cta-primary-btn-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/cta-primary-btn-bottom.png -------------------------------------------------------------------------------- /.stackbit/presets/images/cta-transparent-btn-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/cta-transparent-btn-bottom.png -------------------------------------------------------------------------------- /.stackbit/presets/images/cta-transparent-btn-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/cta-transparent-btn-right.png -------------------------------------------------------------------------------- /.stackbit/presets/images/divider-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/divider-full.png -------------------------------------------------------------------------------- /.stackbit/presets/images/divider-narrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/divider-narrow.png -------------------------------------------------------------------------------- /.stackbit/presets/images/divider-wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/divider-wide.png -------------------------------------------------------------------------------- /.stackbit/presets/images/feat-items-secondary-2-col.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/feat-items-secondary-2-col.png -------------------------------------------------------------------------------- /.stackbit/presets/images/feat-items-transparent-1-col.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/feat-items-transparent-1-col.png -------------------------------------------------------------------------------- /.stackbit/presets/images/hero-primary-image-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/hero-primary-image-right.png -------------------------------------------------------------------------------- /.stackbit/presets/images/hero-secondary-image-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/hero-secondary-image-left.png -------------------------------------------------------------------------------- /.stackbit/presets/images/hero-transparent-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/hero-transparent-text.png -------------------------------------------------------------------------------- /.stackbit/presets/images/media-gallery-dark-5-col.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/media-gallery-dark-5-col.png -------------------------------------------------------------------------------- /.stackbit/presets/images/media-gallery-primary-2-col.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/media-gallery-primary-2-col.png -------------------------------------------------------------------------------- /.stackbit/presets/images/media-gallery-transparent-4-col.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/media-gallery-transparent-4-col.png -------------------------------------------------------------------------------- /.stackbit/presets/images/page-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/page-empty.png -------------------------------------------------------------------------------- /.stackbit/presets/images/page-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/page-info.png -------------------------------------------------------------------------------- /.stackbit/presets/images/page-landing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/page-landing.png -------------------------------------------------------------------------------- /.stackbit/presets/images/posts-secondary-list-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/posts-secondary-list-alt.png -------------------------------------------------------------------------------- /.stackbit/presets/images/posts-transparent-2-col.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/posts-transparent-2-col.png -------------------------------------------------------------------------------- /.stackbit/presets/images/posts-transparent-3-col.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/posts-transparent-3-col.png -------------------------------------------------------------------------------- /.stackbit/presets/images/posts-transparent-list-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/posts-transparent-list-alt.png -------------------------------------------------------------------------------- /.stackbit/presets/images/posts-transparent-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/posts-transparent-list.png -------------------------------------------------------------------------------- /.stackbit/presets/images/projects-primary-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/projects-primary-list.png -------------------------------------------------------------------------------- /.stackbit/presets/images/projects-secondary-3-col.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/projects-secondary-3-col.png -------------------------------------------------------------------------------- /.stackbit/presets/images/projects-transparent-2-col.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/projects-transparent-2-col.png -------------------------------------------------------------------------------- /.stackbit/presets/images/quote-secondary-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/quote-secondary-left.png -------------------------------------------------------------------------------- /.stackbit/presets/images/quote-transparent-centered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/quote-transparent-centered.png -------------------------------------------------------------------------------- /.stackbit/presets/images/quote-transparent-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/quote-transparent-left.png -------------------------------------------------------------------------------- /.stackbit/presets/images/skills-secondary-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/skills-secondary-left.png -------------------------------------------------------------------------------- /.stackbit/presets/images/skills-transparent-centered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/skills-transparent-centered.png -------------------------------------------------------------------------------- /.stackbit/presets/images/skills-transparent-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/skills-transparent-left.png -------------------------------------------------------------------------------- /.stackbit/presets/images/testimonials-dark-small-images-col.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/testimonials-dark-small-images-col.png -------------------------------------------------------------------------------- /.stackbit/presets/images/testimonials-secondary-big-images-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/testimonials-secondary-big-images-list.png -------------------------------------------------------------------------------- /.stackbit/presets/images/testimonials-white-small-images-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/testimonials-white-small-images-list.png -------------------------------------------------------------------------------- /.stackbit/presets/images/text-secondary-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/text-secondary-left.png -------------------------------------------------------------------------------- /.stackbit/presets/images/text-transparent-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/text-transparent-center.png -------------------------------------------------------------------------------- /.stackbit/presets/images/text-transparent-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/.stackbit/presets/images/text-transparent-left.png -------------------------------------------------------------------------------- /.stackbit/presets/media-gallery-section.json: -------------------------------------------------------------------------------- 1 | { 2 | "model": "MediaGallerySection", 3 | "presets": [ 4 | { 5 | "label": "5 columns dark wide", 6 | "thumbnail": "images/media-gallery-dark-5-col.png", 7 | "data": { 8 | "colors": "colors-a", 9 | "title": "Gallery", 10 | "subtitle": "This is the subtitle", 11 | "images": [ 12 | { 13 | "type": "ImageBlock", 14 | "url": "/images/logo1.svg", 15 | "altText": "logo one" 16 | }, 17 | { 18 | "type": "ImageBlock", 19 | "url": "/images/logo2.svg", 20 | "altText": "logo two" 21 | }, 22 | { 23 | "type": "ImageBlock", 24 | "url": "/images/logo3.svg", 25 | "altText": "logo three" 26 | }, 27 | { 28 | "type": "ImageBlock", 29 | "url": "/images/logo4.svg", 30 | "altText": "logo four" 31 | }, 32 | { 33 | "type": "ImageBlock", 34 | "url": "/images/logo5.svg", 35 | "altText": "logo five" 36 | } 37 | ], 38 | "columns": 5, 39 | "spacing": 16, 40 | "aspectRatio": "16:9", 41 | "showCaption": true, 42 | "enableHover": false, 43 | "styles": { 44 | "self": { 45 | "height": "auto", 46 | "width": "wide", 47 | "padding": ["pt-12", "pb-12", "pl-4", "pr-4"], 48 | "textAlign": "center" 49 | } 50 | } 51 | } 52 | }, 53 | { 54 | "label": "4 columns transparent full", 55 | "thumbnail": "images/media-gallery-transparent-4-col.png", 56 | "data": { 57 | "colors": "colors-f", 58 | "title": "Gallery", 59 | "subtitle": "This is the subtitle", 60 | "columns": 4, 61 | "spacing": 16, 62 | "aspectRatio": "4:3", 63 | "showCaption": false, 64 | "enableHover": true, 65 | "styles": { 66 | "self": { 67 | "height": "auto", 68 | "width": "full", 69 | "padding": ["pt-12", "pb-12", "pl-4", "pr-4"], 70 | "textAlign": "center" 71 | } 72 | } 73 | } 74 | }, 75 | { 76 | "label": "2 columns dark narrow", 77 | "thumbnail": "images/media-gallery-primary-2-col.png", 78 | "data": { 79 | "colors": "colors-c", 80 | "title": "Gallery", 81 | "subtitle": "This is the subtitle", 82 | "columns": 2, 83 | "spacing": 16, 84 | "aspectRatio": "1:1", 85 | "showCaption": false, 86 | "enableHover": false, 87 | "styles": { 88 | "self": { 89 | "height": "auto", 90 | "width": "narrow", 91 | "padding": ["pt-12", "pb-12", "pl-4", "pr-4"], 92 | "textAlign": "center" 93 | } 94 | } 95 | } 96 | } 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /.stackbit/presets/quote-section.json: -------------------------------------------------------------------------------- 1 | { 2 | "model": "QuoteSection", 3 | "presets": [ 4 | { 5 | "label": "Quote transparent, text left", 6 | "thumbnail": "images/quote-transparent-left.png", 7 | "data": { 8 | "colors": "colors-f", 9 | "styles": { 10 | "self": { 11 | "height": "auto", 12 | "width": "wide", 13 | "padding": ["pt-36", "pb-36", "pl-4", "pr-4"], 14 | "textAlign": "left" 15 | } 16 | } 17 | } 18 | }, 19 | { 20 | "label": "Quote transparent, text centered", 21 | "thumbnail": "images/quote-transparent-centered.png", 22 | "data": { 23 | "colors": "colors-f", 24 | "styles": { 25 | "self": { 26 | "height": "auto", 27 | "width": "wide", 28 | "padding": ["pt-36", "pb-36", "pl-4", "pr-4"], 29 | "textAlign": "center" 30 | } 31 | } 32 | } 33 | }, 34 | { 35 | "label": "Quote secondary, text left", 36 | "thumbnail": "images/quote-secondary-left.png", 37 | "data": { 38 | "colors": "colors-d", 39 | "styles": { 40 | "self": { 41 | "height": "auto", 42 | "width": "wide", 43 | "padding": ["pt-36", "pb-36", "pl-4", "pr-4"], 44 | "textAlign": "left" 45 | } 46 | } 47 | } 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /.stackbit/presets/recent-projects-section.json: -------------------------------------------------------------------------------- 1 | { 2 | "model": "RecentProjectsSection", 3 | "presets": [ 4 | { 5 | "label": "Recent projects transparent - 2 columns", 6 | "thumbnail": "images/projects-transparent-2-col.png", 7 | "data": { 8 | "colors": "colors-f", 9 | "variant": "variant-a", 10 | "title": null, 11 | "subtitle": "Projects", 12 | "showDate": false, 13 | "showDescription": true, 14 | "showReadMoreLink": true, 15 | "showFeaturedImage": true, 16 | "actions": [ 17 | { 18 | "label": "See all projects", 19 | "altText": "See all projects", 20 | "url": "/projects", 21 | "type": "Link" 22 | } 23 | ], 24 | "styles": { 25 | "self": { 26 | "height": "auto", 27 | "width": "wide", 28 | "padding": ["pt-24", "pb-24", "pl-4", "pr-4"], 29 | "textAlign": "left" 30 | } 31 | } 32 | } 33 | }, 34 | { 35 | "label": "Recent projects secondary - 3 columns", 36 | "thumbnail": "images/projects-secondary-3-col.png", 37 | "data": { 38 | "colors": "colors-d", 39 | "variant": "variant-b", 40 | "title": null, 41 | "subtitle": "Projects", 42 | "showDate": false, 43 | "showDescription": true, 44 | "showReadMoreLink": true, 45 | "showFeaturedImage": true, 46 | "actions": [ 47 | { 48 | "label": "See all projects", 49 | "altText": "See all projects", 50 | "url": "/projects", 51 | "type": "Link" 52 | } 53 | ], 54 | "styles": { 55 | "self": { 56 | "height": "auto", 57 | "width": "wide", 58 | "padding": ["pt-24", "pb-24", "pl-4", "pr-4"], 59 | "textAlign": "left" 60 | } 61 | } 62 | } 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /.stackbit/presets/text-section.json: -------------------------------------------------------------------------------- 1 | { 2 | "model": "TextSection", 3 | "presets": [ 4 | { 5 | "label": "Text transparent left", 6 | "thumbnail": "images/text-transparent-left.png", 7 | "data": { 8 | "colors": "colors-f", 9 | "title": "The Section Title", 10 | "subtitle": null, 11 | "styles": { 12 | "self": { 13 | "height": "auto", 14 | "width": "narrow", 15 | "padding": ["pt-28", "pb-28", "pl-4", "pr-4"], 16 | "textAlign": "left" 17 | } 18 | } 19 | } 20 | }, 21 | { 22 | "label": "Text transparent center", 23 | "thumbnail": "images/text-transparent-center.png", 24 | "data": { 25 | "colors": "colors-f", 26 | "title": "The Section Title", 27 | "subtitle": "The section subtitle", 28 | "styles": { 29 | "self": { 30 | "height": "auto", 31 | "width": "narrow", 32 | "padding": ["pt-28", "pb-28", "pl-4", "pr-4"], 33 | "textAlign": "center" 34 | } 35 | } 36 | } 37 | }, 38 | { 39 | "label": "Text secondary left", 40 | "thumbnail": "images/text-secondary-left.png", 41 | "data": { 42 | "colors": "colors-d", 43 | "variant": "variant-b", 44 | "title": "The Section Title", 45 | "subtitle": "The section subtitle", 46 | "styles": { 47 | "self": { 48 | "height": "auto", 49 | "width": "wide", 50 | "padding": ["pt-28", "pb-28", "pl-4", "pr-4"], 51 | "textAlign": "left" 52 | } 53 | } 54 | } 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "bradlc.vscode-tailwindcss"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Next.js app", 9 | "request": "launch", 10 | "runtimeArgs": ["run", "dev"], 11 | "runtimeExecutable": "npm", 12 | "skipFiles": ["/**"], 13 | "type": "node" 14 | }, 15 | { 16 | "type": "node", 17 | "request": "attach", 18 | "name": "Attach to application", 19 | "skipFiles": ["/**"], 20 | "port": 9229 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 4, 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": "explicit", 6 | "source.organizeImports": "explicit", 7 | "source.fixAll": "explicit" 8 | }, 9 | "[javascript]": { 10 | "editor.defaultFormatter": "esbenp.prettier-vscode", 11 | "editor.formatOnSave": true 12 | }, 13 | "[yaml]": { 14 | "editor.defaultFormatter": "esbenp.prettier-vscode", 15 | "editor.tabSize": 2 16 | }, 17 | "[toml]": { 18 | "editor.defaultFormatter": "esbenp.prettier-vscode", 19 | "editor.tabSize": 2 20 | }, 21 | "[markdown]": { 22 | "editor.defaultFormatter": "esbenp.prettier-vscode", 23 | "editor.tabSize": 2 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Netlify Developer Portfolio Starter (auto-annotated) 2 | 3 | ![Developer Portfolio](https://assets.stackbit.com/docs/personal-nextjs-starter-thumb.png) 4 | 5 | This is a full-fledged portfolio website built with Next.js, Tailwind CSS, [visual editor](https://docs.netlify.com/visual-editor/overview/) and the [Git Content Source](https://docs.netlify.com/create/content-sources/git/). 6 | 7 | The codebase showcases **how to apply annotations at scale**, meaning: how to make much of your components [highlightable in the visual editor](https://docs.netlify.com/visual-editor/visual-editing/inline-editor/) through data attributes without manually adding code throughout the codebase. 8 | 9 | **This is achieved by:** 10 | 11 | 1. Adding an annotation property to the content objects at they're loaded (see `src/utils/content.ts`) 12 | 1. When rendering the page, each content sub-object is dynamically matched to the appropriate component. At this point, wrap each component with an annotation, based on the abovementioned content property. See `src/components/components-registry.tsx`. 13 | 14 | **⚡ Demo:** [auto-annotated-portfolio.netlify.app](https://auto-annotated-portfolio.netlify.app) 15 | 16 | ## Deploying to Netlify 17 | 18 | If you click "Deploy to Netlify" button, it will create a new repo for you that looks exactly like this one, and sets that repo up immediately for deployment on Netlify. 19 | 20 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/netlify-templates/auto-annotated-portfolio) 21 | 22 | ## Getting Started 23 | 24 | The typical development process is to begin by working locally. Clone this repository, then run `npm install` in its root directory. 25 | 26 | Run the Next.js development server: 27 | 28 | ```txt 29 | cd auto-annotated-portfolio 30 | npm run dev 31 | ``` 32 | 33 | Install the [Netlify visual editor CLI](https://www.npmjs.com/package/@stackbit/cli). Then open a new terminal window in the same project directory and run the Netlify visual editor dev server: 34 | 35 | ```txt 36 | npm install -g @stackbit/cli 37 | stackbit dev 38 | ``` 39 | 40 | This outputs your own Netlify visual editor URL. Open this, register or sign in, and you will be directed to Netlify's visual editor for your new project. 41 | 42 | ![Next.js Dev + Netlify visual editor dev](https://assets.stackbit.com/docs/next-dev-stackbit-dev.png) 43 | 44 | ## Next Steps 45 | 46 | Here are a few suggestions on what to do next if you're new to Netlify Visual Editor: 47 | 48 | - Learn [how Netlify Visual Editor works](https://docs.netlify.com/visual-editor/overview/) 49 | - Check [Netlify visual editor reference documentation](https://visual-editor-reference.netlify.com/) 50 | 51 | ## Support 52 | 53 | If you get stuck along the way, get help in our [support forums](https://answers.netlify.com/). 54 | -------------------------------------------------------------------------------- /content/data/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Config", 3 | "favicon": "/images/favicon.svg", 4 | "header": { 5 | "type": "Header", 6 | "headerVariant": "variant-c", 7 | "isSticky": false, 8 | "title": "Personal", 9 | "isTitleVisible": true, 10 | "primaryLinks": [ 11 | { 12 | "type": "Link", 13 | "label": "Info", 14 | "url": "/info" 15 | }, 16 | { 17 | "type": "Link", 18 | "label": "Projects", 19 | "url": "/projects" 20 | }, 21 | { 22 | "type": "Link", 23 | "label": "Blog", 24 | "url": "/blog" 25 | } 26 | ], 27 | "socialLinks": [ 28 | { 29 | "type": "Social", 30 | "altText": "Twitter", 31 | "label": "Twitter", 32 | "url": "https://twitter.com/netlify", 33 | "icon": "twitter" 34 | }, 35 | { 36 | "type": "Social", 37 | "altText": "GitHub", 38 | "label": "GitHub", 39 | "url": "https://github.com/netlify-templates", 40 | "icon": "github" 41 | } 42 | ], 43 | "styles": { 44 | "self": { 45 | "width": "full" 46 | } 47 | } 48 | }, 49 | "footer": { 50 | "type": "Footer", 51 | "primaryLinks": [ 52 | { 53 | "type": "Link", 54 | "label": "Info", 55 | "url": "/info", 56 | "altText": "" 57 | }, 58 | { 59 | "type": "Link", 60 | "label": "Contact", 61 | "url": "mailto:thisismyemail.@myemail.me", 62 | "altText": "" 63 | }, 64 | { 65 | "type": "Link", 66 | "label": "Blog", 67 | "url": "/blog", 68 | "altText": "" 69 | }, 70 | { 71 | "type": "Link", 72 | "label": "GitHub", 73 | "url": "https://github.com/netlify-templates", 74 | "altText": "Netlify templates" 75 | } 76 | ], 77 | "contacts": { 78 | "type": "ContactBlock", 79 | "title": "Contact details", 80 | "phoneNumber": "850-123-5021", 81 | "phoneAltText": "My phone number", 82 | "email": "thisismyemail.@myemail.me", 83 | "emailAltText": "My email", 84 | "address": "312 Lovely Street, Brooklyn, NY, 123456", 85 | "addressAltText": "My address" 86 | }, 87 | "copyrightText": "Powered by [Netlify](https://www.netlify.com/)", 88 | "styles": { 89 | "self": { 90 | "width": "narrow", 91 | "padding": [ 92 | "pt-16", 93 | "pb-16", 94 | "pr-4", 95 | "pl-4" 96 | ] 97 | } 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /content/data/style.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "ThemeStyle", 3 | "light": "#ffffff", 4 | "onLight": "#050806", 5 | "dark": "#000000", 6 | "onDark": "#FBFFF2", 7 | "primary": "#0804F6", 8 | "onPrimary": "#FBFFF2", 9 | "secondary": "#FE491F", 10 | "onSecondary": "#050806", 11 | "complementary": "#565862", 12 | "onComplementary": "#FBFFF2", 13 | "fontBody": "\"DM Mono\", monospace", 14 | "headingWeight": "400", 15 | "headingCase": "uppercase" 16 | } 17 | -------------------------------------------------------------------------------- /content/data/team/doris-soto.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Person", 3 | "firstName": "Doris", 4 | "lastName": "Soto", 5 | "image": { 6 | "type": "ImageBlock", 7 | "url": "/images/about.jpg", 8 | "altText": "Doris Soto photo" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /content/pages/blog/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: PostFeedLayout 3 | title: Blog 4 | colors: colors-a 5 | backgroundImage: 6 | type: BackgroundImage 7 | url: /images/bg2.jpg 8 | backgroundSize: cover 9 | backgroundPosition: center 10 | backgroundRepeat: no-repeat 11 | opacity: 75 12 | postFeed: 13 | type: PostFeedSection 14 | colors: colors-f 15 | showDate: true 16 | showAuthor: false 17 | showExcerpt: true 18 | showFeaturedImage: true 19 | showReadMoreLink: true 20 | variant: variant-d 21 | styles: 22 | self: 23 | width: narrow 24 | padding: 25 | - pt-0 26 | - pl-4 27 | - pr-4 28 | - pb-12 29 | topSections: 30 | - type: HeroSection 31 | title: Blog 32 | subtitle: '' 33 | actions: [] 34 | colors: colors-f 35 | backgroundSize: full 36 | elementId: '' 37 | styles: 38 | self: 39 | height: auto 40 | width: narrow 41 | padding: 42 | - pt-16 43 | - pb-16 44 | - pl-4 45 | - pr-4 46 | flexDirection: row 47 | textAlign: left 48 | --- 49 | -------------------------------------------------------------------------------- /content/pages/projects/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: ProjectFeedLayout 3 | title: Projects 4 | colors: colors-a 5 | backgroundImage: 6 | type: BackgroundImage 7 | url: /images/bg1.jpg 8 | backgroundSize: cover 9 | backgroundPosition: center 10 | backgroundRepeat: no-repeat 11 | opacity: 50 12 | projectFeed: 13 | type: ProjectFeedSection 14 | colors: colors-f 15 | showDate: false 16 | showDescription: true 17 | showReadMoreLink: true 18 | showFeaturedImage: true 19 | variant: variant-a 20 | styles: 21 | self: 22 | width: narrow 23 | padding: 24 | - pt-0 25 | - pl-4 26 | - pr-4 27 | - pb-12 28 | topSections: 29 | - type: HeroSection 30 | title: Projects 31 | subtitle: '' 32 | actions: [] 33 | colors: colors-f 34 | backgroundSize: full 35 | elementId: '' 36 | styles: 37 | self: 38 | height: auto 39 | width: narrow 40 | padding: 41 | - pt-16 42 | - pb-16 43 | - pl-4 44 | - pr-4 45 | flexDirection: row 46 | textAlign: left 47 | bottomSections: 48 | - type: ContactSection 49 | backgroundSize: full 50 | title: "Let’s talk... \U0001F4AC" 51 | colors: colors-f 52 | form: 53 | type: FormBlock 54 | elementId: sign-up-form 55 | fields: 56 | - name: firstName 57 | label: First Name 58 | hideLabel: true 59 | placeholder: First Name 60 | isRequired: true 61 | width: 1/2 62 | type: TextFormControl 63 | - name: lastName 64 | label: Last Name 65 | hideLabel: true 66 | placeholder: Last Name 67 | isRequired: false 68 | width: 1/2 69 | type: TextFormControl 70 | - name: email 71 | label: Email 72 | hideLabel: true 73 | placeholder: Email 74 | isRequired: true 75 | width: full 76 | type: EmailFormControl 77 | - name: message 78 | label: Message 79 | hideLabel: true 80 | placeholder: Tell me about your project 81 | isRequired: true 82 | width: full 83 | type: TextareaFormControl 84 | - name: updatesConsent 85 | label: Sign me up to recieve my words 86 | isRequired: false 87 | width: full 88 | type: CheckboxFormControl 89 | submitLabel: "Submit \U0001F680" 90 | styles: 91 | self: 92 | textAlign: center 93 | styles: 94 | self: 95 | height: auto 96 | width: narrow 97 | margin: 98 | - mt-0 99 | - mb-0 100 | - ml-4 101 | - mr-4 102 | padding: 103 | - pt-24 104 | - pb-24 105 | - pr-4 106 | - pl-4 107 | flexDirection: row 108 | textAlign: left 109 | --- 110 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = ".next" 3 | command = "npm run build" 4 | 5 | [[plugins]] 6 | package = "@netlify/plugin-nextjs" -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | env: { 4 | stackbitPreview: process.env.STACKBIT_PREVIEW 5 | }, 6 | trailingSlash: true, 7 | reactStrictMode: true 8 | }; 9 | 10 | module.exports = nextConfig; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "personal-site", 3 | "version": "0.2.0", 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start" 8 | }, 9 | "dependencies": { 10 | "@tailwindcss/postcss": "^4.0.12", 11 | "classnames": "^2.5.1", 12 | "dayjs": "^1.11.11", 13 | "front-matter": "^4.0.2", 14 | "markdown-to-jsx": "^7.7.1", 15 | "next": "^15.2.1", 16 | "react": "^19.0.0", 17 | "react-dom": "^19.0.0", 18 | "react-syntax-highlighter": "^15.6.1", 19 | "tailwindcss": "^4.0.12" 20 | }, 21 | "devDependencies": { 22 | "@stackbit/cms-git": "^1.0.30", 23 | "@stackbit/types": "^2.1.11", 24 | "@tailwindcss/typography": "^0.5.16", 25 | "@types/glob": "^8.1.0", 26 | "@types/react": "^19.0.1", 27 | "@types/react-syntax-highlighter": "^15.5.13", 28 | "eslint": "^9.21.0", 29 | "eslint-config-next": "^15.2.0", 30 | "js-yaml": "^4.1.0", 31 | "postcss": "^8.5.3", 32 | "prettier": "^3.5.2", 33 | "typescript": "^5.8.2" 34 | }, 35 | "license": "MIT" 36 | } 37 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /public/images/about.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/about.jpg -------------------------------------------------------------------------------- /public/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/bg.jpg -------------------------------------------------------------------------------- /public/images/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/bg1.jpg -------------------------------------------------------------------------------- /public/images/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/bg2.jpg -------------------------------------------------------------------------------- /public/images/bg3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/bg3.jpg -------------------------------------------------------------------------------- /public/images/bg4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/bg4.jpg -------------------------------------------------------------------------------- /public/images/contact.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/contact.jpg -------------------------------------------------------------------------------- /public/images/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/featured-Image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/featured-Image1.jpg -------------------------------------------------------------------------------- /public/images/featured-Image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/featured-Image2.jpg -------------------------------------------------------------------------------- /public/images/featured-Image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/featured-Image3.jpg -------------------------------------------------------------------------------- /public/images/featured-Image4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/featured-Image4.jpg -------------------------------------------------------------------------------- /public/images/featured-Image5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/featured-Image5.jpg -------------------------------------------------------------------------------- /public/images/featured-Image6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/featured-Image6.jpg -------------------------------------------------------------------------------- /public/images/gallery-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/gallery-1.jpg -------------------------------------------------------------------------------- /public/images/gallery-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/gallery-2.jpg -------------------------------------------------------------------------------- /public/images/gallery-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/gallery-3.jpg -------------------------------------------------------------------------------- /public/images/gallery-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/gallery-4.jpg -------------------------------------------------------------------------------- /public/images/logo1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/logo2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/logo3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/logo4.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/logo5.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/person-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/person-1.jpg -------------------------------------------------------------------------------- /public/images/person-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/person-2.jpg -------------------------------------------------------------------------------- /public/images/person-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/person-3.jpg -------------------------------------------------------------------------------- /public/images/post-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/post-1.jpg -------------------------------------------------------------------------------- /public/images/post-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/post-2.jpg -------------------------------------------------------------------------------- /public/images/post-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/post-3.jpg -------------------------------------------------------------------------------- /public/images/post-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/images/post-4.png -------------------------------------------------------------------------------- /public/personal-nextjs-theme-capture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/personal-nextjs-theme-capture.png -------------------------------------------------------------------------------- /public/personal-nextjs-theme-screenshot-2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/personal-nextjs-theme-screenshot-2x.jpg -------------------------------------------------------------------------------- /public/personal-nextjs-theme-screenshot-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/personal-nextjs-theme-screenshot-2x.png -------------------------------------------------------------------------------- /public/personal-nextjs-theme-screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/personal-nextjs-theme-screenshot.jpg -------------------------------------------------------------------------------- /public/personal-nextjs-theme-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify-templates/auto-annotated-portfolio/ca3899ce4f8d4f4dca492d8aef34f63ea4c854b2/public/personal-nextjs-theme-screenshot.png -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>netlify-templates/renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Annotated.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * wrapper component is used both by and explicitly by other components, 3 | * to wrap a given child with a tag having a Stackbit annotation extracted from the component props. 4 | * Note that all content object types include the HasAnnotation type. 5 | * 6 | * If you want to annotate a primitive field (rather than a content object) you can use the 7 | * helper below, which accepts a field-path string. 8 | * Annotating primitive fields this way is more intrusive in code, and requires manually entering a field path, 9 | * but unlocks not just direct selection of fields but also field-level styling controls. 10 | */ 11 | import { PropsWithChildren } from 'react'; 12 | import { isDev } from '@/utils/common'; 13 | import { HasAnnotation, fieldPathAttr, objectIdAttr } from '@/types'; 14 | 15 | type AnnotatedProps = PropsWithChildren & { 16 | content: HasAnnotation; 17 | }; 18 | 19 | export const Annotated: React.FC = (props) => { 20 | const { children } = props; 21 | const baseResult = <>{children}; 22 | if (!isDev) { 23 | return baseResult; 24 | } else if (!props.content) { 25 | console.warn('Annotated: no content property. Props:', props); 26 | return baseResult; 27 | } else if (!children || (Array.isArray(children) && children.length !== 1)) { 28 | console.log('Annotated: provide a single child. Given:', children); 29 | return baseResult; 30 | } 31 | 32 | const annotation = annotationFromProps(props.content); 33 | if (annotation) { 34 | return {props.children}; 35 | } else { 36 | console.warn('Annotated: no annotation in content. Props:', props); 37 | return baseResult; 38 | } 39 | }; 40 | 41 | type AnnotatedFieldProps = PropsWithChildren & { 42 | path: string; 43 | }; 44 | 45 | export const AnnotatedField: React.FC = (props) => { 46 | const content: HasAnnotation = { [fieldPathAttr]: props.path }; 47 | 48 | return {props.children}; 49 | }; 50 | 51 | function annotationFromProps(props: HasAnnotation) { 52 | return props?.[objectIdAttr] ? { [objectIdAttr]: props[objectIdAttr] } : props?.[fieldPathAttr] ? { [fieldPathAttr]: props[fieldPathAttr] } : undefined; 53 | } 54 | 55 | const AnnotatedWrapperTag: React.FC = ({ annotation, children }) => { 56 | const fieldPath = annotation[fieldPathAttr]; 57 | if (fieldPath) annotation = { [fieldPathAttr]: fieldPath + '#*[1]' }; 58 | return {children}; 59 | }; 60 | -------------------------------------------------------------------------------- /src/components/atoms/Action/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | 3 | import { Annotated } from '@/components/Annotated'; 4 | import { iconMap } from '@/components/svgs'; 5 | import Link from '../Link'; 6 | 7 | export default function Action(props) { 8 | const { 9 | type, 10 | elementId, 11 | className, 12 | label, 13 | altText, 14 | url, 15 | showIcon, 16 | icon, 17 | iconPosition = 'right', 18 | style = 'primary' 19 | } = props; 20 | const IconComponent = icon ? iconMap[icon] : null; 21 | 22 | const baseClasses = [ 23 | 'relative inline-flex items-center justify-center gap-1.5 text-center text-lg leading-tight no-underline transition lg:whitespace-nowrap' 24 | ]; 25 | if (type === 'Button') { 26 | label ? baseClasses.push('py-4 px-5') : baseClasses.push('p-4'); 27 | style === 'secondary' && baseClasses.push('rounded-full'); 28 | baseClasses.push('border-2 border-current hover:bottom-shadow-6 hover:-translate-y-1.5'); 29 | } else { 30 | baseClasses.push('uppercase bottom-shadow-1 hover:bottom-shadow-5'); 31 | } 32 | 33 | return ( 34 | 35 | 36 | {showIcon && IconComponent && iconPosition === 'left' && ( 37 | 38 | )} 39 | {label} 40 | {showIcon && IconComponent && iconPosition === 'right' && ( 41 | 42 | )} 43 | 44 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /src/components/atoms/BackgroundImage/index.tsx: -------------------------------------------------------------------------------- 1 | import { mapStylesToClassNames as mapStyles } from '@/utils/map-styles-to-class-names'; 2 | import classNames from 'classnames'; 3 | 4 | export default function BackgroundImage(props) { 5 | const { url, className, backgroundSize, backgroundPosition, backgroundRepeat, opacity } = props; 6 | if (!url) { 7 | return null; 8 | } 9 | return ( 10 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/atoms/Link/index.tsx: -------------------------------------------------------------------------------- 1 | import { ContentObject, PageModelType } from '@/types'; 2 | import NextLink, { LinkProps as NextLinkProps } from 'next/link'; 3 | import * as React from 'react'; 4 | 5 | import { Annotated } from '@/components/Annotated'; 6 | 7 | type RegularLinkProps = React.PropsWithChildren & NextLinkProps & React.AnchorHTMLAttributes; 8 | 9 | type LinkProps = RegularLinkProps | (Omit & { href: PageModelType }); 10 | 11 | const Link: React.FC = (props) => { 12 | const { children, href: hrefArgument, ...other } = props; 13 | let hrefString: string = null; 14 | let hrefContent: ContentObject = null; 15 | 16 | if (typeof hrefArgument === 'string') { 17 | hrefString = hrefArgument; 18 | } else { 19 | hrefContent = hrefArgument; 20 | hrefString = hrefArgument.__metadata.urlPath; 21 | } 22 | 23 | // Pass Any internal link to Next.js Link, for anything else, use tag 24 | const internal = /^\/(?!\/)/.test(hrefString); 25 | const linkTag = internal ? ( 26 | 27 | {children} 28 | 29 | ) : ( 30 | 31 | {children} 32 | 33 | ); 34 | 35 | return hrefContent ? {linkTag} : linkTag; 36 | }; 37 | 38 | export default Link; 39 | -------------------------------------------------------------------------------- /src/components/atoms/Social/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | 3 | import { Annotated } from '@/components/Annotated'; 4 | import { iconMap } from '@/components/svgs'; 5 | import Link from '../Link'; 6 | 7 | export default function Social(props) { 8 | const { elementId, className, label, altText, url, icon = 'facebook' } = props; 9 | const IconComponent = iconMap[icon]; 10 | 11 | return ( 12 | 13 | 19 | {label && {label}} 20 | {IconComponent && } 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/components/atoms/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Action } from './Action'; 2 | export { default as BackgroundImage } from './BackgroundImage'; 3 | export { default as Link } from './Link'; 4 | export { default as Social } from './Social'; 5 | -------------------------------------------------------------------------------- /src/components/layouts/BaseLayout/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { Annotated } from '@/components/Annotated'; 4 | import { BackgroundImage } from '@/components/atoms'; 5 | import Footer from '@/components/sections/Footer'; 6 | import Header from '@/components/sections/Header'; 7 | import { PageComponentProps } from '@/types'; 8 | import { PageModelType } from '@/types/generated'; 9 | 10 | type BaseLayoutProps = React.PropsWithChildren & PageComponentProps & PageModelType; 11 | 12 | const BaseLayout: React.FC = (props) => { 13 | const { global, ...page } = props; 14 | const { site } = global; 15 | 16 | return ( 17 |
18 | {page?.backgroundImage && } 19 | {site.header && ( 20 | 21 | 22 |
23 | 24 | 25 | )} 26 | 27 |
28 | {props.children} 29 |
30 |
31 | {site.footer && ( 32 | 33 | 34 |
35 | 36 | 37 | )} 38 |
39 | ); 40 | }; 41 | 42 | export default BaseLayout; 43 | -------------------------------------------------------------------------------- /src/components/layouts/PageLayout/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { DynamicComponent } from '@/components/components-registry'; 4 | import { PageComponentProps, PageLayout } from '@/types'; 5 | import BaseLayout from '../BaseLayout'; 6 | 7 | type ComponentProps = PageComponentProps & PageLayout; 8 | 9 | const Component: React.FC = (props) => { 10 | const { sections = [] } = props; 11 | 12 | return ( 13 | 14 | {sections.map((section, index) => { 15 | return ; 16 | })} 17 | 18 | ); 19 | }; 20 | export default Component; 21 | -------------------------------------------------------------------------------- /src/components/layouts/PostFeedLayout/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { DynamicComponent } from '@/components/components-registry'; 4 | import PostFeedSection from '@/components/sections/PostFeedSection'; 5 | import { PageComponentProps, PostFeedLayout, PostLayout } from '@/types'; 6 | import BaseLayout from '../BaseLayout'; 7 | 8 | type ComponentProps = PageComponentProps & PostFeedLayout & { items: PostLayout[] }; 9 | 10 | const Component: React.FC = (props) => { 11 | const { topSections = [], bottomSections = [], items, postFeed } = props; 12 | 13 | return ( 14 | 15 | {topSections?.map((section, index) => { 16 | return ; 17 | })} 18 | 19 | {bottomSections?.map((section, index) => { 20 | return ; 21 | })} 22 | 23 | ); 24 | }; 25 | export default Component; 26 | -------------------------------------------------------------------------------- /src/components/layouts/PostLayout/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import dayjs from 'dayjs'; 3 | import Markdown from 'markdown-to-jsx'; 4 | import * as React from 'react'; 5 | 6 | import { DynamicComponent } from '@/components/components-registry'; 7 | import { PageComponentProps, PostLayout } from '@/types'; 8 | import HighlightedPreBlock from '@/utils/highlighted-markdown'; 9 | import BaseLayout from '../BaseLayout'; 10 | 11 | type ComponentProps = PageComponentProps & PostLayout; 12 | 13 | const Component: React.FC = (props) => { 14 | const { title, date, author, markdownContent, media, bottomSections = [] } = props; 15 | const dateTimeAttr = dayjs(date).format('YYYY-MM-DD HH:mm:ss'); 16 | const formattedDate = dayjs(date).format('YYYY-MM-DD'); 17 | 18 | return ( 19 | 20 |
21 |
22 |
23 | 24 | {author && ( 25 | <> 26 | {' | '} 27 | {author.firstName} {author.lastName} 28 | 29 | )} 30 |
31 |

{title}

32 |
33 | {media && ( 34 |
35 | 36 |
37 | )} 38 | {markdownContent && ( 39 | 43 | {markdownContent} 44 | 45 | )} 46 |
47 | {bottomSections?.map((section, index) => { 48 | return ; 49 | })} 50 |
51 | ); 52 | }; 53 | export default Component; 54 | 55 | function PostMedia({ media }) { 56 | return ; 57 | } 58 | -------------------------------------------------------------------------------- /src/components/layouts/ProjectFeedLayout/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { DynamicComponent } from '@/components/components-registry'; 4 | import ProjectFeedSection from '@/components/sections/ProjectFeedSection'; 5 | import { PageComponentProps, ProjectFeedLayout, ProjectLayout } from '@/types'; 6 | import BaseLayout from '../BaseLayout'; 7 | 8 | type ComponentProps = PageComponentProps & ProjectFeedLayout & { items: ProjectLayout[] }; 9 | 10 | const Component: React.FC = (props) => { 11 | const { topSections = [], bottomSections = [], items, projectFeed } = props; 12 | 13 | return ( 14 | 15 | {topSections?.map((section, index) => { 16 | return ; 17 | })} 18 | 19 | {bottomSections?.map((section, index) => { 20 | return ; 21 | })} 22 | 23 | ); 24 | }; 25 | export default Component; 26 | -------------------------------------------------------------------------------- /src/components/molecules/FormBlock/CheckboxFormControl/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | 3 | export default function CheckboxFormControl(props) { 4 | const { name, label, isRequired, width = 'full' } = props; 5 | const labelId = `${name}-label`; 6 | const attr: any = {}; 7 | if (label) { 8 | attr['aria-labelledby'] = labelId; 9 | } 10 | if (isRequired) { 11 | attr.required = true; 12 | } 13 | return ( 14 |
15 | 22 | {label && ( 23 | 30 | )} 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/components/molecules/FormBlock/EmailFormControl/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | 3 | export default function EmailFormControl(props) { 4 | const { name, label, hideLabel, placeholder, isRequired, width = 'full' } = props; 5 | const labelId = `${name}-label`; 6 | const attr: any = {}; 7 | if (label) { 8 | attr['aria-labelledby'] = labelId; 9 | } 10 | if (isRequired) { 11 | attr.required = true; 12 | } 13 | if (placeholder) { 14 | attr.placeholder = placeholder; 15 | } 16 | return ( 17 |
18 | {label && ( 19 | 26 | )} 27 | 34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/components/molecules/FormBlock/SelectFormControl/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | 3 | export default function SelectFormControl(props) { 4 | const { name, label, hideLabel, defaultValue, options = [], isRequired, width = 'full' } = props; 5 | const labelId = `${name}-label`; 6 | const attr: any = {}; 7 | if (label) { 8 | attr['aria-labelledby'] = labelId; 9 | } 10 | if (isRequired) { 11 | attr.required = true; 12 | } 13 | return ( 14 |
15 | {label && ( 16 | 23 | )} 24 | 38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/components/molecules/FormBlock/TextFormControl/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | 3 | export default function TextFormControl(props) { 4 | const { name, label, hideLabel, placeholder, isRequired, width = 'full' } = props; 5 | const labelId = `${name}-label`; 6 | const attr: any = {}; 7 | if (label) { 8 | attr['aria-labelledby'] = labelId; 9 | } 10 | if (isRequired) { 11 | attr.required = true; 12 | } 13 | if (placeholder) { 14 | attr.placeholder = placeholder; 15 | } 16 | return ( 17 |
18 | {label && ( 19 | 26 | )} 27 | 34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/components/molecules/FormBlock/TextareaFormControl/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | 3 | export default function TextareaFormControl(props) { 4 | const { name, label, hideLabel, placeholder, isRequired, width = 'full' } = props; 5 | const labelId = `${name}-label`; 6 | const attr: any = {}; 7 | if (label) { 8 | attr['aria-labelledby'] = labelId; 9 | } 10 | if (isRequired) { 11 | attr.required = true; 12 | } 13 | if (placeholder) { 14 | attr.placeholder = placeholder; 15 | } 16 | return ( 17 |
18 | {label && ( 19 | 26 | )} 27 |