Add spfx tailwindcss3 sample

This commit is contained in:
AriGunawan 2023-10-07 22:01:04 +07:00
parent 5459dd34de
commit aceeb95902
29 changed files with 36356 additions and 0 deletions

View File

@ -0,0 +1,99 @@
# Tailwind 3
## Summary
This project showcases an integration of Tailwind CSS into an SPFx project. It offers a sample implementation, facilitating straightforward setup and utilization of Tailwind CSS for enhanced styling and design in SharePoint Framework projects.
Here is an illustration of the styling outcome achieved with Tailwind, along with the corresponding code.
![Result](assets/example.png)
![Code](assets/code.png)
To setup Tailwind on your existing project, proceed with the following steps:
1. Execute the following commands:
- `npm i -D tailwindcss postcss autoprefixer gulp-postcss`
- `npx tailwindcss init`
2. Add the following files:
- /assets/tailwind.css
- postcss.config.js
- tailwind.config.js
3. Update the following files:
- gulpfile.js
- Check the changes in this project
- entry/main component file (e.g. /src/webparts/HelloTailwind/HelloTailwindWebPart.ts)
- Add `import '../../../assets/dist/tailwind.css`
## Compatibility
| :warning: Important |
|:---------------------------|
| Every SPFx version is optimally compatible with specific versions of Node.js. In order to be able to build this sample, you need to ensure that the version of Node on your workstation matches one of the versions listed in this section. This sample will not work on a different version of Node.|
|Refer to <https://aka.ms/spfx-matrix> for more information on SPFx compatibility. |
This sample is optimally compatible with the following environment configuration:
![SPFx 1.18.0](https://img.shields.io/badge/SPFx-1.18.0-green.svg)
![Node.js v18.18](https://img.shields.io/badge/Node.js-v18.18+-green.svg)
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
![Local Workbench Unsupported](https://img.shields.io/badge/Local%20Workbench-Unsupported-red.svg "Local workbench is no longer available as of SPFx 1.13 and above")
![Hosted Workbench Compatible](https://img.shields.io/badge/Hosted%20Workbench-Compatible-green.svg)
![Compatible with Remote Containers](https://img.shields.io/badge/Remote%20Containers-Compatible-green.svg)
## Applies to
* [SharePoint Framework](https://learn.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [Microsoft 365 tenant](https://learn.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
> Get your own free development tenant by subscribing to [Microsoft 365 developer program](http://aka.ms/m365devprogram)
## Contributors
* [Ari Gunawan](https://github.com/AriGunawan)
## Version history
Version|Date|Comments
-------|----|--------
1.0|October 7, 2023|Initial release
## Minimal path to awesome
* Clone this repository (or [download this solution as a .ZIP file](https://pnp.github.io/download-partial/?url=https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-tailwindcss3) then unzip it)
* From your command line, change your current directory to the directory containing this sample (`react-tailwindcss3`, located under `samples`)
* in the command line run:
* `npm install`
* `gulp serve`
> This sample can also be opened with [VS Code Remote Development](https://code.visualstudio.com/docs/remote/remote-overview). Visit <https://aka.ms/spfx-devcontainer> for further instructions.
<!--
RESERVED FOR REPO MAINTAINERS
We'll add the video from the community call recording here
## Video
[![YouTube video title](./assets/video-thumbnail.jpg)](https://www.youtube.com/watch?v=XXXXX "YouTube video title")
-->
## Help
We do not support samples, but this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues.
If you're having issues building the solution, please run [spfx doctor](https://pnp.github.io/cli-microsoft365/cmd/spfx/spfx-doctor/) from within the solution folder to diagnose incompatibility issues with your environment.
You can try looking at [issues related to this sample](https://github.com/pnp/sp-dev-fx-webparts/issues?q=label%3A%22sample%3A%20react-tailwindcss3%22) to see if anybody else is having the same issues.
You can also try looking at [discussions related to this sample](https://github.com/pnp/sp-dev-fx-webparts/discussions?discussions_q=react-tailwindcss3) and see what the community is saying.
If you encounter any issues using this sample, [create a new issue](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected%2Csample%3A%20react-tailwindcss3&template=bug-report.yml&sample=react-tailwindcss3&authors=@AriGunawan&title=react-tailwindcss3%20-%20).
For questions regarding this sample, [create a new question](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Aquestion%2Csample%3A%20react-tailwindcss3&template=question.yml&sample=react-tailwindcss3&authors=@AriGunawan&title=react-tailwindcss3%20-%20).
Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Aenhancement%2Csample%3A%20react-tailwindcss3&template=suggestion.yml&sample=react-tailwindcss3&authors=@AriGunawan&title=react-tailwindcss3%20-%20).
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
<img src="https://m365-visitor-stats.azurewebsites.net/sp-dev-fx-webparts/samples/react-tailwindcss3" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,18 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"hello-tailwind-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/helloTailwind/HelloTailwindWebPart.js",
"manifest": "./src/webparts/helloTailwind/HelloTailwindWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"HelloTailwindWebPartStrings": "lib/webparts/helloTailwind/loc/{locale}.js"
}
}

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./release/assets/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "spfx-tailwind",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,40 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "SPFx Tailwind",
"id": "9c2d370c-9957-4e3f-b322-6b2cbd458c69",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false,
"developer": {
"name": "",
"websiteUrl": "",
"privacyUrl": "",
"termsOfUseUrl": "",
"mpnId": "Undefined-1.18.0"
},
"metadata": {
"shortDescription": {
"default": "spfx-tailwind description"
},
"longDescription": {
"default": "spfx-tailwind description"
},
"screenshotPaths": [],
"videoUrl": "",
"categories": []
},
"features": [
{
"title": "spfx-tailwind Feature",
"description": "The feature that activates elements of the spfx-tailwind solution.",
"id": "57651270-7533-41c5-bbc9-88bc9494876c",
"version": "1.0.0.0"
}
]
},
"paths": {
"zippedPackage": "solution/SPFx Tailwind.sppkg"
}
}

View File

@ -0,0 +1,3 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/sass.schema.json"
}

View File

@ -0,0 +1,6 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://yourdomain.sharepoint.com/_layouts/workbench.aspx"
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/s-KaiNet/spfx-fast-serve/master/schema/config.latest.schema.json",
"cli": {
"isLibraryComponent": false
}
}

View File

@ -0,0 +1,31 @@
/*
* User webpack settings file. You can add your own settings here.
* Changes from this file will be merged into the base webpack configuration file.
* This file will not be overwritten by the subsequent spfx-fast-serve calls.
*/
/**
* you can add your project related webpack configuration here, it will be merged using webpack-merge module
* i.e. plugins: [new webpack.Plugin()]
*/
const webpackConfig = {
}
/**
* For even more fine-grained control, you can apply custom webpack settings using below function
* @param {object} initialWebpackConfig - initial webpack config object
* @param {object} webpack - webpack object, used by SPFx pipeline
* @returns webpack config object
*/
const transformConfig = function (initialWebpackConfig, webpack) {
// transform the initial webpack config here, i.e.
// initialWebpackConfig.plugins.push(new webpack.Plugin()); etc.
return initialWebpackConfig;
}
module.exports = {
webpackConfig,
transformConfig
}

43
samples/react-tailwindcss3/gulpfile.js vendored Normal file
View File

@ -0,0 +1,43 @@
'use strict';
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
var getTasks = build.rig.getTasks;
build.rig.getTasks = function () {
var result = getTasks.call(build.rig);
result.set('serve', result.get('serve-deprecated'));
return result;
};
/* tailwind */
const postcss = require("gulp-postcss");
const tailwind = require("tailwindcss");
const tailwindcss = build.subTask(
"tailwindcss",
function (gulp, buildOptions, done) {
gulp
.src("assets/tailwind.css")
.pipe(
postcss([
tailwind("./tailwind.config.js"),
])
)
.pipe(gulp.dest("assets/dist"));
done();
}
);
build.rig.addPreBuildTask(tailwindcss);
/* end of tailwind */
/* fast-serve */
const { addFastServe } = require("spfx-fast-serve-helpers");
addFastServe(build);
/* end of fast-serve */
build.initialize(require('gulp'));

35763
samples/react-tailwindcss3/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
{
"name": "spfx-tailwind",
"version": "0.0.1",
"private": true,
"engines": {
"node": ">=16.13.0 <17.0.0 || >=18.17.1 <19.0.0"
},
"main": "lib/index.js",
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test",
"serve": "gulp bundle --custom-serve --max_old_space_size=4096 && fast-serve"
},
"dependencies": {
"@fluentui/react": "^8.106.4",
"@microsoft/sp-component-base": "1.18.0",
"@microsoft/sp-core-library": "1.18.0",
"@microsoft/sp-lodash-subset": "1.18.0",
"@microsoft/sp-office-ui-fabric-core": "1.18.0",
"@microsoft/sp-property-pane": "1.18.0",
"@microsoft/sp-webpart-base": "1.18.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"tslib": "2.3.1"
},
"devDependencies": {
"@microsoft/eslint-config-spfx": "1.18.0",
"@microsoft/eslint-plugin-spfx": "1.18.0",
"@microsoft/rush-stack-compiler-4.7": "0.1.0",
"@microsoft/sp-build-web": "1.18.0",
"@microsoft/sp-module-interfaces": "1.18.0",
"@rushstack/eslint-config": "2.5.1",
"@types/react": "17.0.45",
"@types/react-dom": "17.0.17",
"@types/webpack-env": "~1.15.2",
"ajv": "^6.12.5",
"autoprefixer": "^10.4.16",
"eslint": "8.7.0",
"eslint-plugin-react-hooks": "4.3.0",
"gulp": "4.0.2",
"gulp-postcss": "^9.0.1",
"postcss": "^8.4.31",
"spfx-fast-serve-helpers": "~1.18.0",
"tailwindcss": "^3.3.3",
"typescript": "4.7.4"
}
}

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,28 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "32d20565-ce71-49e4-ac49-ec8dd74a44fa",
"alias": "HelloTailwindWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart", "TeamsPersonalApp", "TeamsTab", "SharePointFullPage"],
"supportsThemeVariants": true,
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Advanced
"group": { "default": "Advanced" },
"title": { "default": "Hello Tailwind" },
"description": { "default": "HelloTailwind description" },
"officeFabricIconFontName": "Color",
"properties": {
"description": "HelloTailwind"
}
}]
}

View File

@ -0,0 +1,123 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { Version } from '@microsoft/sp-core-library';
import {
type IPropertyPaneConfiguration,
PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { IReadonlyTheme } from '@microsoft/sp-component-base';
import * as strings from 'HelloTailwindWebPartStrings';
import HelloTailwind from './components/HelloTailwind';
import { IHelloTailwindProps } from './components/IHelloTailwindProps';
import '../../../assets/dist/tailwind.css';
export interface IHelloTailwindWebPartProps {
description: string;
}
export default class HelloTailwindWebPart extends BaseClientSideWebPart<IHelloTailwindWebPartProps> {
private _isDarkTheme: boolean = false;
private _environmentMessage: string = '';
public render(): void {
const element: React.ReactElement<IHelloTailwindProps> = React.createElement(
HelloTailwind,
{
description: this.properties.description,
isDarkTheme: this._isDarkTheme,
environmentMessage: this._environmentMessage,
hasTeamsContext: !!this.context.sdks.microsoftTeams,
userDisplayName: this.context.pageContext.user.displayName
}
);
ReactDom.render(element, this.domElement);
}
protected onInit(): Promise<void> {
return this._getEnvironmentMessage().then(message => {
this._environmentMessage = message;
});
}
private _getEnvironmentMessage(): Promise<string> {
if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook
return this.context.sdks.microsoftTeams.teamsJs.app.getContext()
.then(context => {
let environmentMessage: string = '';
switch (context.app.host.name) {
case 'Office': // running in Office
environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment;
break;
case 'Outlook': // running in Outlook
environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment;
break;
case 'Teams': // running in Teams
case 'TeamsModern':
environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
break;
default:
environmentMessage = strings.UnknownEnvironment;
}
return environmentMessage;
});
}
return Promise.resolve(this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment);
}
protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
if (!currentTheme) {
return;
}
this._isDarkTheme = !!currentTheme.isInverted;
const {
semanticColors
} = currentTheme;
if (semanticColors) {
this.domElement.style.setProperty('--bodyText', semanticColors.bodyText || null);
this.domElement.style.setProperty('--link', semanticColors.link || null);
this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered || null);
}
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,41 @@
import * as React from 'react';
import type { IHelloTailwindProps } from './IHelloTailwindProps';
import { escape } from '@microsoft/sp-lodash-subset';
export default class HelloTailwind extends React.Component<IHelloTailwindProps, {}> {
public render(): React.ReactElement<IHelloTailwindProps> {
const {
description,
isDarkTheme,
environmentMessage,
userDisplayName
} = this.props;
return (
<section className='tw-overflow-hidden tw-p-4 tw-text-black'>
<div className='tw-text-center'>
<img alt="" src={isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png')} className='tw-m-auto tw-w-full tw-max-w-[420px]' />
<h2 className='tw-text-2xl'>Well done, {escape(userDisplayName)}!</h2>
<div>{environmentMessage}</div>
<div>Web part property value: <strong>{escape(description)}</strong></div>
</div>
<div>
<h3 className='tw-text-xl tw-text-red-600 tw-mb-2'>Welcome to SharePoint Framework!</h3>
<p>
The SharePoint Framework (SPFx) is a extensibility model for Microsoft Viva, Microsoft Teams and SharePoint. It&#39;s the easiest way to extend Microsoft 365 with automatic Single Sign On, automatic hosting and industry standard tooling.
</p>
<h4 className='tw-text-lg tw-mt-2'>Learn more about SPFx development:</h4>
<ul className='[&_a]:tw-underline [&_a]:tw-text-blue-600'>
<li><a href="https://aka.ms/spfx" target="_blank" rel="noreferrer">SharePoint Framework Overview</a></li>
<li><a href="https://aka.ms/spfx-yeoman-graph" target="_blank" rel="noreferrer">Use Microsoft Graph in your solution</a></li>
<li><a href="https://aka.ms/spfx-yeoman-teams" target="_blank" rel="noreferrer">Build for Microsoft Teams using SharePoint Framework</a></li>
<li><a href="https://aka.ms/spfx-yeoman-viva" target="_blank" rel="noreferrer">Build for Microsoft Viva Connections using SharePoint Framework</a></li>
<li><a href="https://aka.ms/spfx-yeoman-store" target="_blank" rel="noreferrer">Publish SharePoint Framework applications to the marketplace</a></li>
<li><a href="https://aka.ms/spfx-yeoman-api" target="_blank" rel="noreferrer">SharePoint Framework API reference</a></li>
<li><a href="https://aka.ms/m365pnp" target="_blank" rel="noreferrer">Microsoft 365 Developer Community</a></li>
</ul>
</div>
</section>
);
}
}

View File

@ -0,0 +1,7 @@
export interface IHelloTailwindProps {
description: string;
isDarkTheme: boolean;
environmentMessage: string;
hasTeamsContext: boolean;
userDisplayName: string;
}

View File

@ -0,0 +1,16 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field",
"AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part",
"AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app",
"AppLocalEnvironmentOffice": "The app is running on your local environment in office.com",
"AppLocalEnvironmentOutlook": "The app is running on your local environment in Outlook",
"AppSharePointEnvironment": "The app is running on SharePoint page",
"AppTeamsTabEnvironment": "The app is running in Microsoft Teams",
"AppOfficeEnvironment": "The app is running in office.com",
"AppOutlookEnvironment": "The app is running in Outlook",
"UnknownEnvironment": "The app is running in an unknown environment"
}
});

View File

@ -0,0 +1,19 @@
declare interface IHelloTailwindWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
AppLocalEnvironmentSharePoint: string;
AppLocalEnvironmentTeams: string;
AppLocalEnvironmentOffice: string;
AppLocalEnvironmentOutlook: string;
AppSharePointEnvironment: string;
AppTeamsTabEnvironment: string;
AppOfficeEnvironment: string;
AppOutlookEnvironment: string;
UnknownEnvironment: string;
}
declare module 'HelloTailwindWebPartStrings' {
const strings: IHelloTailwindWebPartStrings;
export = strings;
}

View File

@ -0,0 +1,9 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
prefix: 'tw-',
content: ["./src/**/*.{html,js,tsx}"],
theme: {
extend: {},
},
plugins: [],
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

View File

@ -0,0 +1,35 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-4.7/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"noImplicitAny": true,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection",
"es2015.promise"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
]
}