├── .gitattributes
├── .github
├── FUNDING.yml
└── workflows
│ ├── ci.yml
│ ├── close-stale-issues.yml
│ └── publish.yml
├── .gitignore
├── .husky
└── pre-commit
├── .vscode
├── extensions.json
└── settings.json
├── .yarn
└── plugins
│ └── @yarnpkg
│ └── plugin-nolyfill.cjs
├── .yarnrc.yml
├── LICENSE
├── README.md
├── biome.json
├── package.json
├── packages
└── react-clock
│ ├── LICENSE
│ ├── README.md
│ ├── package.json
│ ├── src
│ ├── Clock.css
│ ├── Clock.spec.tsx
│ ├── Clock.tsx
│ ├── Hand.spec.tsx
│ ├── Hand.tsx
│ ├── Mark.spec.tsx
│ ├── Mark.tsx
│ ├── MarkNumber.spec.tsx
│ ├── MarkNumber.tsx
│ ├── index.ts
│ └── shared
│ │ ├── hourFormatter.ts
│ │ ├── types.ts
│ │ ├── utils.spec.ts
│ │ └── utils.ts
│ ├── tsconfig.build.json
│ ├── tsconfig.json
│ ├── vitest.config.ts
│ └── vitest.setup.ts
├── sample
├── .gitignore
├── Sample.css
├── Sample.tsx
├── index.html
├── index.tsx
├── package.json
├── tsconfig.json
├── vite.config.ts
└── yarn.lock
├── test
├── .gitignore
├── LocaleOptions.tsx
├── Test.css
├── Test.tsx
├── ValueOptions.tsx
├── ViewOptions.tsx
├── index.html
├── index.tsx
├── package.json
├── shared
│ └── types.ts
├── tsconfig.json
└── vite.config.ts
└── yarn.lock
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: wojtekmaj
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: ['*']
6 | pull_request:
7 | branches: [main]
8 |
9 | env:
10 | HUSKY: 0
11 |
12 | jobs:
13 | lint:
14 | name: Static code analysis
15 | runs-on: ubuntu-24.04-arm
16 |
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v4
20 |
21 | - name: Setup Biome
22 | uses: biomejs/setup-biome@v2
23 |
24 | - name: Run tests
25 | run: biome lint
26 |
27 | typescript:
28 | name: Type checking
29 | runs-on: ubuntu-24.04-arm
30 |
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v4
34 |
35 | - name: Cache Yarn cache
36 | uses: actions/cache@v4
37 | env:
38 | cache-name: yarn-cache
39 | with:
40 | path: ~/.yarn/berry/cache
41 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
42 | restore-keys: |
43 | ${{ runner.os }}-${{ env.cache-name }}
44 |
45 | - name: Use Node.js
46 | uses: actions/setup-node@v4
47 | with:
48 | node-version: '22'
49 |
50 | - name: Enable Corepack
51 | run: corepack enable
52 |
53 | - name: Install dependencies
54 | run: yarn --immutable
55 |
56 | - name: Build package
57 | run: yarn build
58 |
59 | - name: Run type checking
60 | run: yarn tsc
61 |
62 | format:
63 | name: Formatting
64 | runs-on: ubuntu-24.04-arm
65 |
66 | steps:
67 | - name: Checkout
68 | uses: actions/checkout@v4
69 |
70 | - name: Setup Biome
71 | uses: biomejs/setup-biome@v2
72 |
73 | - name: Run formatting
74 | run: biome format
75 |
76 | unit:
77 | name: Unit tests
78 | runs-on: ubuntu-24.04-arm
79 |
80 | steps:
81 | - name: Checkout
82 | uses: actions/checkout@v4
83 |
84 | - name: Cache Yarn cache
85 | uses: actions/cache@v4
86 | env:
87 | cache-name: yarn-cache
88 | with:
89 | path: ~/.yarn/berry/cache
90 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
91 | restore-keys: |
92 | ${{ runner.os }}-${{ env.cache-name }}
93 |
94 | - name: Use Node.js
95 | uses: actions/setup-node@v4
96 | with:
97 | node-version: '22'
98 |
99 | - name: Enable Corepack
100 | run: corepack enable
101 |
102 | - name: Install dependencies
103 | run: yarn --immutable
104 |
105 | - name: Run tests
106 | run: yarn unit
107 |
--------------------------------------------------------------------------------
/.github/workflows/close-stale-issues.yml:
--------------------------------------------------------------------------------
1 | name: Close stale issues
2 |
3 | on:
4 | schedule:
5 | - cron: '0 0 * * 1' # Every Monday
6 | workflow_dispatch:
7 |
8 | jobs:
9 | close-issues:
10 | name: Close stale issues
11 | runs-on: ubuntu-24.04-arm
12 |
13 | steps:
14 | - name: Close stale issues
15 | uses: actions/stale@v8
16 | with:
17 | days-before-issue-stale: 90
18 | days-before-issue-close: 14
19 | stale-issue-label: 'stale'
20 | stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this issue will be closed in 14 days.'
21 | close-issue-message: 'This issue was closed because it has been stalled for 14 days with no activity.'
22 | exempt-issue-labels: 'fresh'
23 | remove-issue-stale-when-updated: true
24 | days-before-pr-stale: -1
25 | days-before-pr-close: -1
26 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | env:
8 | HUSKY: 0
9 |
10 | permissions:
11 | id-token: write
12 |
13 | jobs:
14 | publish:
15 | name: Publish
16 | runs-on: ubuntu-24.04-arm
17 |
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v4
21 |
22 | - name: Cache Yarn cache
23 | uses: actions/cache@v4
24 | env:
25 | cache-name: yarn-cache
26 | with:
27 | path: ~/.yarn/berry/cache
28 | key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('**/yarn.lock') }}
29 | restore-keys: |
30 | ${{ runner.os }}-${{ env.cache-name }}
31 |
32 | - name: Use Node.js
33 | uses: actions/setup-node@v4
34 | with:
35 | node-version: '22'
36 | registry-url: 'https://registry.npmjs.org'
37 |
38 | - name: Enable Corepack
39 | run: corepack enable
40 |
41 | - name: Install dependencies
42 | run: yarn --immutable
43 |
44 | - name: Publish with latest tag
45 | if: github.event.release.prelease == false
46 | run: yarn npm publish --tag latest --provenance
47 | working-directory: packages/react-clock
48 | env:
49 | YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
50 |
51 | - name: Publish with next tag
52 | if: github.event.release.prelease == true
53 | run: yarn npm publish --tag next --provenance
54 | working-directory: packages/react-clock
55 | env:
56 | YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS
2 | .DS_Store
3 |
4 | # Cache
5 | .cache
6 | .playwright
7 | .tmp
8 | *.tsbuildinfo
9 | .eslintcache
10 |
11 | # Yarn
12 | .pnp.*
13 | **/.yarn/*
14 | !**/.yarn/patches
15 | !**/.yarn/plugins
16 | !**/.yarn/releases
17 | !**/.yarn/sdks
18 | !**/.yarn/versions
19 |
20 | # Project-generated directories and files
21 | coverage
22 | dist
23 | node_modules
24 | playwright-report
25 | test-results
26 | package.tgz
27 |
28 | # Logs
29 | npm-debug.log
30 | yarn-error.log
31 |
32 | # .env files
33 | **/.env
34 | **/.env.*
35 | !**/.env.example
36 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | yarn format --staged --no-errors-on-unmatched --write
2 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["biomejs.biome"],
3 | "unwantedRecommendations": ["dbaeumer.jshint", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.defaultFormatter": "biomejs.biome",
3 | "editor.formatOnSave": true,
4 | "search.exclude": {
5 | "**/.yarn": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.yarn/plugins/@yarnpkg/plugin-nolyfill.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | //prettier-ignore
3 | module.exports = {
4 | name: "@yarnpkg/plugin-nolyfill",
5 | factory: function (require) {
6 | "use strict";var plugin=(()=>{var p=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var n=Object.getOwnPropertyNames;var y=Object.prototype.hasOwnProperty;var c=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(r,e)=>(typeof require<"u"?require:r)[e]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+t+'" is not supported')});var l=(t,r)=>{for(var e in r)p(t,e,{get:r[e],enumerable:!0})},g=(t,r,e,s)=>{if(r&&typeof r=="object"||typeof r=="function")for(let a of n(r))!y.call(t,a)&&a!==e&&p(t,a,{get:()=>r[a],enumerable:!(s=i(r,a))||s.enumerable});return t};var f=t=>g(p({},"__esModule",{value:!0}),t);var b={};l(b,{default:()=>u});var o=c("@yarnpkg/core"),d=["array-buffer-byte-length","array-includes","array.from","array.of","array.prototype.at","array.prototype.every","array.prototype.find","array.prototype.findlast","array.prototype.findlastindex","array.prototype.flat","array.prototype.flatmap","array.prototype.flatmap","array.prototype.foreach","array.prototype.reduce","array.prototype.tosorted","arraybuffer.prototype.slice","assert","asynciterator.prototype","available-typed-arrays","deep-equal","define-properties","es-aggregate-error","es-iterator-helpers","es-set-tostringtag","es6-object-assign","function-bind","function.prototype.name","get-symbol-description","globalthis","gopd","harmony-reflect","has","has-property-descriptors","has-proto","has-symbols","has-tostringtag","hasown","internal-slot","is-arguments","is-array-buffer","is-date-object","is-generator-function","is-nan","is-regex","is-shared-array-buffer","is-string","is-symbol","is-typed-array","is-weakref","isarray","iterator.prototype","jsonify","object-is","object-keys","object.assign","object.entries","object.fromentries","object.getownpropertydescriptors","object.groupby","object.hasown","object.values","promise.allsettled","promise.any","reflect.getprototypeof","reflect.ownkeys","regexp.prototype.flags","safe-array-concat","safe-regex-test","set-function-length","side-channel","string.prototype.at","string.prototype.codepointat","string.prototype.includes","string.prototype.matchall","string.prototype.padend","string.prototype.padstart","string.prototype.repeat","string.prototype.replaceall","string.prototype.split","string.prototype.startswith","string.prototype.trim","string.prototype.trimend","string.prototype.trimleft","string.prototype.trimright","string.prototype.trimstart","typed-array-buffer","typed-array-byte-length","typed-array-byte-offset","typed-array-length","typedarray","unbox-primitive","which-boxed-primitive","which-typed-array"],h=new Map(d.map(t=>[o.structUtils.makeIdent(null,t).identHash,o.structUtils.makeIdent("nolyfill",t)])),m={hooks:{reduceDependency:async t=>{let r=h.get(t.identHash);if(r){let e=o.structUtils.makeDescriptor(r,"latest"),s=o.structUtils.makeRange({protocol:"npm:",source:null,selector:o.structUtils.stringifyDescriptor(e),params:null});return o.structUtils.makeDescriptor(t,s)}return t}}},u=m;return f(b);})();
7 | return plugin;
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/.yarnrc.yml:
--------------------------------------------------------------------------------
1 | logFilters:
2 | - code: YN0076
3 | level: discard
4 |
5 | nodeLinker: node-modules
6 |
7 | plugins:
8 | - checksum: e3ca535b4c4288976eebb726082e2e6547c43e0ba1492b3ddbb0cdadc9d61d82ff14307358da06c46a446328345a464364d6c148b2d39fccc18cf6d232291858
9 | path: .yarn/plugins/@yarnpkg/plugin-nolyfill.cjs
10 | spec: 'https://raw.githubusercontent.com/wojtekmaj/yarn-plugin-nolyfill/v0.1.1/bundles/@yarnpkg/plugin-nolyfill.js'
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017–2024 Wojciech Maj
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | packages/react-clock/README.md
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
3 | "files": {
4 | "ignore": [".tsimp", ".yarn", "coverage", "dist", ".pnp.cjs", ".pnp.loader.mjs"]
5 | },
6 | "formatter": {
7 | "lineWidth": 100,
8 | "indentStyle": "space"
9 | },
10 | "linter": {
11 | "rules": {
12 | "complexity": {
13 | "noUselessSwitchCase": "off"
14 | },
15 | "correctness": {
16 | "noUnusedImports": "warn",
17 | "noUnusedVariables": "warn"
18 | },
19 | "suspicious": {
20 | "noConsoleLog": "warn"
21 | }
22 | }
23 | },
24 | "css": {
25 | "formatter": {
26 | "quoteStyle": "single"
27 | }
28 | },
29 | "javascript": {
30 | "formatter": {
31 | "quoteStyle": "single"
32 | }
33 | },
34 | "overrides": [
35 | {
36 | "include": ["**/package.json"],
37 | "formatter": {
38 | "lineWidth": 1
39 | }
40 | },
41 | {
42 | "include": ["**/vite.config.ts"],
43 | "linter": {
44 | "rules": {
45 | "suspicious": {
46 | "noConsoleLog": "off"
47 | }
48 | }
49 | }
50 | }
51 | ]
52 | }
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-clock-monorepo",
3 | "version": "1.0.0",
4 | "description": "react-clock monorepo",
5 | "type": "module",
6 | "workspaces": [
7 | "packages/*",
8 | "test"
9 | ],
10 | "scripts": {
11 | "build": "yarn workspace react-clock build",
12 | "dev": "yarn workspace react-clock watch & yarn workspace test dev",
13 | "format": "yarn workspaces foreach --all run format",
14 | "lint": "yarn workspaces foreach --all run lint",
15 | "postinstall": "husky",
16 | "test": "yarn workspaces foreach --all run test",
17 | "tsc": "yarn workspaces foreach --all run tsc",
18 | "unit": "yarn workspaces foreach --all run unit"
19 | },
20 | "devDependencies": {
21 | "husky": "^9.0.0"
22 | },
23 | "packageManager": "yarn@4.9.1"
24 | }
25 |
--------------------------------------------------------------------------------
/packages/react-clock/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017–2024 Wojciech Maj
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/react-clock/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/react-clock)  [](https://github.com/wojtekmaj/react-clock/actions)
2 |
3 | # react-clock
4 |
5 | An analog clock for your React app.
6 |
7 | ## tl;dr
8 |
9 | - Install by executing `npm install react-clock` or `yarn add react-clock`.
10 | - Import by adding `import Clock from 'react-clock'`.
11 | - Use by adding ``.
12 |
13 | ## Demo
14 |
15 | A minimal demo page can be found in `sample` directory.
16 |
17 | [Online demo](https://projects.wojtekmaj.pl/react-clock/) is also available!
18 |
19 | ## Installation
20 |
21 | Add react-clock to your project by executing `npm install react-clock` or `yarn add react-clock`.
22 |
23 | ### Usage
24 |
25 | Here's an example of basic usage:
26 |
27 | ```tsx
28 | import { useEffect, useState } from 'react';
29 | import Clock from 'react-clock';
30 |
31 | function MyApp() {
32 | const [value, setValue] = useState(new Date());
33 |
34 | useEffect(() => {
35 | const interval = setInterval(() => setValue(new Date()), 1000);
36 |
37 | return () => {
38 | clearInterval(interval);
39 | };
40 | }, []);
41 |
42 | return (
43 |
44 |
Current time:
45 |
46 |
47 | );
48 | }
49 | ```
50 |
51 | ### Custom styling
52 |
53 | If you want to use default react-clock styling to build upon it, you can import react-clock's styles by using:
54 |
55 | ```ts
56 | import 'react-clock/dist/Clock.css';
57 | ```
58 |
59 | ## User guide
60 |
61 | ### Clock
62 |
63 | Displays a complete clock.
64 |
65 | #### Props
66 |
67 | | Prop name | Description | Default value | Example values |
68 | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------- |
69 | | className | Class name(s) that will be added along with `"react-clock"` to the main react-clock `